Handling errors in PowerShell scripts

Security Briefs

Syndication

$get-help *error*
$

So this shocked the hell outta me.

Well, actually I lied, it's not quite that bad - this query actually returns one result, but it's for a function called Write-Error, which tells you nothing about how to do exception handling in PowerShell. And once you learn the two primary keywords for doing it (trap, throw), you won't get any help on those either. Turns out they just aren't documented in the current release, but the Error Handling and Debugging chapter of Jones & Hicks book on PowerShell is available on the web that will help you get started. FWIW, I also found a couple more chapters available online from Sapien Press. Thanks, guys!

I skimmed the chapter on the web and was able to get started. I'm recording a bit of what I discovered along the way so that others (myself included) will benefit from my research.

Here was the problem I wanted to solve. This particular problem isn't really that - it's nothing close to a showstopper for me, but I wanted to take the opportunity to learn how to do exception handling in PowerShell so that I would be ready when I really needed it. Anyway, I have this script that I use a lot to restart a process, which I'll call “foo“ for the sake of this post. Here's the two-liner script:

function restartFoo {
  get-process “foo” | stop-process
  c:\bin\foo.exe
}

It turns out that if there *are* no processes called “foo” running, get-process won't just send an empty list (too bad), rather it throws an InvalidOperationException. Here's how you'd normally trap such an exception in PowerShell:

function restartFoo {
  trap { # this installs an error handler for the function
    continue; # exiting a trap with "continue" says
              # to continue on the next line of the script
  }
  get-process foo | stop-process
  c:\bin\foo.exe
}

This was pretty much all I needed. It says that if there was any problem with the get-process line of the script, it should just continue on. So if there are no copies of "foo" running (or if stop-process fails for any reason) just skip that part. Pretty dumb error handling logic, but for what I needed in this non-critical script, it seemed as though it would suffice.

Well for some reason (and I've still not figured out why - please comment if you know) when get-process throws its exception, it's not trapped by my trap logic. It's definitely not entering the trap block; I added a "entering trap" line to see if it got in there, and it's not. Of course if I stick a throw "oops" statement in there, it enters the trap block just fine!

Soo, I thought perhaps I would try the more specific version of trap: trap [ExceptionType] {}. But I needed to figure out what type of exception was being thrown, and as Murphy would have it, the class of exception isn't displayed in the error output. So back to the chapter I went, and I found that most cmdlets have an “ea” argument that allows you to control how it behaves in the face of an exception. This turned to be an even easier solution to my problem, but I also learned some other stuff that I'll share here. I also learned about the $error variable, which is an array populated with errors as they occur:

get-process nosuchprocess -ea SilentlyContinue
$error

What I got from this was a whole laundry list of errors! Apparently the $error object at global scope collects all errors over time. I tried and was able to clear the list, which made things a little easier:

$error.clear

Now I could use the Exception property of the ErrorRecord class, which is the type you'll discover if you pipe $error into the get-member cmdlet:

$error.clear
get-process nosuchprocess -ea SilentlyContinue
$error[0].Exception | get-member

This was how I figured out that the type of exception I was looking for was InvalidOperationException (get-member tells you the type of the object at the beginning of it's output, which is convenient). To make a long story short, I tried trapping that particular error, but control still wasn't transferred to my trap handler. I'm still not sure what's up with that. But here was my final solution (UPDATE - I have a newer one, below):

function restartFoo {
  $procList = get-process foo -ea SilentlyContinue
  if (!$error) { $procList | stop-process }
  c:\bin\foo.exe
}

One thing I learned about $error is that there appears to be a new version of it created in each scope. That is, the $error in my restartFoo function doesn't keep getting bigger over time as the one at global scope does. That makes it easy to test in a function as I've done above (this is how you test for null in PowerShell, as far as I can tell - there is no "null" keyword).

UPDATE (23 Jan, 2007): Looks like the above paragraph is wrong. My function started leaving copies of the process around, and it turned out this was because $error is indeed a single global variable that collects errors over time. I've posted my new solution here.

Anyway, I hope you find something useful here, even if it's just the free chapter(s) of the book. Be sure to buy a copy if you like it!


Posted Jan 22 2007, 05:34 PM by keith-brown
Filed under:

Comments

Security Briefs wrote $error is actually global
on 01-23-2007 5:27 PM
Security Briefs wrote $error is actually global
on 01-25-2007 5:33 AM
mred wrote re: Handling errors in PowerShell scripts
on 02-03-2007 9:59 AM
Great post Keith. One thing however, if you use $error.clear you will simply print out the meta ... you need to use $error.clear()
Peter wrote re: Handling errors in PowerShell scripts
on 02-05-2007 12:45 PM
Today, sir, you are my hero. This is exactly what I was looking for, and I had the same problem with help *error*!
William wrote re: Handling errors in PowerShell scripts
on 02-06-2007 7:54 PM
I think you may just need to add -ea stop to that will throw the exception to your trap.

function Restart-NP
{
trap { continue }
get-process -ea stop notepad | stop-process
notepad.exe
}
scott zimmerman wrote re: Handling errors in PowerShell scripts
on 08-29-2007 7:18 AM
I think the trick to get the trap handler to be invoked the first way you tried is to add
-errorp silentcontinue
to the end of the get-process line.
Bob1980 wrote re: Handling errors in PowerShell scripts
on 07-24-2008 4:41 AM

a great post on trapping exceptions in powershell, here:

huddledmasses.org/trap-exception-in-powershell

Enjoy ~!

Bob