13 Oct
Brandon and I have been talking a lot about error handling in #PowerShell (on irc.freenode.net) the last few weeks, going back and forth (and dragging half the other channel participants into the discussion) with examples and counter examples and trying to make sense of how it’s supposed to work, and how it really does work. I’m sure he’s about to write about TRAP from the perspective of a sysadmin, but I thought I’d throw out this post as a complement to his — from the perspective of a programmer.
The biggest problem with error handling in PowerShell is an almost complete lack of documentation. Neither the trap statement or the throw statement even have about_ documents in the powershell help system, nor do they have pages on MSDN (that I can find). In fact, it’s so bad that I’ve been known on the IRC channel to accuse the PowerShell developers of deliberately not writing documentation so that they and the various MVPs could make some extra cash selling books about PowerShell.
Despite that, we’ve been able by trial and error to deduce how these things work, and hopefully this explanation will help some of you come to a better understanding of how you can use error handling in PowerShell.
The first thing you need to understand is the concept of Exceptions in .Net (since PowerShell is .Net, and everything you’ll be traping is going to be a .Net exception). This is a huge concept, which fortunately is exceptionally well documented and blogged about, so let me just give brief overview (if you don’t understand anything in this, please read some of the links).
In the .Net framework, when an error occurs inside a method which cannot be recovered from inside the method, it’s considered an exceptional circumstance. Since the problem can’t be handled by the method, the exception is raised up to the code which called the method by throwing an error object derived from System.Exception, which has an error message as well as a stack trace and information about the source of the error. In general, the caller can catch the exception and deal with it, or, if it can’t recover from the error either, it can rethrow it or simply not catch it, allowing it to pass on up to the next caller in the stack.
There are lots of Exception classes in the .Net Base Class Library, and many more in the PowerShell Management and Automation namespaces and in third party cmdlets which you may be dealing with, but the important thing to know is that they are all derived from System.Exception … and they usually have an error message and a source and/or stack trace.
In .Net, you can only throw exceptions, but in PowerShell, you can throw pretty much anything … the reason is that when you throw something that is not an Exception, it gets automatically wrapped up into a RuntimeException for you. So in a powershell script you can throw (ls *.txt) and it will simply convert the output of the command ls *.txt to a string and set it as the Message on a RuntimeException. Of course, you can also throw actual exceptions like” throw (new-object IO.DirectoryNotFoundException).
Generally speaking, it’s a good idea to throw an exception from the existing exception hiearchy rather than always relying on RuntimeException, because it will give you (or others who use your scripts) the ability to trap more specific errors. I could go into this in depth, but it’s been covered rather comprehensively in the .Net Guidelines, so I’ll spare you
.
The first thing you need to know about trapping is that you don’t always have to do it. The $ErrorActionPreference can be set to “Continue” when an error is thrown (or even “SilentlyContinue”) which is basically the equivalent of VB’s ON ERROR RESUME NEXT and doesn’t require any specific error handling code. If you’re sure of where errors might occur, you can set that to SilentlyContinue at the top of your script, and then after any line where you might have an exception, just check for an error like this: if(-not $?) { #handle $Error ... The $Error Object is fairly well documented, so I’ll just leave the rest to your experimentation.
Note that you can control how much information is shown about each exception with several built-in variables:
Having said all of that, PowerShell’s error handling is basically like VB’s: ON ERROR GOTO TRAP ... RESUME NEXT. You put a trap in (for each exception you want to handle) and when an exception is thrown, execution goes to the trap’s scriptblock. In your trap scriptblock you can try to correct the error and then either continue or break. Ok, it’s time for an example (followed by the output):
As you can see, the trap is trapping the lowest level [Exception] class,
NOTE: Trapping the [Exception] class is not generally a good idea, since it traps EVERY possible exception, including out-of-memory, and your error handling will likely not be able to account for all possibilities… However, this is fairly acceptable in script, and is certainly no more dangerous than using the $ErrorActionPreference = "SilentlyContinue" option
.
In this example, we used continue to caused execution to return to the scope the trap is in and execute the next command. It’s important to note that execution only returns to the scope of the trap, so if the exception was thrown inside a function, or even inside a if statement, and trapped outside of it … the continue will pick up at the end of the nested scope. Also (no matter what you read in books), execution continues at the next command, not the next line, so you can’t pull tricks by putting multiple semicolon-separated commands on the same line.
We could have used break instead, which would basically result in re-throwing the exception, so it can be trapped and handled by further traps. For example, try this code:
So far, so good, right? A more complicated example will show off some of the problems. Excuse me for introducing a parameter which allows you to trigger an extra exception, but it will allow you to see the multiple exception traps working, and I’ll use the implicit trap { ... } syntax for trapping the . NOTE: this code does not function as you might expect, so please try it out…
When you execute that with and without the -CauseProblems switch, you’ll see a few odd things develop.
First: regardless of the value of $ErrorActionPreference, when you call Test-Trap2 -Cause you will get an ugly red exception message when we hit line 20 (break) which will be identified as coming from the original source of the exception: line 5 (where we throw the DirectoryNotFoundException). That’s weird because it seems to ignore or override the “SilentlyContinue” setting, but since that’s probably what you wanted, I guess it’s ok.
Second: regardless (again) of the value of $ErrorActionPreference, when you do NOT set the -Cause flag, the function will return True every time. Even though in our error handler we were able to access the value of $Result and see that it was True … and even though we set it to $False, and printed it out so you could see it was set … the function still returns True, because the trap scope doesn’t modify the external scope unless you explicitly set the scope of a variable. NOTE: If you had used $script:result instead of $result (in every instance where $result appears in that script), you would get the output which the string/comments led you to expect.
Third: continue sends execution to “the next command” but only if the trap is in the same scope as the line which throws the exception. So you’ll never see the message from line 10: “Created the file with no problems” because although the error handler calls continue, it continues at the next command that’s within the same scope as the trap, so if the exception is in a nested scope (inside an if, while, switch, etc) a trap outside that scop can only continue at the end of the scope.
According to Chapter 11 of Don Jones’ and Jeffery Hicks’ book Windows PowerShell: TFM, there are actually three actions you can take inside a trap: break and continue which I mentioned above, and return [argument]. However, unlike break and continue, return is affected by the setting of $ErrorActionPreference, which makes it unpredictable and basically broken … if you want to output something from a trap error handler, you’re better off using Write-Output followed by a break or continue (which, incidentally, gives you more flexibility).
If you’re coming from a C# world, and just want to know how you can do what you’re used to, here’s a simple try-catch version:
Now, if you have a more complicated scenario and you need a finally clause, you can replace the “return” in the try clause with a Write-Output, and then any code at the end, after the trap(s), basically becomes your finally clause. An even more complicated scenario is if you need to rethrow an exception — lets say, you catch it just to log it, or you catch it and then decide you can’t correct for it. Normally you would just break at that point, but if you have clean up code you need to execute (a finally clause) then you can’t break, so instead you have to store the error record and then rethrow it later by hand. The catch is … you have to make sure that the traps don’t retrap it (you would end up in an infinite loop), so you have to create an extra layer of scope, which you can do using the & execute operator, like this:
PostscriptCash Foley points out an alternate try -catch -finally function by Adam Weigert which works great, and simplifies your code substantially by encapsulating the scope-nesting into a function — as long as you’re able to handle all of your exceptions in a single trap.