Summary: Learn how to use error handling in your Windows PowerShell scripts.
Microsoft Scripting Guy, Ed Wilson, is here. Guest Blogger Week continues with Bhargav Shukla.
Bhargav Shukla is a senior premier field engineer—unified communications, with his primary focus on the Exchange Server platform. Bhargav has been in IT since the beginning of his career 14 years ago. Before joining Microsoft, he managed to work on almost any technology an IT consultant would be required to know, including Active Directory, Exchange, RSA Security, VMware, Citrix, and Cisco. He also holds industry certifications such as Microsoft Certified Master: Exchange 2010, VMware Certified Professional, Citrix CCEA, RSA: CSE, and Cisco CCNA/CCDA. He started working on scripting with small DOS batch scripts in his early career, and he learned to be a better scripter with new scripting languages. From batch files to VBScript and on to Windows PowerShell, he has written many scripts to address specific needs and reusable functions for repetitive code. When he is not working with customers, Bhargav leads the Philadelphia Area Exchange Server User Group, shares his knowledge on his blog and twitter, plays chess, and flies model airplanes.
When I was judging the scripts submitted for Scripting Games 2011, I noticed that most beginners’ scripts had a common theme, which resembles the scripts I created in my beginner days when I was learning scripting.
It isn’t an accident, nor it is something that all beginners should have known and overlooked. It’s more of a mindset. I remember writing scripts as a beginner, and most of my scripts stayed strict to the goals—I want to accomplish x. The scripts were coded to do only that, and they basically looked like a set of commands strung together with minimally required logic.
As I started writing more scripts, I realized that I was spending more time troubleshooting my scripts when they didn’t work as expected outside of my confined specs. I did not have control over where the scripts are run. I did not have control over environments. I made assumptions that weren’t always true.
As my scripts matured, they included error handling. The more scripts I wrote, the time I spent on actual code decreased, and the time I spent on error handling increased. I wouldn’t claim that all my scripts now run everywhere and as expected, but I can assure you that they inform the user of the unexpected, and sometimes how to handle it when the unexpected is encountered.
The purpose of this post is to familiarize you to the world of error handling in Windows PowerShell. It’s not aimed at advanced error handling or at covering all possible scenarios, but rather to give you the tools to get started. So let’s dive in.
What is error handling?
When the code (your script in this case) encounters something unexpected, it usually fails. The failure usually means the task that you intended to finish with a successful execution of your script, didn’t complete. Writing more code to address what to do when an error is encountered is error handling.
Most of the time, when you write a script and test it in different environments (such as running it on a different machine, using the noprofile switch, or having your friend test it on his laptop), it is very likely that you will see errors. Sometimes when you are writing scripts, you will anticipate that certain things will not work if certain conditions are not met. These examples help you write necessary code to handle anticipated and unexpected errors.
You can write the code so that errors can be handled during execution. Some examples are to ask the user for input; to create a missing folder; or simply to record the error, inform the user, and stop the execution.
How does PowerShell help you handle errors?
Windows PowerShell offers you few ways to learn about an error. The first one I would like to mention is the Error object. Because Windows PowerShell is so very much in love with objects, even an error that a code or a cmdlet encounters is stored in an object. (See how I refrained from using an exclamation point…because this shouldn’t be a surprise? J)
The next best thing (after Swiss cheese) in error handling is the error variable called $error. Every time an error is encountered, the error object is stored in $error variable.
When you start Windows PowerShell, this variable is initialized and contains nothing, which is expected if you started PowerShell and everything in your profile worked without generating an error.
Now, let’s try something that would result in an error:
You will be greeted with the expected error message.
Now let’s look at the error variable:
PS C:\Temp> $error
Get-Item : Cannot find path ‘C:\Temp\afilethatdoesntexist.txt’ because it does not exist.
At line:1 char:9
+ Get-Item <<<< afilethatdoesntexist.txt
+ CategoryInfo : ObjectNotFound: (C:\Temp…doesntexist.txt:String) [Get-Item], ItemNotFoundExcep
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand
So even if your unruly coworker didn’t want you to see the error on screen and cleared screen before calling you to help, you can now see the error that was generated. How cool is that?
The error variable is, however, a circular buffer. It keeps a number of errors in the buffer, and then at its defined capacity, it discards the oldest error objects as new error objects are added. By default, this number is set to 256. If you want to change it, you can simply define it by changing MaximumErrorcount variable. Let’s say you want to set the maximum error objects that are stored to 500—all you need to do is run the following:
$MaximumErrorCount = 500
Another thing I need to mention here is that the errors are stored top down in the array. What I mean by that is that the last error generated is first one in the list. If you want to only see the last error that was generated, you can simply run:
Now you may be thinking, “Great, I know what the error variable is and how to use it in my script, but how do I know that the error was not generated by my script? How do I ensure that the error wasn’t already there when script execution started?”
There are many ways you can approach this. An ugly way is to know the number of errors that are stored in variable before the script starts by using $error.count, and then compare it along the way to see if the number increases. This might work, but it will certainly fail you if the circular buffer is full. In that case, your $error.count will remain the same, even if new errors are generated.
Another way is to clear the error variable when you start. Just run $error.clear(). A downside of this method is that you lose all the errors that were generated before you clear the error variable.
A much better way to handle this is the common parameter called ErrorVariable. When you run any cmdlet, you have the ability to use ErrorVariable, and then store the error that the cmdlet generates in a user-defined variable.
Let’s try the same error-prone command that we tried before—this time with ErrorVariable.
Get-Item afilethatdoesntexist.txt –errorvariable myerrorvariable
Now let’s look at variable that we asked it to create for us.
PS C:\temp> $myerrorvariable
Get-Item : Cannot find path ‘C:\temp\afilethatdoesntexist.txt’ because it does not exist.
At line:1 char:9
+ Get-Item <<<< afilethatdoesntexist.txt -errorvariable myerrorvariable
+ CategoryInfo : ObjectNotFound: (C:\temp\afilethatdoesntexist.txt:String) [Get-Item], ItemNotFoundExcept
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand
Now within your scripts, you can refer to the variable that you created and handle errors appropriately. But we haven’t talked about handling errors just yet!
Before we do, I want to talk about a couple other error variables. These variables help you determine whether a command was successful. One such variable is $?. In the sequence of events we have been through so far, the last command resulted in an error. So if I run $?, here is what I will see:
PS C:\temp> $?
If you haven’t noticed already, $? is a Boolean value. All it tells you is if the last command was successful (True) or unsuccessful (False, as in our case).
Here’s a test to see if you were paying attention. Answer the question honestly, without trying it in your PowerShell window if you were following along. What will be the value of $? Did you answer it correctly?
The next variable is LastExitCode. If you were following along, try to see if the variable exists by typing $laste<tab>. You will notice that the variable name didn’t expand. It’s not an accident that the variable doesn’t exist. We did encounter errors along the way though, didn’t we? Should this variable exist?
The answer is in the definition of this variable. The variable only applies to external commands and scripts. All we ran previously are built-in Windows PowerShell cmdlets. That explains why the variable doesn’t exist yet.
Let’s try this then. Try to ping localhost. When you do, try to look at $LastExitCode as shown here.
PS C:\temp> $LASTEXITCODE
Now try to ping a non-existent computer. What should be the value of $lastexitcode?
Zero indicates that the last execution of the script or external command was successful. However, unlike $?, it’s not a binary. When the execution ends in error, the variable doesn’t always have to be 1. It could be any integer that the external command or script returned.
Also, remember that when external command or script is run, $? will not always tell you a true story. Let’s see why. Create a script that has nothing but one line—our favorite error generating command:
Now run the script and see the output. You will notice that the host shows you the error. You will also notice that $error contains the error object that was generated by the command in the script. But $? Is set to TRUE! Oh, and don’t try this in the order I mentioned because it will skew the results of $?.
So can you tell me why $? Is set to True? Because your script ran successfully as far as Windows PowerShell is concerned. Every step in script was executed—whether it resulted in an error or not. And that was successful in doing what the script told Windows PowerShell to do. It doesn’t necessarily mean that the commands within script didn’t generate any error. See how quickly it gets confusing? And we haven’t started to go deep yet!
And did you have a, “Wait…what?” moment when I said “…any integer that the external command or script returned”? Yes, your scripts can return an integer exitcode that you define. If you want to try, just modify the script that you created above and add this line.
Run the script, and look at the value of LastExitCode. Isn’t that cool? Making up your own exit code?
When I was talking to Ed about blog ideas, we discussed that I should write about error handling because I was very adamant about it in rating the scripts. I felt that at least errors that are expected must be handled by the submitted scripts, even if they are in Beginner events.
Well, I am already exhausted trying to write about error handling, and we haven’t even scratched the surface. I bet you are too, just trying to keep up with me.
Thank you, Bhargav, for an excellent introduction to error handling.
For additional information on Error Handling in Windows PowerShell scripts, refer to this series of Hey Scripting Guy! blogs.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at firstname.lastname@example.org, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy