PowerShell Great Debate: Capturing Errors

Hot on the heels of our last Great Debate, let's take the discussion to the next logical step and talk about how you like to capture errors when they occur.

The first technique is to use -ErrorVariable:

Try {
  Get-WmiObject Win32_BIOS -comp nothing -ea stop -ev mine
} Catch {
  # use $mine for error
}

Another is to use the $Error collection:

Try {
  Get-WmiObject Win32_BIOS -comp badname -ea stop
} Catch {
  # use $error[0]
}

And a third is to use $_:

Try {
  Get-WmiObject Win32_BIOS -comp snoopy -ea stop
} Catch {
  # use $_
}

Personally, I've always disliked the last approach, because people don't realize that in some situations $_ can get "hijacked." For example:

Get-Content names.txt |
ForEach-Object { 
  Try {
    Get-WmiObject Win32_BIOS -Comp $_ -EA Stop
  } Catch {
    # is $_ an error or a computer name?
  }
}

Now, I'm a big not-fan of using pipelines like this in a script, but that's another debate (it's on my list). The point is really that I can't universally, 100% rely on $_... and when someone uses $_ without realizing what's happening, they back themselves into a tricky corner that's difficult to diagnose. Since my big focus is on learning and teaching, I tend to want to teach techniques that are universal and always work the same way.

That said, $error[0] and the -ErrorVariable (-EV) technique return slightly different objects, meaning you have to work with them somewhat differently.

So what's your preference? Why? Which of these don't you like so much... and why?

[boilerplate greatdebate]

About the Author

Don Jones

Don Jones is a Windows PowerShell MVP, author of several Windows PowerShell books (and other IT books), Co-founder and President/CEO of PowerShell.org, PowerShell columnist for Microsoft TechNet Magazine, PowerShell educator, and designer/author of several Windows PowerShell courses (including Microsoft's). Power to the shell!

6 Comments

  1. I typically use try .. catch blocks with the $_ variable. I don't typically run into the limitation you provided in the example, because I tend to avoid using the pipeline. If I'm iterating over a list of computer names, I will use a standard foreach (...) looping construct, rather than the pipeline equivalent. I do this to help make my code more portable between languages (eg. C# PowerShell).

  2. I try to avoid using $_ everywhere because of its ambiguous nature, and don't use -ErrorVariable because it makes the line of code too long (I don't like using aliases), and so prefer to use $Error[0]. There are two benefits of this (in my opionion). One, the error variable name is consistent inside and outside the Try/Catch block; two, rarely, but sometimes I need to know the total number and type of errors that have occured during script execution and consistent use of $Error provides that.

  3. I also use the $_ variable, for pretty much exactly the same reasons Trevor stated. I'm aware of the potential problems, but if you're properly using the foreach keyword instead of the pipeline for iterating, you don't have the issue of hijacking $_. In my opinion, if you've written something that uses $_ for more than one thing, it had better be an interactive command you aren't going to use again/share, or else you need to re-write it.

  4. Ideally, I like to use multiple catch blocks for handling specific exception types and not rely on the contents of error variables. The other thing I saw a lot of in the Games was using Write-Warning. It seems everyone has a pathological fear of Write-Error, however Write-Error lets the end user control the ErrorAction and wrap your tool in their own Try-Catch block. A request for v4 would be to have error variable assignment in the Catch block similar to C# and that would resolve this debate.

  5. If we're strictly concerned with just capturing the errors, background jobs provide yet another mechanism for this. The output streams are held is separate buffers in a background job, and the error stream can be read (and captured) separately from the rest of the script output.

  6. I usually use the $_ in the catch block. I really like the foreach-object and using pipelines, because it enables me to stream the processing of objects. But I do agree the $_ is usually too versatile. So I often assign it to variable with more descriptive name. This enables me to keep the code readable and still use the best of the foreach-object ( $ComputerList | foreach { $Computer = $_ } ). Will the series continue with another Error related topic or should I elaborate on the topic here?:))