Post

How are you handling your errors (in PowerShell)?

How are you handling your errors (in PowerShell)?

When writing a PowerShell script, there is one thing that always needs to be considered: error handling. The approach to error handling doesn’t always need to be elaborate, but it must be deliberate — it’s an essential component of every script.

In this blog post I’ll walk you through the basic steps to get started with handling your errors.

Set your preferences

The most basic form of handling errors in a PowerShell script is to simply instruct PowerShell to stop the execution whenever an error occurs.

There are two primary ways to achieve this:

  1. Using the -ErrorAction "Stop" common parameter for every cmdlet call or
  2. Setting the $ErrorActionPreference = "Stop" preference variable in the beginning of your script.

I personally prefer the latter since that means I don’t have to set the ErrorAction for each cmdlet separately. If there are cmdlets whose errors I want to handle differently, I can simply specify the -ErrorAction when calling those specific cmdlets.

If one doesn’t explicitly specify the ErrorAction, then PowerShell defaults to "Continue". In this case there is a real chance that your script could do something unexpected when conditions change or there is for example a temporary problem with an API that you are using.

Let’s consider the following hypothetical script that is meant to upload each file in C:\MyDirectory using an imaginary REST API and then delete the file but is missing any form of error handling

1
2
3
4
5
Foreach ($File in (Get-ChildItem -Path "C:\MyDirectory" -File)) {
    $FileContents = Get-Content $File
    Invoke-RestMethod -Uri "https://api.someservice.fi/upload" -Method Post -Body $FileContents
    Remove-Item $File
}

If for any reason uploading a file (line number 3) would fail the file would still get deleted (line number 4), resulting in data loss. This could have been avoided by simply setting $ErrorActionPreference = "Stop" before the Foreach loop and the script’s execution would have stopped at the first error.

Catch your errors

While effective, there are many situations where simply erroring out isn’t good enough. There might for example be a need to perform specific actions to revert the effects of the script before the error occurred or to simply log the error message to a log file.

This is where try-catch-finally comes in. The basic syntax combined with $ErrorActionPreference = "Stop" is as follows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ErrorActionPreference = "Stop"
try {

  # Do something that might throw an error.
  $Response = Invoke-RestMethod -Uri "https://api.someservice.fi/foo" -Method Get

} catch {

  # The code specified here gets executed if an error has occurred.
  # The $_ aka $PSItem holds the error object which we 
  # can implicitly convert to a string by using $_ inside ""
  Write-MyLog -Message "Error: $_"

} finally {

  # The code specified here gets executed regardless of any errors,
  # even if the code in the catch block would throw an error.
  Invoke-Finalization
  Write-MyLog -Message "Script finished."

}

The code above is a good starting point for a solid PowerShell script with a controlled error handling.

One thing to note is that an error occurring inside catch will cause the execution to jump immediately to finally and respectively an error inside finally will cause the script to stop. Of course if you wanted, you could nest another try-catch inside catch or finally to control this. Nesting multiple try-catch structures is a valid option in some cases, but can also add unnecessary complexity to your script.

Employ Zero Trust

When handling PowerShell errors there is a catch (pun intended) you should be aware of: not all errors are terminating. When an error is non-terminating it cannot be caught using try-catch.

Normally using $ErrorActionPreference = "Stop" will make all errors terminating but there are some cases where $ErrorActionPreference (or other preference variables) is not honored.

For more information see: https://github.com/PowerShell/PowerShell/issues/4568

You should also keep in mind that some cmdlets or functions could, for example, return $null instead of throwing an error. It’s completely up to the developer of a cmdlet or a function to implement the internal error handling in them. That is why I would advice to always verify explicitly (kind of like Zero Trust for error handling) the results before continuing. You can do this by using the following pattern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ErrorActionPreference = "Stop"
try {
  # Run something that does not throw an error but returns $null instead
  $Results = Invoke-MyCustomCommand -Parameter "Value"
  
  # Verify explicitly
  If ($null -eq $Results) {
    # Use Throw to generate a terminating error on purpose
    Throw "Did not receive any results."
  }
} catch {
  # Handle errors here
  ...
}

Finally

In this blog I showed you a basic approach to error handling in PowerShell. It gives you a good starting point for building a reliable PowerShell script. For those that are interested to learn more, I would suggest this excellent article in MS Docs: Everything you wanted to know about exceptions.

This post is licensed under CC BY 4.0 by the author.