Hey, Scripting Guy! How Can I Use Try/Catch/Finally in Windows PowerShell?

Hey, Scripting Guy! How Can I Use Try/Catch/Finally in Windows PowerShell?

  • Comments 14
  • Likes

Bookmark and Share 

 

Hey, Scripting Guy! QuestionHey, Scripting Guy! One thing I miss in Windows PowerShell is the ability to use a Try/Catch/Finally block that I can use in C#. To me it is an elegant solution to error handling. You attempt something, catch any resulting errors, and clean up the mess you made. Are there any plans in Windows PowerShell 3.0 to introduce this kind of structured error handling? I know there is the Trap statement, but that is weak in comparison to Try/Catch/Finally.

-- JK

 

Hey, Scripting Guy! AnswerHello JK,

Microsoft Scripting Guy Ed Wilson here. Today I will be interviewed by the people from the Get-Scripting podcast. It will be pretty cool, and we will talk about Windows PowerShell best practices, as well as the 2010 Scripting Games that begin April 26, 2010. I have meetings both before and after the podcast, so this morning is the only time I have to check scripter@microsoft.com e-mail, make a few tweets, and post to Facebook. The rest of the day is pretty much a goner. I am drinking a rather indifferent black tea that I spiced up with a little bit of anise. It is actually pretty good this way, although I do not think it will become part of my regular repertoire.

JK, you do not need to wait for Windows PowerShell 3.0 to come out, because Windows PowerShell 2.0, which is installed on both Windows 7 and Windows Server 2008 R2, includes Try/Catch/Finally today.

When using a Try/Catch/Finally block, the command you wish to execute is placed in the Try block. If an error occurs when the command executes, the error will be written to the $Error variable, and script execution moves to the Catch block. The TestTryCatchFinally.ps1 script uses the Try command to attempt to create an object. A string states that the script is attempting to create a new object. The object to create is stored in the $ob1 variable. The New-Object cmdlet creates the object. After the object has been created and stored in the $a variable, the members of the object are displayed via the Get-Member cmdlet. This code illustrates the technique:

Try
 {
  "Attempting to create new object $ob1"
   $a = new-object $ob1
   "Members of the $ob1"
   "New object $ob1 created"
   $a | Get-Member
 }

Use the Catch block to capture errors that occurred during the Try block. You can specify the type of error to catch, as well as the action you wish to perform when the error occurs. In the TestTryCatchFinally.ps1 script, I monitor for System.Exception errors. The System.Exception .NET Framework class is the base class from which all other exceptions derive. This means a System.Exception is as generic as you can get; in essence, it will capture all predefined, common, system runtime exceptions. Upon catching the error, you can then specify what code you would like to execute. In this example, I display a single string that states that the script caught a system exception. The Catch block is shown here:

Catch
 {
  [system.exception]
  "caught a system exception"
 }

The Finally block of a Try/Catch/Finally sequence always runs, regardless if an error is generated or not. This means that any code cleanup you wish to do, such as explicitly releasing COM objects, should be placed in a Finally block. In the TestTryCatchFinally.ps1 script, the Finally block displays a string that states the script has ended. This is shown here:

Finally
 {
  "end of script"
 }

The complete TestTryCatchFinally.ps1 script is seen here.

TestTryCatchFinally.ps1

$ob1 = "kenobie"
"Begin test"

Try
 {
  "Attempting to create new object $ob1"
   $a = new-object $ob1
   "Members of the $ob1"
   "New object $ob1 created"
   $a | Get-Member
 }
Catch [system.exception]
 {
  "caught a system exception"
 }
Finally
 {
  "end of script"
 }

When the TestTryCatchFinally.ps1 script runs and the value of $ob1 is equal to “kenobie,” an error occurs because there is no object named “kenobie” that is creatable via the New-Object cmdlet. The following image displays the output from the script.

Image of output of TestTryCatchFinally.ps1

 

As seen in the previous image, the “Begin Test” string displays because it is outside the Try/Catch/Finally loop. Inside the Try block, the string “Attempting to create new object kenobie” is displayed because it comes before the New-Object command. This illustrates that the Try block is always attempted. The members of the “kenobie” object are not displayed, nor are the string “new object kenobie created.” This indicates that after the error is generated, the script moves to the next block.

In the Catch block, the System.Exception error is caught and displayed. The string “caught a system exception” is also displayed. Next, the script moves to the Finally block, and the “end of script” string is displayed.

If the script is run with the value of $ob1 equal to “system.object” (which is a valid object), the Try block completes successfully. As seen in the following image, the members of the object are displayed, and the string that states the object was successfully created is also displayed. The Catch block is not entered, but the “end of script” string from the Finally block is displayed.

Image of object created and members displayed

 

You can have multiple Catch blocks in a Try/Catch/Finally block. The thing to keep in mind is that when an exception occurs, Windows PowerShell leaves the Try block and searches for the Catch block. The first Catch block that matches the exception that was generated will be used. Therefore, you want to use the most specific exception first, and then move to the more generic exceptions. This is seen in TestTryMultipleCatchFinally.ps1.

TestTryMultipleCatchFinally.ps1

$ob1 = "foo"
"Begin test"
$ErrorActionPreference = "stop"
Try
 {
  Get-Content foo
  "Attempting to create new object $ob1"
   $a = new-object $ob1
   "Members of the $ob1"
   "New object $ob1 created"
   $a | Get-Member
 }
Catch [System.Management.Automation.PSArgumentException]
 {
  "invalid object"
 }
Catch [system.exception]
 {
  "caught a system exception"
 }
Finally
 {
  "end of script"
 }

The next image displays the output when running the TestTryMultipleCatchFinally.ps1 script. Two changes are made: The $ErrorActionPreference command is commented out as is the Get-Content foo command. Therefore, the error that will be generated is the one raised when attempting to create a nonexistent object. To find the specific error, I examined the $error variable after running the offending command. The error is found in the Exception field. The specific error that is raised is an instance of the System.Management.Automation.PSArgumentException error. This is seen here:

PS C:\> $error | fl * -f


PSMessageDetails      :
Exception             : System.Management.Automation.PSArgumentException: Cannot find type [foo]: make sure the assembly containing this type is loaded. at System.Management.Automation.MshCommandRuntime.ThrowTerminatingError(ErrorRecord errorRecord)
TargetObject          :
CategoryInfo          : InvalidType: (:) [New-Object], PSArgumentException
FullyQualifiedErrorId : TypeNotFound,Microsoft.PowerShell.Commands.NewObjectCommand
ErrorDetails          :
InvocationInfo        : System.Management.Automation.InvocationInfo
PipelineIterationInfo : {0, 0}

Image of output of TestTryMultipleCatchFinally.ps1

 

If a script has multiple errors and the error action preference is set to stop, the first error will cause the script to fail. By removing the comments from the $ErrorActionPreference line and the Get-Content line, the first error to be generated will be caught by the System.Exception Catch block and will therefore skip the argument exception. This is seen in the following image.

Image of first error caught by System.Exception Catch block

 

JK that is all there is to using Try/Catch/Finally. Join us tomorrow for Quick-Hits Friday.

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys

 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Hello,

    I've noticed that you can have a catch [system.exception]. With languages like C#, C++ and VB.net you are able to write out the actual system exception being thrown back to the user. Is it possible to throw the actual exception back to the user instead of "System Exception has occurred" ?

    I've been playing around and cant seem to figure it out.

  • @Kyle - just ran across your comment while searching around.  Inside the catch block, the $_ built-in variable refers to the ErrorRecord that was created to wrap the exception.  To get access to the actual exception, you can just refer to the Exception property off of that object.

    John Gilbert added a comment to the 'about_Try_Catch_Finally' page that says you can access the exception via the $_ built-in variable.

    technet.microsoft.com/.../dd315350.aspx

    Here's a couple of examples in case these help - hope the formatting comes out ok.

    try

    {

       throw 'default exception type'

    }

    catch [exception]

    {

       write-host '$_ is' $_

       write-host '$_.GetType().FullName is' $_.GetType().FullName

       write-host '$_.Exception is' $_.Exception

       write-host '$_.Exception.GetType().FullName is' $_.Exception.GetType().FullName

       write-host '$_.Exception.Message is' $_.Exception.Message

    }

    try

    {

       throw [invalidoperationexception] 'some message goes here'

    }

    catch [exception]

    {

       write-host '$_ is' $_

       write-host '$_.GetType().FullName is' $_.GetType().FullName

       write-host '$_.Exception is' $_.Exception

       write-host '$_.Exception.GetType().FullName is' $_.Exception.GetType().FullName

       write-host '$_.Exception.Message is' $_.Exception.Message

    }

  • @Kyle yes, it is possible. It is also possible to have multiple catch blocks for more specific types of errors.

    @James Manning Thanks for sharing this. It is good information.

  • This post should mention the difference between terminating vs non-terminating errors. Try/Catch does NOT catch non-terminating errors (ex. errors during moving a file).

    A good explanation can be found here: stackoverflow.com/.../powershell-ioexception-try-catch-isnt-working

  • forget my comment - you already wrote it in your text!

  • Hi,

       I've some question about multiple catch. How I can know what actual exception maybe occur in my code? I've find in MSDN, but don't find any exception about each cmdlet. what should I do?

  • Helped me to answer forum question :)

  • Thanks for the blog, would be nice to enhance the blog to show how to handle specific exception, the case I am dealing with is in winSCP automation server is rejecting host key finger print in that scenario I need to check for specific exception code and try using the alternate host key.

  • @Ed - needs a rewrite to modernize.