It happens: you run a script or execute a command, and something doesn't work quite right. You get an ugly red error. Now what?
There are many ways to handle this. When you're sitting at the console, frequently it comes down to simply reading the error message and figuring out what you forgot or mistyped. These are often the easiest errors to deal with just for the fact that they're interactive by nature, so you get the opportunity to try other things until you get the results you're looking for before proceeding.
Things get a lot more complicated, however, when you're writing a script, especially one that is meant to run without someone sitting at a console to see the errors. At least if the script is being run manually, the person sitting at the console will be able to see that something did go wrong; when it's unattended, though, frequently you wind up losing valuable clues as to what went wrong, so you need to think about how your script could fail and account for that ahead of time with error handling.
What follows is a quick primer on the basics to get you started in error handling. I plan to write a much more detailed series on this topic in the future.
Stream 2: Error output
Without going into too much detail, it's important to understand that errors are not passed along standard output (stream 1, basically what goes through the pipeline). This is a good thing, but can be frustrating at first when you're trying to figure out how to capture an error (for example, sending it out to a file to log it) and find that the ugly red text doesn't seem to go anywhere. It's a good thing because if it sent errors out the primary stream, every following part of the pipeline would try to process the error and throw its own errors, and that would just be a huge mess.
You can get around this by using the alternate stream pipe to send errors wherever you want.
Get-Content Not-A-File.txt 2> erroroutput.txt
One thing to note here: all you'll get is the text that shows on the screen. You can't treat this like the pipeline (as in, you can't send it to another cmdlet to do additional processing on it). There's a lot more detail available and ways you can programmatically react to errors.
For more details on streams, see this excellent write up: Understanding Streams, Redirection, and Write-Host in PowerShell
The automatic variable $Error is one of the most basic parts of PowerShell's error handling system. One thing to remember about it: because it's an automatic variable, you cannon use it for something else (this caused me some headaches early on while I was getting the handle of this, as I was trying to track errors using $Error, not realizing what it was yet; I mention this so you won't make the same mistake I did).
Every time something is sent to stream 2 (the error stream), it is also stored as an exception object in the $Error variable. Note: these are actual objects in the sense that they have .NET types, and there are a lot of .NET exception types available. Going into detail on that is beyond the scope of this post, but there is a lot of detail on each exception, including important things like TargetObject and StackTrace. You can see these by generating an error and looking at everything that is in it. Also note: the $Error variable doesn't just contain the last error; it's actually an array of all of the errors that have happened in your current session (up to a configurable limit set by $MaximumErrorCount, 256 by default) with the most recent always being at the first, or 0, index.
Every cmdlet has a built in parameter called ErrorAction specifically to tell PowerShell what to do if that cmdlet puts anything into stream 2. If nothing is specified, it will use the default for your session, which is configured with the variable $ErrorActionPreference, or the default for that cmdlet or exception (some exceptions override the default behavior). By default, this action is Continue, which adds the error to the $Error variable and displays the text (in red (by default; this can be changed)) in the console alongside other output.
Here are the main ErrorAction values to be concerned about and what they do:
- Continue (default): Adds to $Error and displays in console, continues execution
- SilentlyContinue: Same as Continue without displaying in console
- Ignore: Does not add to $Error or display in console; acts as if the error never happened
- Stop: Same as Continue, but halts further execution; note: this is necessary for try/catch to work (more on that later)
It is strongly recommended that you do not use SilentlyContinue or Ignore to correct for actual errors, and managing this with the $ErrorActionPreference variable is bad practice. SilentlyContinue and Ignore are useful, however, when errors are both expected and don't need to be handled or logged for whatever reason.
Write-Error and throw
When writing a script or advanced cmdlet, you might want to create your own error messages. I won't go into details on this other than to let you know they're here, and one major difference between using Write-Error and throw: Write-Error, by default, respects $ErrorActionPreference, while throw always generates a halting error (same as -ErrorAction Stop).
In writing scripts and cmdlets, this is the most useful logical structure for handling errors that come along. If you're doing something that has a risk of failure (for example: reading from a file that may not be there), you probably want to have your scripts handle problems on their own if possible (for example: if the file isn't there, send an email letting someone know).
Sometimes these potential errors can be avoided altogether by checking if everything is in the correct state before proceeding (example: check to see if the file is there before trying to read from it), but this isn't always possible or easy to do and might add a lot of additional overhead to your script. My examples here don't need a Try/Catch/Finally block, but they're easy enough to understand.
Basically, once you've identified something that can go wrong and trow an error, you add that code into a Try section (with ErrorAction set to Stop to trigger the system to execute the Catch section), and you add the handling code in the Catch section.
A really important point to understand is that you have access to the error object that caused the Catch section to execute within the Catch. It is added to the $_ auto variable. Note that it's identical to what will be in $Error immediately after getting the error on the console (and it will also still be added to $Error), so manually triggering the error and playing with $Error is a great way to test what you'll have available to work with in your Catch section.
The Finally section is not as commonly used. It executes regardless of what happens in the Try and Catch sections, so even if an error is thrown, it will execute. As an example, this can be useful when connecting to a system where you need to close the connection when you're done, and you want to make sure it happens even if the work your script was supposed to complete caused an error to be thrown.
Note that a Try section must be followed by a Catch and/or Finally section, so you can do Try/Catch, Try/Catch/Finally, or Try/Finally.
Here's an example (contrived, but shows how it works):
These are the basic building blocks of PowerShell's excellent error handling system. If you are writing scripts or cmdlets, you should be using at least some of these. This primer should, hopefully, give you enough to get started.
- Get-Help about_Automatic_Variables
- Get-Help about_Throw
- Get-Help Write-Error
- Get-Help about_Try_Catch_Finally