PowerShell Great Debate: Error Trapping

In the aftermath of The Scripting Games, it's clear we need to have several community discussions - thus, I present to you, The Great Debates. These will be a series of posts wherein I'll outline the basic situation, and you're encouraged to debate and discuss in the comments section.

The general gist is that, during the Games, we saw different people voting "up" and "down" for the exact same techniques. So... which one is right? Neither! But all approaches have pros and cons... so that's what we'll discuss and debate. In the end, I'll take the discussion into a community-owned (free) ebook on patterns and practices for PowerShell.

Today's Debate: Error Trapping

There are a few different approaches folks take to trapping an error (I'm not discussing capturing the error, just knowing that one occurred).

Hopefully the Trap construct is familiar to everyone; I've always believed it's awkward and outdated. The product team has said as much; it was just the best they could do in v1 given time constraints. Its use of scope makes it especially tricky sometimes.

Try...Catch...Finally seems to be what a lot of people prefer. It's procedural and structured, and it works against any terminating exception. You do have to remember to make errors into terminating exceptions (-EA Stop on a cmdlet, for example), but it's a very programmatic approach.

I see folks sometimes use $?:

Do-Something
If ($?) { 
 # deal with it
}

A "con" of this approach is that $? doesn't indicate an error. It indicates whether or not the previous command thinks it completed successfully. It's reliable with most cmdlets - but I've seen it fail for a lot of external utilities. Given that it isn't 100% reliable as an indicator, I tend to shy away from it. I'd rather learn one way that always works, and that's been Try/Catch for me.

Try/Catch also makes it easy to catch different exceptions differently. I don't always need to do so... but again, I'd rather learn one way to do things that always works and provides more flexibility. I don't want to use $? sometimes, and then use something else other times, because that's more to remember, teach, learn, etc.

Some folks will do an $error.clear(), clearing the error collection, and then run a command. They'll then check $error.count to see if it's nonzero. I don't like that as much because it looks messy to me, and again - it doesn't let me easily handle different exceptions as easily as Try/Catch.

Ok... your thoughts?

 

[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!

28 Comments

  1. Hey Don,

    I've thought about this quite a bit recently. Personally, I prefer try/catch for the exact reasons you described. I am of the opinion though that if you are going to suppress an error completely in a catch block that you should remove the generated error from $error as well (i.e. $error.RemoveAt(0)). The reason I feel this is the right thing to do is because say a user executes your function and it runs without any errors. Later on, they reference $error and see errors generated from your script that they were made unaware of. You intentionally suppressed those errors so you should also remove them from $error. The only exception to that rule would be if they provided '-ErrorAction SilentlyContinue'. In that case, obviously, all bets are off. Thoughts?

    Cheers,
    Matt (@mattifestation)

  2. I generally dislike $? because it forces you to test after every command. Many times you'll have a sequence of commands that can be grouped together in a script block and treated as a unit of code, where any error in any of the commands will render the output of the script block invalid. You want to exit the entire script block if anything goes wrong anywhere. Having that test after each command creates a lot of clutter, and makes reading the script and quickly understanding what it's doing more difficult (to me anyway).

    With Try/Catch you can put that script block into a Try. Sometimes I'll set $ErrorActionPreference to Stop inside the Try block. Some people consider this bad practice, but I don't see the problem with it if, within that block, each command is dependent on the last completing successfully and any error needs to stop processing of the rest of the block.

    If I need granular reporting of exactly what went wrong, I prefer to pick up $Error[0] in the Catch block and run that through a Switch to determine what kind of error to report.

    All just MHO, and subject to change if confronted with better arguments 🙂

  3. This all sounds about right to me. I avoid setting the $ErrorActionPreference, but as long as you set it back in a finally clause it isn't dangerous. Using $Error[0] is good, and you didn't mention clearing $Error, which I think is a bad practice.

    To summarize my thoughts:
    1. Don't use $? (as it clutters the script)
    2. Use try/catch with -ErrorAction Stop
    3. (optional) If you have a big try block, you could set $ErrorActionPreference, but make sure to set it back in the finally clause.
    4. Don't clear $Error, because someone might be depending on the contents that you started with.

  4. This is certainly an area I need to improve on. I typically will do something like:
    New-Mailbox "bob"
    if (Get-Mailbox "bob"){#Do something here}

    But I have been trying to do more Try/Catch.

  5. For the first time, I needed to be able to catch an error so that what I was doing would continue. That was this week.

    I was trying to download some urls, and if it failed (for whatever reason, server not found being one of them, server not responding (mine!) was another), in which case I set the result to $null, which was then checked in the regular code and the lack of results was not used.

    try {
    $newicon = $webclient.downloadstring($s[2])
    }
    catch {
    $newicon = $null
    }

    I really don't care what caused the error. I plan on fleshing out the catch a little bit better (it works sufficiently for my testing of what I get when I get it. 🙂 ) with some logging so that I can actually tell what urls aren't working, and fix them if possible later.

    I'm writing for myself, so I generally don't have a problem if a big black and red block pops up on my screen (I've probably got a logic error I need to fix). I do get disappointed when it does. :/

    I don't think that
    try { somebigblockofcodelikeafunction $with $parameters }
    catch { #maybe I'll get an idea of what the problem is... }
    is really a valid use of try and catch.

    try and catch should be used used on a much more atomic level. Like in the more likely place somebigblockofcodelikeafunction can produce an error, not trying to catch any errors that the block may have and then dealing with it in a cogent matter. And yeah, sometimes you can't do that.

    I also code sanity checks on a regular basis. Sometimes too well, I discovered I was using the break statement when I should have been using the continue statement, which meant that the conditions I was testing for never really ever hit, and if they did, I would certainly have gotten invalid output. That never happened.

  6. I very rarely find it necessary to worry about changing it back. The Try block is going to run in it's own scope, so whatever preference variables you set there are only effective within that script block. The ErrorActionPreference is going to revert back to whatever it was set to in the parent scope as soon as it exits the Try block.

    • Actually, that's not true. Neither the Try block nor the Catch block have their own scope; constructs in PowerShell are not scoped elements. In the following, if the Try block had its own scope, then $check should be 1 after exiting.

      $check = 1
      try { $check = 2; gwmi win32_bios -ea stop -comp badname }
      catch { #something }
      $check

      After running this, however, $check is 2, because the change made in the Try block affects the overall scope, not a private scope.

  7. I stand corrected. Mike (and Don) are correct. If you do use $ErrorActionPreference to force terminating errors you should re-set it.

    Any thoughts on use $PSDefaultParameterValues to accomplish the same effect by setting the default ErrorAction parameter of specific cmdlets to 'Stop'?

    Since this is a hash table rather than a value type parameter, you need to create a new hash table in the script scope first, or clone the parent scope hash table if you want to retain any existing settings.

    • I'd never thought of setting a default that way... I *think* I prefer to explicitly use -EA, so that I can always see what's happening instead of having to remember a default I set... but I'm tentative on that opinion. If I was using Trap, I could see setting a default since Traps are more global; with Try/Catch, it seems I'd only want -EA inside a Try.

  8. Removing it from $Error doesn't sound like a good idea. It's one thing to suppress reporting of an error, but removing it from $Error would only serve to frustrate any attempt to debug a script if those errors were having some unintended consequence you handn't considered.

  9. Im interested in the post try/catch code. Your script must then handle whether or not an error occurred. If the error is so bad that you cannot continue then its easy to terminate in the catch block. But if you must continue , how do people go about it?
    Do you simply use a flag like:

    try {
    $errorflag = $false
    some possible error causing statement -ea stop
    }
    catch {
    $errorflag = $true
    ...
    }

    if ($errorflag) {
    ...
    } etc

  10. 1) Unfortunately the whole article and discussion overlooked the -ErrorVariable common paramater which is IMHO great for capturing cmdlet specific (non-terminating) errors. It simply combines the pros of the $? and $error.clean(): You can do if ($GetItemError) {} and still be sure you test against the correct set of nonterminating errors.
    The errors in $error are not deleted.

    2) The trap is great in one situation: You want to capture all unhandled errors in the script (or in scope) write them to log and rethrow the error, without adding huge try-catch to the whole script. ( Rethrowing is important so you have chance to handle the error somewhere else or it can be shown on screen. )

    On side note:

    Working with errors is too complicated in PowerShell especially for beginners. And if it is not done correctly it may cost a lot of effort in debugging. I often see people tend to use the try catch and then ending up using the try catch that does more harm than good. The problem is the try-catch is too general and they don't see or care about the implications.

    Example: try { Get-Process -nme 'idle' -ea stop} catch {Write-Error "Process not found."}
    This captures all errors, but the scripter meant just to error if the process was not found.

    The error returned is "Process not found." but in reality the cause is incorrectly spelled parameter name. Overlook this error, bury the function in a module, then call the module from a script and you will most likely spent more than few moments checking why the process is not available. Which is totally not the cause.

    Now to debug this code you need to look up the code that causes the error, and run it to learn what the actual error message is. (In this case it is easy, but if you need specific data to even run the code it becomes nightmare.)

    There is no universal solution to this, because it depends on what you are trying to do, and what are your plans with the captured error. But you can highly reduce the effort for debugging the script by:
    a) capturing ate least only the errors that would be non-terminating if you would not specify the ErrorAction Stop they would not be captured by the try catch (or better put errors that occurs after parameters are validated, while the cmdlet is running.) Giving you ability to handle the non-terminting error the way you need. And at the same time letting the terminating errors bubble up.

    try {} catch [System.Management.Automation.ActionPreferenceStopException] {}

    b) Incuding at least the message from the exception in the error you write/throw.

    • Working in an Exchange 2010 environment has made me leery of using ErrorVariable. In an implicit remoting scenario using ErrorVariable (or WarningVariable) with the Exchange cmdlets just doesn't work - you don't get anything back. I suspect it's due to the NoLanguage constraint of those remote sessions preventing it from being able to create the necessary variable there to store the error, but someone with more knowlege of the internals would have to verify that.

        • ErrorVariable was only being used to be able to determine which command in the Try block had the error. I generally don't have more than one of the same command in a Try block, and the name of the command that generated the error is in the error record. I believe I used a regex Switch in the Catch block, and used that to control which script block got ran according to what matched in $Error[0].

          Warnings are more of a problem. I wanted to capture information from the warning messages during a conversion, and ended up parsing them out of a transcript file. I've since learned that if you use a background job, you can read the job output buffers separately, which means you can get just the warnings, separated from the rest of the output, which is much easier to deal with.

        • No, I didn't, but it's easy enough to repro if you're using Exchange 2010. There's actually quite a bit that's different when you use implicit remoting vs using a full Exchange Management Console. You aren't actually running cmdlets - it's all proxy functions, and what's being returned aren't "live" Exchange objects, but deserialized versions. Serialization strips off the methods of objects, and can result in some properties missing or being reduced to string values due to loss of fidelity in the serialization process.

  11. One of the things that I like best about Try/Catch is that it makes it very easy to consolidate all the things that work and all the things that don't work into separate, but closely coupled, script blocks. But, I noticed during the scripting games that some folks only use it to set a flag and then run a bunch of If/Then blocks against that flag. Yuck! Extra variables that don't do anything useful and code spread all over the place. Here's an example of what they did:
    Try {
    Test-Connection $ComputerName -Quiet -Count 1 -ErrorAction Stop
    $Flag = $true
    }
    Catch {
    $Flag = $false
    }
    If ($Flag -eq $true) {Do-Something}
    Do-SomethingElse
    If ($Flag -eq $false) {Do-ErrorThing}

    We should promote a best practice recommendation for Try/Catch that encourages folks to put ALL of the code that runs in the success case in the Try block and ALL of the code that runs in the fail case in the Catch block. No extra variables and code all in one place, not spread around the script.

  12. I was never able to get enough of a consensus within the PowerShell team to write a prescriptive "How to Handle Errors" topic in about_Errors. I think almost everyone recommends Try/Catch, but it would be useful to have guidance about which commands to enclose in a Try/Catch block and precisely how to handle them.

  13. I don't like forcing all errors to become terminating errors with -ErrorAction Stop, personally. That dumps you out of your entire pipeline, even if much of it would have succeeded otherwise. The only circumstance where I'd find that acceptable is if I were constructing the script to pass only a single object to a cmdlet, and even then, it's just as fast to check your error variable.

    It would be nice if try/catch also informed you of the presence of non-terminating errors, but since it doesn't, I resort to using both ErrorVariable (for nonterminating errors) and try/catch (terminating errors from cmdlets, exceptions from .NET methods).

  14. If anything, some kind of standard. For example, a low impact cmdlet would cause non-terminating errors, while everything else would throw terminating. For example, I was completely annoyed that Get-ADGroup (from Server 2008 R2, at least) would throw a terminating error when the group didn't exist. By comparison, Get-Process doesn't throw a terminating error when a process doesn't exist...