SOLVED

Validating PowerShell operations - Delete file

Brass Contributor

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

6 Replies

@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"
    }
}
best response confirmed by robmo (Brass Contributor)
Solution

@robmo 

 

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.

 

  1. Avoid storing results in variables unless it's unavoidable;
  2. 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.

@LainRobertson,

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!

Let us know :smiling_face_with_smiling_eyes:

@robmo 

 

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.

LainRobertson_0-1654731066757.png

 

Cheers,

Lain

I definitely need to experiment with this more but I do have a better understanding now.

Thank you for taking the time to respond!
1 best response

Accepted Solutions
best response confirmed by robmo (Brass Contributor)
Solution

@robmo 

 

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.

 

  1. Avoid storing results in variables unless it's unavoidable;
  2. 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.

View solution in original post