Trapping Errors

A lot of people are confused and ask how to correctly trap errors. Let's see how that works.

Default Error Handling

Whenever an error occurs, a so called "exception" is raised. If noone bothers to "handle" that exception, it finally ends up being handled by PowerShell, and PowerShell does whatever you have specified in $ErrorActionPreference. The default setting is "Continue", so a red error message appears in your console, and PowerShell continues with the next statement.

Other possible settings are "SilentlyContinue" (no error message), "Inquire" (asking whether you want to continue or abort a script) or "Stop" (stopping execution). While the setting defined in $ErrorActionPreference is the default if no other preference is specified, almost all Cmdlets support the -EA parameter where you can specify an individual setting just for that call. -EA beats the default setting in $ErrorActionPreference. It is in fact an alias for the -ErrorAction parameter so you can use -ErrorAction instead of -EA as well.

Trapping Errors

To catch an error yourself, you use the Trap statement. It expects a script block that gets executed whenever an unhandled exception is raised. Let's take a look at that:

Trap { 'Something terrible happened!' }
1/$null

The first thing you will notice is that this will have no effect when entered interactively. Traps only work inside of scripts. The second thing you notice is that when you run this as script, you will receive both your error message and the red PowerShell error message.

. 'C:\Scripts\test.ps1'
Something terrible happened!
Attempted to divide by zero.
At C:\Scripts\test.ps1:2 Char:3
+ 1/ <<<< null

This is because your Trap did not really handle the exception. To handle an exception, you need to add the "Continue" statement to your trap:

trap { 'Something terrible happened!'; continue }
1/$null

Now, the trap works as expected. It does whatever you specified in the trap script block, and PowerShell does not get to see the exception anymore. You no longer get the red error message.

Meaningful Traps

Most often, you want your trap to respond to individual errors adequately so it would be nice to know what kind of error actually occured. This is why inside your trap script block, you can access the current error record using the special variable $_. So to output the original error message, your trap could look like this:

trap { 'Hey, that did not work: {0}' -f $_.Exception.Message; continue}
1/$null

Unfortunately, the error record in $_ is valid only inside the trap so you do not get code completion for it. With a little trick, however, you can examine the object model because $_ is identical to the last error record in your error record list. When you enter interactively the following command after catching an error, you can see the very same error record:

$error[0].Exception.Message
Attempted to divide by zero.

In PowerShellPlus, you get full intellisense-like code completion for the object. Soon, you will discover that each error has a category info and a type:

$error[0].CategoryInfo.Category
NotSpecified
$error[0].FullyQualifiedErrorId
RuntimeException
$error[0].Exception.GetType().FullName
System.Management.Automation.RuntimeException

Those will be important when you try and handle different error types (just a second). They do not always produce results. For a simple error such as 1/$null, for example, you will not get a meaningful FullyQualifiedErrorID.

Make sure Cmdlets Raise Exceptions!

What if your trap is never called? Take a look at the following piece of code! There is a trap and there is an error but still PowerShell handles the error! Why?

trap { 
'Error Category {0}, Error Type {1}, ID: {2}, Message: {3}' -f
$_.CategoryInfo.Category, $_.Exception.GetType().FullName,
$_.FullyQualifiedErrorID, $_.Exception.Message;
continue
}
dir c:\wrongfoldername

Funny enough, your trap is never called. Instead, PowerShell outputs its default error message. This is because Cmdlets handle their errors themselves, and no exception is raised. The error is "invisible" to your trap. To make it visible, you need to set $ErrorActionPreference to 'Stop'. Whenever a Cmdlet causes an error, this setting will generate a "Stop" exception which then can be caught by your trap. Either set $ErrorActionPreference to 'Stop' to apply to all Cmdlets in your script, or specify the -EA parameter individually:

trap { 
'Error Category {0}, Error Type {1}, ID: {2}, Message: {3}' -f
$_.CategoryInfo.Category, $_.Exception.GetType().FullName,
$_.FullyQualifiedErrorID, $_.Exception.Message;
continue
}
dir c:\wrongfoldername -EA 'Stop'

On a side note, exceptions caused by pure syntactical errors like 1/0 can never be caught by your traps because those errors are detected during parsing even before the script had a chance to run.

Specialized Traps

While it is perfectly ok for a trap to handle all exceptions and distinguish the error type internally by looking at the CategoryInfo and FullyQualifiedErrorID, sometimes it is easier to implement different traps for different errors. To do that, you need to know the type of an exception that you want to catch. Here is an example:

$oldsetting = $ErrorActionPreference
$ErrorActionPreference = 'Stop'
trap [System.Management.Automation.ActionPreferenceStopException] {
'The Cmdlet {0} caused this error: {1}' -f
$_.CategoryInfo.Activity, $_.Exception.Message;
continue
}
trap [System.Management.Automation.RuntimeException] {
'A runtime exception occured: {0}' -f
$_.Exception.Message;
continue
}
Dir o:\thisdoesnotexist
1/$null
$ErrorActionPreference = $oldsetting

So you can make a trap specific for a certain type of exception by adding the exception type in square brackets right after the trap statement.

Understanding Scope

Whenever an error occurs and your trap is called, once your trap code is done life continues. PowerShell continues with the next statement in the scope of your trap. If you only have one scope, PowerShell will therefore continue with the next statement following your error:

trap { 'An Error Occured!'; continue }
'Hello'
1/$null
'World'

Often, sets of commands should work as groups, and if something goes wrong, the remaining commands of that group should be omitted. That's easy once you group them by scope. You could add functions to do that but the easiest way is to use plain script blocks. Just don't forget to invoke the script block using "&" or ".":

trap { 'An Error Occured!'; continue }
& { 'Hello'
1/$null
'World'}
' This is outside the scope'

When you run this, once the error occurs all remaining commands in your script block are omitted, and the script continues with the next statement outside the script block.

Raising Exceptions

You can also raise exceptions which is a good way of writing flexible code because by raising exceptions you leave it to error handlers like Trap how to respond to the error. To raise an exception, use Throw and specify the kind of error you would like to generate. Then, leave it to others to pick up and handle your error. If noone handles it, PowerShell will eventually handle it and display a red error message:

Throw (New-Object System.Management.Automation.RuntimeException)
System Error.
At Line:1 Column:6
+ Throw <<<< (New-Object System.Management.Automation.RuntimeException)
Throw (New-Object System.IO.DirectoryNotFoundException)
Attempted to access a path that is not on the disk.
At Line:1 Column:6
+ Throw <<<< (New-Object System.IO.DirectoryNotFoundException)

Replacing Error Messages

Maybe all you want to do is replace the standard error message with a more meaningful error message and then let PowerShell handle the error. To do that, you create an ErrorDetails object with your replacement message and assign this to the ErrorDetails property of the current error record. The following code changes the error message to a different error message:

trap { 
$Error[0].ErrorDetails = `
New-Object System.Management.Automation.ErrorDetails `
'Something really weird has happened.'
}
1/$null

Note that your Trap did not handle the exception because it did not call "Continue". Instead, it just modified the existing error record and left it to PowerShell to output the error information. Of course, in real life you would first check error details and then create a meaningful replacement message.

More Detailed Error Messages

By default, PowerShell outputs only exception messages and the positional information that helps you find out where the error occured. To get more verbose error information, you can change a number of automatic variables:

# Adds the exception class name.
$ReportErrorShowExceptionClass = $true
# All inner exceptions will also be displayed. Inner exceptions are
# exceptions that were caught by outer exceptions.
$ReportErrorShowInnerException = $true
# Outputs the stack trace, telling you which internal .NET methods
# were called immediately before the error occured.
$ReportErrorShowStackTrace = $true
#If you'd like to see less information, you can change
$ReportErrorShowSource = $false

Custom Colors for Error Messages

By default, PowerShell displays error messages in red on black background. Depending on your other color settings, error messages might not be readable so you can change error message colors like this:

$Host.PrivateData.ErrorForegroundColor = 'Red'
$Host.PrivateData.ErrorBackgroundColor = 'White'

Have fun!

-Tobias


Posted Sep 29 2008, 02:13 PM by Tobias Weltner
Copyright 2012 PowerShell.com. All rights reserved.