Monday, January 26, 2009

Error Handling with PowerShell

At last week’s UK PowerShell User Group meeting, we heard a great talk on Error handling. It got me thinking about the whole business of errors and error handling in PowerShell.

One of the great differences between casual and production scripts is the need to manage, control and handle errors. If I write a script to open a file, eg c:\foo\gd.txt, I know it exists. I created it, I regularly edit it, and I can see it in my folder. So why bother with error handling?

Tor casual scripting the answer is probably that you don’t need to worry. But when you start moving scripts into production, you can’t always be so sure. Stuff happens in production and your scripts need to deal with that.

There are three sets of error handling statements included in the PowerShell language:

  • Trap – allows you to trap any errors that occur in your code. You define the trap block before any risky code is executed. Trap is part of V1 and included in V2.
  • Try/Catch/Finally – these three statements allow you to try some dodgy code, catch any errors that occur in that dodgy code then do any clean up (whether or not an error occurred). Try/catch/finally is an addition to V2 and is not supported in V1.
  • Throw – this allows you to detect an error (for example in a subordinate script or function) and throw an error for a higher level script or function to catch or trap. This statement is in both V1 and V2.

So which do you use? It depends I suppose. For most scripts, you probably won’t need to use Throw. If you have some utility function, you might enable it to catch specific errors then throw an exception to callers of that code. Typically a throw is inside of your subordinate’s trap or catch block. You handle the error, perhaps do some clue generation (ie is the file you are trying to open, or is the drive just not accessible, etc) then throwing a more specific error. Try/catch (and finally if needed) should surround any production script logic that could fail.

To illustrate this point, take a look at the Get-StockQuote script (see http://pshscripts.blogspot.com/2008/12/get-stockquoteps1.html for full script). At the core of this script is the following code:

Process {  
$s = New-WebServiceProxy –uri http://www.webservicex.net/stockquote.asmx  
foreach ($symbol in $ticker) {  
$result = [xml]$s.GetQuote($symbol)  
$result.StockQuotes.Stock  
  }  

There are (at least!) two things that could go wrong with this fragment. First, the New-WebsServiceProxy cmdlet could fail – in fact, of late, this call has been producing errors (the site appears down). Second, the GetQuote web service could fail. There is a third source of error ($ticker is empty), but that would be dealt with via parameter declarations, which I omitted form this snippet!

As a casual script, just showing the basics of using this web service, I don’t need to worry about such things. But if this was a web service I depended on (and is outside my direct control!), I should either have one (or possibly) to trap blocks, or two try/catch blocks. I could expand this code as follows:

process {  
   try { 
      $s = new-webserviceproxy -uri http://www.webservicex.net/stockquote.asmx

   
  return
   } 
   catch { 
    "StockQuote web service not available"; $error[0] 
    return 
   }
 
 foreach ($symbol in $ticker) {  
    try  { 
      $result = [xml]$s.GetQuote($symbol)  
    } 
    catch { 
      return ("Can not get stock quote for {0}" -f $symbol) 
    } 
    # return results 
    $result.StockQuotes.Stock  
    }  

This code is a bit longer, but it enables the script to run and not abort. When errors do occur, this script fragement won’t produce the results you might have hoped for. In both catch blocks, I’ve not added much in the way of error handling. In the first catch block, some additional detection might include:

  • Checking the host IP configuration to see if the host is on-line and host networking is enabled.  There might be a local issue with a cable being unplugged.
  • Checking the local IP gateway. Networking might be OK, but the gateway might be down.
  • Determining if the site name (www.webservicex.net) was resolving via DNS to an IP address. Networking and gateways might be OK, but the DNS resolution of the site might be down.
  • Checking policy to see if the site’s IP address is being blocked. You might be able to ask the firewall(s) if they’re blocking traffic to the service.
  • Checking For SSL/TLS protected web services – can you create the secure tunnel? Is the certificate protecting the site duff?
  • Pinging the site to see if it’s actually up. DNS may resolve, but the site might be down.
  • Opening the home page for the site to see if there’s a working web site at the root. The site might be up but the web service gone.
  • Etc. I suspect there are more things you could check for.

Moving on in the code, with the second catch block, you could have checked the error details to produce a more relevant exception. Simply returning an instance of $Error puts the onus on the caller to handle the error. Also on both catch blocks, you could have thrown an exception for the caller to handle rather than just continuing.

As I think you can see, a pretty simple script that just creates a web proxy then calls it (effectively 2 lines of code) could end up with 10 times that many lines of error checking/handling code. This is amongst one aspect of writing production oriented PowerShell scripts.

Something related to this, of course, is the $Error variable. This is a subject worthy of an entire book chapter!

3 comments:

Anonymous said...

Until V2 is available, Adam Weigert has an implementation of try/catch/finally that works in V1.

Cheers ~
Keith

Kiquenet said...

any updates for PS v 2.0 ?

Thomas Lee said...

@kiquenet: the article describes what is in V2.0. Assuming you meant any updates for V3.0, the answer is no.