For my sins I have been (and to some extents still am) a C# developer. Compared with scripting there is a lot I like about writing applications in fully fledged high-level languages, especially when it comes to flow control.
Not all scripting languages are created equal and something I really like about PowerShell is its support for .NET style try-catch-finally statements. Error handling in PowerShell v1 was pretty rubbish – anyone remember trying to figure out the trap statement? – but it’s now very good and gives a lot of control over how errors are caught and handled.
Essentially there are 2 types of errors in PowerShell – terminating and non-terminating. As you might expect given the name, if a non-terminating error occurs then statement execution still continues. This makes sense for many sysadmin operations where you don’t want one failure to cause your scripts to stop (such as a server being offline). Terminating errors cause statement execution to stop and PowerShell looks for an error handler instead (such as a catch statement). If none is found then the script will exit.
But something weird I noticed is that surrounding a command with a try-catch statement can make PowerShell change a non-terminating error into a terminating one.
This occurs when an exception is thrown from a .NET class. To show what happens, let’s use a very basic dummy class with a single static method that throws an exception:
1 2 3 |
$Definition = "public class Foo { public static void Bar() " ` +" { throw new System.Exception(""Exception""); } }" Add-Type -TypeDefinition $Definition -Language CSharp |
Calling Bar() outside of try-catch will generate a non-terminating error. You can see that PowerShell continues execution as the “After” string is sent to the console. (It is important here that $ErrorActionPreference is Continue – if it is set to Stop then PS will treat all errors as terminating).
1 2 3 4 5 6 7 8 9 10 11 12 |
PS C:\Users\Administrator> $ErrorActionPreference Continue PS C:\Users\Administrator> "Before" ; [Foo]::Bar(); "After" Before Exception calling "Bar" with "0" argument(s): "BarException" At line:1 char:12 + "Before" ; [Foo]::Bar(); "After" + ~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : Exception After |
But put a try-catch around the call to Bar() and PowerShell changes the behaviour to a terminating error, and control is sent to the catch block instead of “After” being printed:
1 2 3 |
PS C:\Users\Administrator> try { "Before" ; [Foo]::Bar(); "After" } catch {"Exception" } Before Exception |
I don’t know why this happens but it’s important to know. If you add try-catch error handling to your PowerShell scripts – something I recommend as it makes your scripts more readable and maintainable – then you might end up changing their behaviour in ways you don’t expect. So be sure to test, test and test again!