Forum Discussion
robmo
Jun 07, 2022Brass Contributor
Validating PowerShell operations - Delete file
Hi,
What is the most accurate and efficient way to be certain PowerShell has executed a task with no discrepancies? In this case, I need to delete files with a certain extension (*.jpg). I have been able to successfully delete the files on my assigned computer but when I tried it on a virtual machine, the file was not deleted and my error detection reported no discrepancy using $?
I am looking for the best solution to ensure that if I delete a file, there is no question the deletion succeeded and there is no need to check the file system. I created a function due to making this same validation several times in my script. Here is my sample:
function Get-ErrorStatus() {
#Check the error status of the last operation
if ($?) {
#The last operation succeeded
return $true
}
else {
#The last operation failed
return $false
}
}
$files = Get-ChildItem -Path "C:\Temp" -Name -Include "*.jpg"
foreach ($file in $files) {
Remove-Item -Path "C:\Temp\$file" -Force | Out-Null
#Check error status
if (Get-ErrorStatus) {
#Delete succeeded
Write-Host "Delete succeeded: $file"
}
else {
#Delete failed
Write-Host "Delete failed: $file"
}
}
I believe this approach is failing because $? is looking at the Get-ErrorStatus() call which always succeeds rather than looking at the delete operation just before that.
Thank you!
Rob
Hi, Rob.
The Try..Catch method mentioned by Harm_Veenstra is the best (though there's a good chance the "catch" won't be used since ErrorAction defaults to "Continue" instead of "Stop".)
However, since you mentioned "efficient" and "no need to check the file system", here are some minor adjustments that deliver against those requirements.
- Avoid storing results in variables unless it's unavoidable;
- Leverage the -ErrorAction:Stop parameter.
Point 1 allows scripts to scale up significantly better through not churning memory and allowing the GC (garbage collector) to release objects earlier, while point 2 allows you to avoid checking the file system after the Remove-Item.
I've spaced it out here for readability, but really, this could be a one-liner.
Get-ChildItem -Path "C:\Temp\" -Filter "*.jpg" | ForEach-Object { try { $FilePath = $_.FullName; Remove-Item -Path $FilePath -ErrorAction:Stop; "Deleted $FilePath."; } catch { "Failed to delete $FilePath."; } }
Cheers,
Lain
Edited to correct a typo.
- LainRobertsonSilver Contributor
Hi, Rob.
The Try..Catch method mentioned by Harm_Veenstra is the best (though there's a good chance the "catch" won't be used since ErrorAction defaults to "Continue" instead of "Stop".)
However, since you mentioned "efficient" and "no need to check the file system", here are some minor adjustments that deliver against those requirements.
- Avoid storing results in variables unless it's unavoidable;
- Leverage the -ErrorAction:Stop parameter.
Point 1 allows scripts to scale up significantly better through not churning memory and allowing the GC (garbage collector) to release objects earlier, while point 2 allows you to avoid checking the file system after the Remove-Item.
I've spaced it out here for readability, but really, this could be a one-liner.
Get-ChildItem -Path "C:\Temp\" -Filter "*.jpg" | ForEach-Object { try { $FilePath = $_.FullName; Remove-Item -Path $FilePath -ErrorAction:Stop; "Deleted $FilePath."; } catch { "Failed to delete $FilePath."; } }
Cheers,
Lain
Edited to correct a typo.
- robmoBrass ContributorI definitely need to experiment with this more but I do have a better understanding now.
Thank you for taking the time to respond! - robmoBrass Contributor
If you set ErrorAction:Stop, will that terminate the script execution? I need the script to attempt deleting all files found and report if it succeeded or not.
For example, if Get-ChildItem collects 5 files, I need the script to attempt deleting all of these while tracking if an error occurred.
I am trying our both yours and Harm_Veenstra's code to see what works best.
Thank you!
- LainRobertsonSilver Contributor
Hey, Rob.
ErrorAction:Stop only causes execution of the current scope to exit.
When used in conjunction with the try...catch block, it will only halt processing within the "try" block and jump down to the "catch" block - which is what you want.
Only if ErrorAction:Stop were used in the main script body and outside of a try...catch block would it cause the script to exit - which is still useful in a lot of situations where subsequent actions depend on former ones completing successfully.
The default value for ErrorAction within PowerShell is "continue".
When you encounter an error under "continue" mode, the error is shown in red text - as you're accustomed to seeing - but the flow of execution is not interrupted, meaning execution simply moves onto the next line.
Using my code block as the example, if Remove-Item (on line 6) generates an error, execution will continue to line 7 as if nothing failed. We will never get into the catch block to run line 11 - which is what we actually wanted to happen.
By changing the ErrorAction to Stop, the error is still reported above in red text, but additionally, PowerShell honours the exception thrown by Remove-Item and jumps to the catch block, meaning line 11 finally gets used properly.
This is why if you do not specify ErrorAction:Stop, the catch block remains unused.
Try it yourself with the following basic test. The statement in the catch block never gets executed despite the error being reported.
try { Get-Item -Path abc:; "Life is good. No errors to see here, right?"; } catch { "What are you smoking?" }
And now again, but this time with ErrorAction:Stop.
try { Get-Item -Path abc: -ErrorAction:Stop; "Life is good. No errors to see here, right?"; } catch { "What are you smoking?" }
Here's the output from both purely for reference. You can see the second example does what's expected.
Cheers,
Lain
robmo I think it's easier to use a try-catch approach, I changed the get-childitem to -filter *.jpg
$files = Get-ChildItem -Path C:\Temp -Filter *.jpg foreach ($file in $files) { try { Remove-Item -Path $file.fullname -Force | Out-Null if (-not (Test-Path $file.fullname)) { #Delete succeeded Write-Host "Delete succeeded: $file" } } catch { #Delete failed Write-Host "Delete failed: $file" } }