Friday, April 9, 2010

PowerShell’s Try/Catch/Finally and –erroraction Stop

Update: From an anonymous commenter, evidently error handing when using "-erroraction Stop" now works as one would expect in PowerShell version 3!  However, if you're still using version 2 the information below should help you out.

When planning for exception handling, I’ve recently learned that forcing execution to stop by setting $erroractionpreference to ‘Stop’ or by using the –erroraction common parameter requires some special handling.

First let’s see what doesn’t work even though it seems like it should.  Handling a normal terminating error is fairly straightforward:

image

Here we encountered an exception, got the exception’s type by inspecting the first element in the typenames member of the first error record’s exception’s base object, and then used that type name to catch the exception.  This worked whether or not we specified the –ea or –erroraction parameter.  This works because a ParameterBindingException is a terminating error.  However, let’s see what happens when we try the same thing with an error that isn’t of terminating error family:

image

Hmm, bugger.  We tried the same pattern, but this time it just doesn’t work.  But…why?  Without getting too long winded about it, I tried looking back through the automatic $error variable but couldn’t find an answer.  So I tried a different tactic:

image

Aha!  When we use –erroraction stop, the terminating exception isn’t the error that was encountered but an exception of type System.Management.Automation.ActionPreferencesStopException!  But…wait, if that’s true, how do we know what type of error was encountered?  Almost inexplicably the automatic variable $_ is set to the error record of the error that was encountered.  If you know why, please share in the comments or send me an e-mail.  Anyway, we can use that to re-throw the error as a terminating exception that can be caught and handled:

image

Basically we just wrap our command that makes use of the –erroraction stop inside two try/catch blocks.  The inner block is used to trap the ActionPreferenceStopException generated by the erroraction parameter.  In the inner catch block we throw the exception for the error that was caught which is then caught by the outer catch block statements and can the be handled.  So, let’s finish with a generic pattern that one could turn into a code snippet or something:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
try
{
  try
  {
    # Command that uses -erroraction stop or $erroractionpreference = 'Stop'
  }
  # Here we catch the Exception generated by the ErrorAction "Stop"
  # Only necessary if there is any processing we want to do if the
  # exception is of type ActionPreferenceStopExecution,
  # otherwise this block can be deleted
  catch [System.Management.Automation.ActionPreferenceStopException]
  {
    # Error Handling specific to ActionPreferenceStopException goes here
    # Rethrow the "real" exception as a terminating error
    Throw $_.exception
  }
  # All errors are caught and rethrown to the outer try/catch block
  # as terminating errors to be handled.
  catch
  {
    Throw $_.exception
  }
} 
# Here we can resume exception type handling as usual
catch #[System.Management.Automation.ItemNotFoundException]
{
  "Got it!"
}

And with that, Happy Friday!