Trap [Exception] { “In PowerShell” }

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.

Exceptions

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.

Throw

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 ;-) .

Trap

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:

$ReportErrorShowExceptionClass
If true, the class name of exceptions will be output when they are displayed. Default is false.
$ReportErrorShowInnerException
If true, the full chain of inner exceptions will be output. Each exception message will be indented an additional level, and formatted by the rest of the options listed here. Default is false.
$ReportErrorShowSource
If true, the assembly name for the source of the exception will be displayed. Default is true.
$ReportErrorShowStackTrace
If true, the stack trace of the exception will be output. Default is false.

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):


Function Test-Trap1() {
   trap [Exception] {
      write-host
      write-error $("TRAPPED: " + $_.Exception.GetType().FullName);
      write-error $("TRAPPED: " + $_.Exception.Message);
      continue;
   }
   write-host "Hello " -nonewline;
   throw (new-object IO.DirectoryNotFoundException); write-host "World!";
   write-host "Hello World!";
}
   Hello
   TRAPPED: System.IO.DirectoryNotFoundException
   TRAPPED: Attempted to access a path that is not on the disk.
   World!
   Hello World!

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:


Function Test-Trap2() {
   trap [Exception] {
      write-host "Trapped $($_.Exception.Message) in the Outer Trap!"
      continue
   }
   
   if($true){ # a nested scope
      trap [Exception] {
         write-host "Trapped $($_.Exception.Message) in the Inner Trap!"
         break
      }
      throw "Some Fun";
      "We won't reach this!"
   }
   "Wasn't that fun?"
}
## Will output:
# Trapped Some Fun in the Inner Trap!
# Trapped Some Fun in the Outer Trap!
# Wasn't that fun?

Trap Scope

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…


Function Test-Trap3([switch]$CauseProblems) {
   $result = $true
   if($CauseProblems) {
      "Try to create a folder, but it's parent doesn't exist so throw:"
      throw (new-object IO.DirectoryNotFoundException);
   } else {
      "Created a folder, now fake a problem creating a file."
      throw "Couldn't create a file"
      # you'd see this if continue returned into this scope
      "Created the file with no problems"
   }

   "Returning the result..." # ought to be false, but won't be.
   return $result
   
   # an interesting feature of powershell is that it doesn't care the order of the traps...
   trap [IO.DirectoryNotFoundException] {
      write-host "Can't find that directory!";
      $result = $false
      break;
   }
   trap {
      write-host "`$Result started set to $result."
      write-host $("`tTRAPPED: " + $_.Exception.GetType().FullName);
      write-host $("`tTRAPPED: " + $_.Exception.Message);
      $result = $false
      write-host "`$Result is now set to $result, since you had a problem"
      continue;
   }
}

When you execute that with and without the -CauseProblems switch, you’ll see a few odd things develop.


PS > Test-Trap3
Created a folder, now fake a problem creating a file.
$Result started set to True.
        TRAPPED: System.Management.Automation.RuntimeException
        TRAPPED: Couldn't create a file
$Result is now set to False, since you had a problem
Returning the result...
True

PS > Test-Trap3 -Cause
Try to create a folder, but it's parent doesn't exist so throw:
Can't find that directory!
Attempted to access a path that is not on the disk.
At line:5 char:14
+         throw  <<<< (new-object IO.DirectoryNotFoundException);
 

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.

Trap Return

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).

Try-Catch-Finally

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:


Function Test-TryCatch([switch]$One) {
   &{#Try
      Write-Host "Create a DB connection"
      Write-Host "Run some db queries"
      # throw things for testing purposes
      if( $One ) { throw (new-object NullReferenceException) }
      return "Success!" # you can return if you don't have a "finally"
   }
   ### CATCH NullReference and IndexOutOfRange, etc
   trap [SystemException] {
      Write-Host "Data access failed because $_"
      Write-Output "Failure!"
      continue # So that the "Finally" stuff gets executed
   }
}

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:


Function Test-TryCatch([switch]$One,[switch]$Two,[switch]$Three) {
   $script:Exception = $null;
   # We have a finally clause and need to rethrow exceptions
   # So we nest the try and all the traps inside a scope...
   &{
      &{#TRY
         Write-Host "Create a DB connection"
         Write-Host "Run some db queries"
         # throw things for testing purposes
         if( $One   ) { throw (new-object NullReferenceException) }
         if( $Two   ) { throw (new-object IndexOutOfRangeException) }
         if( $Three ) { throw "a runtime exception" }
         # If you need a finally clause, Write-Output, don't return
         Write-Output "Success!"
      }
      ### CATCH NullReference and IndexOutOfRangeException
      trap [SystemException] {
         Write-Host "Data access failed because $_"
         Write-Output "Failure!" # Instead of return, when in a trap
         continue # So that the "Finally" stuff gets executed
      }
      ### CATCH ... the other thing
      trap [Management.Automation.RuntimeException] {
         Write-Host "We would write some error-logging code here to log $($_.Exception.Message)"
         Write-Output "Failure!" # Instead of return, when in a trap
         # If we need to rethrow, we have to do it after the "Finally"
         $script:Exception = $_; continue
      }
   } # end the extra scope before the finally
   #### FINALLY always gets executed
   Write-Host "Clean up and close the DB connection"  -foreground green
   #### Rethrow the exception if need be...
   if($script:Exception -ne $null) { throw ($script:Exception) }
   
   # If you had code after your try-catch-finally, it would be here...
}

[new] Postscript

Cash 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.

Similar Posts:

2 thoughts on “Trap [Exception] { “In PowerShell” }”

  1. You qualified the Try-Finally-Catch “as long as you’re able to handle all of your exceptions in a single trap”.

    Actually, you can nest the Try-Finally-Catch blocks effectively.

    There are a couple of side effects I’ve learned and I’ll blog it in the near future. In short, whenever you use a scriptblock, you effectively creates a scope for variables. As a result, varibales created or changed in a Try Section are unavailable in the Finaly or Catch scriptblocks. This is not a big deal but it might need to be anticipated. Also, this is not a specific effect from Try-Finally-Catch. It happens with all scriptblocks. This is usually managed by “dotting” the script. I just haven’t found a way to work it into the Try-Finally-Catch commandlet.

    Cash Foley

Comments are closed.