Error Trapping and Handling in PowerShell
How to use the Trap and Try...Catch...Finally constructs
July 20, 2010
Sometimes when something goes wrong in Windows PowerShell, it isn't a bad thing. That is, there are certain conditions that you can anticipate and potentially deal with, such as a missing file or a computer that can't be contacted over the network. In response, you might want to prompt the user for an action to take or just log the error so that you can try again later. Windows PowerShell makes this possible through a scheme called error trapping and handling.
First, You Need an Error
To trap and handle an error, you actually need one to occur. Technically, in PowerShell terminology, you need an exception to occur. That can actually be a little tricky to do, believe it or not. For example, try running the following command. It will fail, but pay attention to what happens:
Get-WmiObject Win32_BIOS -comp 'localhost','not-here'
First, you should see the Win32_BIOS instance from your local computer. Then, you should see an error message (unless you actually have a computer named not-here on your network). Think you've seen an exception? Wrong. In PowerShell, just because you've seen an error message doesn't mean an exception was created. You can't trap or handle an error message. You can only trap and handle exceptions.
What you just saw was an example of a non-terminating exception. That is, an exception really did happen, but it wasn't so bad that the cmdlet needed to stop executing. So the cmdlet basically held the exception deep inside, suppressing its feelings of failure, and continued trying to do what you'd asked. You can't help the cmdlet if it isn't going to be more open with its feelings. In other words, you can't trap and handle non-terminating exceptions. Many of the problems a cmdlet can run into will typically generate a non-terminating exception. That's because cmdlets don't want folks to start calling them crybabies, so if something moderately bad happens, they just shut up and keep going.
This cmdlet behavior is controlled by a built-in PowerShell variable named $ErrorActionPreference. You can view its contents by simply typing the variable's name at the command line:
$ErrorActionPreference
By default, it's set to Continue, which is what cmdlets do when they encounter a non-terminating error—they keep going. The cmdlets also display error messages by default, but you can shut them off by setting $ErrorActionPreference to SilentlyContinue. Try it:
$ErrorActionPreference = "SilentlyContinue"Get-WmiObject Win32_BIOS -comp 'localhost','not-here'
This time, the failure occurred but not a word was said about it. Our cmdlet just bit its lip and kept on going, not so much as whimpering about the error. Now, this is where a lot of new PowerShell users go wrong, so I need you to picture me standing up on a table and screaming, "Do not set $ErrorActionPreference to SilentlyContinue just to make the error messages go away."
Error messages are, by and large, good things. They tell us what's broken. They're like the nerves in your fingertips that tell you the stove you're about to touch is very hot. People who have problems with those nerves often burn themselves. We usually want to see error messages. What we don't want to see are the error messages that we can anticipate and deal with on our own.
Just Cry Out Loud
When you anticipate a cmdlet running into a problem that you want to deal with, you need to tell that cmdlet to stop bottling up its emotions. You're not doing this for every cmdlet across the shell, but just for a specific cmdlet that you know you can handle. Since you don't want to make a global behavior change, you should leave $ErrorActionPreference set to Continue. Instead, you can modify the error action for just one cmdlet.
Every cmdlet in PowerShell supports a set of common parameters, one of which is -ErrorAction (which can be abbreviated -ea). It accepts the same values as $ErrorActionPreference, including stop, which tells the cmdlet to turn a non-terminating exception into a terminating exception—and terminating exceptions are ones you can trap and handle. For this example, you'd run the command
Get-WmiObject Win32_BIOS -comp 'localhost','not-here' -ea stop
Tricky Traps
The first way you can trap an error is to use a Trap construct. Listing 1 shows an example of a trap that's defined within a function. This code works in PowerShell 1.0 as well as PowerShell 2.0.
Function Do-Something { Trap { Write-Host 'Error in function' -fore white -back red Continue } Write-Host 'Trying' -fore white -back black gwmi Win32_BIOS -comp localhost,not-here -ea stop Write-Host 'Tried' -fore white -back black}Write-Host 'Starting' -fore white -back greenDo-SomethingWrite-Host 'Ending' -fore white -back green
Figure 1 shows the output from the code in Listing 1. As you can see, PowerShell first displayed the line Starting. It then executed the function, which displayed the line Trying.
Figure 1: Results from the Trap construct in Listing 1
Next, PowerShell ran Get-WmiObject, which can be abbreviated as gwmi. It first ran this cmdlet against localhost, and you can see the Win32_BIOS output. But it ran into a problem trying to contact not-here, so an exception occurred. The -ea stop parameter turned that into a terminating exception, so PowerShell looked for a Trap construct within the same scope. It found one inside the function and executed it. That's why Error in function displayed. The trap finished with the Continue statement, which kept the execution inside the same scope (i.e., inside the function), and Tried was displayed. Finally, the function exited and Ending was displayed.
Traps can be tricky because they are their own scope. Specifically, they're a child of whatever scope they live in. Consider the modified Trap construct in Listing 2.
Function Do-Something { Trap { Write-Host 'Error in function' -fore white -back red# BEGIN CALLOUT A $test = 'Two'# END CALLOUT A Continue } $test = 'One' Write-Host "Trying $test" -fore white -back black gwmi Win32_BIOS -comp localhost,not-here -ea stop Write-Host "Tried $test" -fore white -back black}Write-Host 'Starting' -fore white -back greenDo-SomethingWrite-Host 'Ending' -fore white -back green
Figure 2 shows the output from this version, and I want you to follow the value of the $test variable.
Figure 2: Results from the problematic Trap construct in Listing 2
The script set the $test variable to One, and that's displayed in the Trying One output. When the exception occurred, the trap set the $test variable to Two. However, when the trap exited, the output still displayed Tried One. What happened? As a child scope, a trap can access its parent's variables for reading only. So, when the trap tried to modify $test, it actually created a new local $test variable, which means that $test from the parent scope (i.e., the function) was never changed. This is a real bummer if you want your trap to modify something so that your script can continue. There are ways to remedy this. For example, you can replace the command in callout A in Listing 2 with the following command to change the variable's contents:
Set-Variable -name test -value 'Two' -scope 1
The -scope parameter treats scope 0 as the local scope, which is within the trap. The next scope up—the trap's parent—is scope 1. So by changing test in scope 1, you're modifying the variable that had been set to One. Note that when you use the Set-Variable cmdlet (as well as the other -Variable cmdlets), you don't use a dollar sign ($) when specifying a variable's name.
There's one more tricky bit about traps that I want to share. Take a look at the alternative Trap construct in Listing 3.
Trap { Write-Host 'Error in script' -fore white -back red Continue}Function Do-Something { Trap { Write-Host 'Error in function' -fore white -back red Break } Write-Host "Trying" -fore white -back black gwmi Win32_BIOS -comp localhost,not-here -ea stop Write-Host "Tried" -fore white -back black}Write-Host 'Starting' -fore white -back greenDo-SomethingWrite-Host 'Ending' -fore white -back green
What I've done is defined a trap within the script itself, prior to the function's definition. I've also modified the trap within the function to use a Break statement rather than a Continue statement. The Break statement forces the trap to exit the scope in which the error occurred (in this case, the function) and to pass the exception to the parent scope, which is the script. The shell will then look to see if a trap exists in that scope, and I have indeed defined one.
Figure 3 shows what the results look like.
Figure 3: Results from the alternative Trap construct in Listing 3
When the exception occurred in the function, its trap executed and "broke out of" the function. The exception was passed to the script, so its trap executed. Notice that Tried isn't displayed. That's because the function exited before that command could run. All you see is Ending, which is the last line in the script. Although the script's trap concludes with the Continue statement, all it does is keep the shell’s execution in the same scope (i.e., the script). The shell can't dive back into the function; it broke out of the function and is out for good unless you call the function afresh.
As this example shows, you can include more than one Trap construct in a script. This means you can set different traps for different types of errors. To get more details, run the command
Help about_Trap
if you're using PowerShell 2.0. Although PowerShell 1.0 supports the Trap construct, there isn't a Help file for it. So, if you're using PowerShell 1.0, you need to access the information at technet.microsoft.com/en-us/library/dd347548.aspx.
Try a Different Approach
Frankly, I find the Trap construct and its scope rules pretty confusing. Fortunately, PowerShell 2.0 offers an alternative: the Try...Catch...Finally construct, which Listing 4 shows.
Try { gwmi Win32_BIOS -comp localhost,not-here -ea stop} Catch { Write-Host 'Something bad happened' -fore white -back red} Finally { Write-Host 'Glad that is over'}
As you can see, you put the command that might fail in the Try block and the command that deals with the failure in the Catch block. You can even add a Finally block that will execute whether or not an error occurred.
Within the Catch block, you can do almost anything, including writing to log files, logging an event log entry, and sending email messages. It's even possible to create multiple Catch blocks, each of which deals with a certain kind of error. In PowerShell 2.0, you can run the command
Help about_Try_Catch_Finally
for more details.
What's Your Preference?
In PowerShell 1.0, you must use the Trap construct to trap and handle errors. In PowerShell 2.0, you have a choice between the Trap and Try...Catch...Finally constructs. I prefer using the latter. Not only is the Try...Catch...Finally construct easier to use, but it also keeps the error-handling logic closer to the location of the command that might fail. If you're using PowerShell 1.0 and you often need to catch and handle exceptions, you might consider upgrading to PowerShell 2.0 so that you can take advantage of this new error trapping and handling tool.
About the Author
You May Also Like