Error Handling

One of the key parts of any good PowerShell script is error handling. Even in the shortest script, being able to handle errors helps to ensure that an unexpected event will not go on to wreck the system you are working on.

The first requirement is to understand the types of errors that can occur during execution.

Terminating vs. Non-Terminating Errors:

The $error variable

When either type of error occurs during execution, it is logged to a global variable called $error. This variable is a collection of PowerShell Error Objects with the most recent error at index 0. On a freshly initialized PowerShell instance (no errors have occurred yet) the $error variable is ready and waiting as an empty collection:

PS C:\> $error.GetType() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True ArrayList System.Object PS C:\> $error.Count 0


Error Action Preference:

PowerShell halts execution on terminating errors, as mentioned before. For non-terminating errors we have the option to tell PowerShell how to handle these situations. This is where the error action preference comes in. Error Action Preference allows us to specify the desired behavior for a non-terminating error; it can be scoped at the command level or all the way up to the script level.

Available choices for error action preference:

Example: Set the preference at the script scope to Stop, place the following near the top of the script file:

  1. $ErrorActionPreference = "Stop"

Example: Set the preference at the cmdlet level to Inquire, add error action switch (or alias EA): 

  1. get-childitem "G:\FakeFolder" -ErrorAction "Inquire"
  2. get-childitem "G:\FakeFolder" -ea "Inquire"


Try/Catch/Finally Blocks:

The Try, Catch, and Finally statements allow us to control script flow when we encounter errors. The statements behave similar to the statements of the same name found in C# and other languages.

The behavior of try/catch is to catch terminating errors (exceptions). This means Non-terminating (operational) errors inside a try block will not trigger a Catch*. If you would like to catch all possible errors (terminating and non-terminating) – then simply set the error action preference to Stop. Remember that Stop error action forces a non-terminating error to behave like a terminating error, which means it can then be trapped in a catch block. Here is an example:

  1. try
  2. {
  3.     <#
  4.         Add dangerous code here that might produce exceptions.
  5.         Place as many code statements as needed here.
  6.         Non-terminating errors must have error action preference set to Stop to be caught.
  7.     #>
  8.  
  9.     write-host "Attempting dangerous operation"
  10.     $content = get-content -Path "C:\SomeFolder\This_File_Might_Not_Exist.txt" -ErrorAction Stop
  11. }
  12. catch
  13. {
  14.     <#
  15.         You can have multiple catch blocks (for different exceptions), or one single catch.
  16.         The last error record is available inside the catch block under the $_ variable.
  17.         Code inside this block is used for error handling. Examples include logging an error,
  18.         sending an email, writing to the event log, performing a recovery action, etc.
  19.         In this example I'm just printing the exception type and message to the screen.
  20.     #>
  21.  
  22.     write-host "Caught an exception:" -ForegroundColor Red
  23.     write-host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red
  24.     write-host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red
  25. }
  26. finally
  27. {
  28.     <#
  29.         Any statements in this block will always run even if errors are caught.
  30.         This statement block is optional. Normally used for cleanup and
  31.         releasing resources that must happen even under error situations.
  32.     #>
  33.  
  34.     write-host "Finally block reached"
  35. }

 
You might be wondering how I found the type name for the previous exception. The possible exceptions for cmdlets are not usually documented, so you may need to find them on your own. When an exception occurs you can look up the error in the $error collection, or while inside a catch block under the $_ variable. Call the GetType() method on the base exception to extract the FullName property. Like shown here:

  1. PS C:\> $error[0].Exception.GetType().FullName
  2. System.Management.Automation.ItemNotFoundException


Handling Errors from non-PowerShell processes:

What happens when your script needs to run an external process from PowerShell and you want to know if it succeeded? An example would be a cmdline tool such as robocopy.exe. It’s an external application that returns an exit code upon completion. But since it is an external process, its errors will not be caught in your try/catch blocks.

To trap this exit code utilize the $LastExitCode PowerShell variable.

When the launched process exits, PowerShell will write the exit code directly to $LastExitCode. In most cases an exit code of 0 means success, and 1 or greater indicates a failure. Check the external tool's documentation to verify of course.

Here it is seen in action:

  1. PS C:\> robocopy.exe "C:\DirectoryDoesNotExist" "C:\NewDestination" "*.*" /R:0
  2.  
  3. -------------------------------------------------------------------------------
  4.    ROBOCOPY     ::     Robust File Copy for Windows
  5.  
  6. -------------------------------------------------------------------------------
  7.  
  8.   Started : Sun Jun 09 18:42:09 2013
  9.  
  10.    Source : C:\DirectoryDoesNotExist\\par      Dest : C:\NewDestination\\par
  11.     Files : *.*
  12.  
  13.   Options : *.* /COPY:DAT /R:0 /W:30
  14.  
  15. ------------------------------------------------------------------------------
  16.  
  17. 2013/06/09 18:42:09 ERROR 2 (0x00000002) Accessing Source Directory C:\Directory
  18. DoesNotExist\\par The system cannot find the file specified.
  19. PS C:\> $lastexitcode
  20. 16