Have you ever run a script or a PowerShell cmdlet and get confronted with a screaming wall of text – in red – like the one shown below?
Not a reader? Watch this related video tutorial!Errors can become overwhelming and confusing. And most of all, errors are often hard to read, which makes determining what and where the script went wrong near impossible.
Luckily, you have some options in PowerShell to make this better through error handling. Using error handling, errors can be filtered and displayed in such a way that it’s easier to make sense of. And, understanding the error makes it easy to add more logic to error handling.
In this article, you will learn about errors in PowerShell and how they can be intercepted to perform error handling using the PowerShell Try Catch
blocks (and finally
blocks).
Understanding How Errors Work in PowerShell
Before diving into error handling, let’s first cover a few concepts around errors in PowerShell. Understanding errors can lead to better error handling strategies.
The $Error
Automatic Variable
In PowerShell, there are a lot of automatic variables, and one of them is the $Error
automatic variable. PowerShell uses the $Error
variable to store all errors that are encountered in the session. The $Error
variable is an array of errors sorted by most recent.
When you first open a PowerShell session, the $Error
variable is empty. You can check it so by calling the $Error
variable.
As you can see, the $Error
variable starts off empty. But, once an error is generated, the error will be added and stored into the $Error
variable.
In the example below, the error is generated by deliberately getting a service name that does not exist.
PS> Get-Service xyz
PS> $Error
PS> $Error.Count
As you can see from the output above, the generated error was added to the $Error
variable.
The $Error variable contains a collection of errors generated in the PowerShell session. Each error can be access by calling its array position. The latest error will always be at index 0.
For example, the latest error can be retrieved using
$Error[0]
.
The $Error
Object Properties
Since everything in PowerShell is an object, the $Error
variable is an object, and objects have properties. By piping the $Error
variable to the Get-Member
cmdlet, you should see the list of the properties available.
$Error | Get-Member
To determine the reason for the error, you can view the content of the InvocationInfo
property using the command below.
$Error[0].InvocationInfo
Now, you could do the same with the other properties and discover what other information you can find!
Terminating Errors
Terminating errors stop the execution flow when it is encountered by PowerShell vs non-terminating errors. There are several ways a terminating error can occur. One example is when you call a cmdlet with a parameter that does not exist.
As you’ll from the screenshot below, when the command Get-Process notepad
runs, the command is valid, and the details of the notepad process are displayed.
But, when a parameter that does not exist is used like Get-Process notepad -handle 251
, the cmdlet displays an error that the handle
parameter is not valid. Then, the cmdlet exits without showing the details of the notepad
process.
Non-Terminating Errors
Non-terminating errors are errors that do not stop the execution of the script or command. For example, check out the code below. This code gets the list of file names from the fileslist.txt file. Then, the script goes through each file name, read the contents of each file, and outputs it on the screen.
$file_list = Get-Content .\filelist.txt
foreach ($file in $file_list) {
Write-Output "Reading file $file"
Get-Content $file
}
The contents of the filelist.txt file are the file names shows in the list below.
File_1.log
File_2.log
File_3.log
File_4.log
File_5.log
File_6.log
File_7.log
File_8.log
File_9.log
File_10.log
But what if File_6.log didn’t actually exist? When you run the code, you’d expect an error will happen because the script cannot find the File_6.log. You’ll see a similar output shown below.
As you can see from the screenshot of the result above, the script was able to read the first five files in the list, but when it tried to read the file File_6.txt, an error is returned. The script then continued to read the rest of the files before exiting. It did not terminate.
The $ErrorActionPreference
Variable
So far, you’ve learned about the terminating and non-terminating errors and how they differ from each other. But, did you know that a non-terminating error can be forced to be treated as a terminating error?
PowerShell has a concept called preference variables. These variables are used to change how PowerShell behaves many different ways. One of these variables is called $ErrorActionPreference
.
The $ErrorActionPreference
variable is used to change the way PowerShell treats non-terminating errors. By default, the $ErrorActionPreference
value is set to Continue
. Changing the value of the $ErrorActionPreference
variable to STOP
forces PowerShell to treat all errors as terminating errors.
Use the code below to change the $ErrorActionPreference
value.
$ErrorActionPreference = "STOP"
To learn more about other valid $ErrorActionPreference variable values, visit PowerShell ErrorActionPreference.
Now, refer back to the example used in the Non-Terminating Errors section in this article. The script can modified to include the change in $ErrorActionPreference
like the code shown below:
# Set the $ErrorActionPreference value to STOP
$ErrorActionPreference = "STOP"
$file_list = Get-Content .\filelist.txt
foreach ($file in $file_list) {
Write-Output "Reading file $file"
Get-Content $file
}
Running the modified code above will behave differently than before when the $ErrorActionPreference
value is set to the default value of Continue
.
As you can see from the screenshot of the result above, the script was able to read the first five files in the list, but when it tried to read the file File_6.txt, an error is returned because the file was not found. Then, the script terminated, and the rest of the files are not read.
The
$ErrorActionPreference
value is only valid in the current PowerShell session. It resets to the default value once a new PowerShell session is started.
The ErrorAction
Common Parameter
If the $ErrorActionPreference
value is applied to the PowerShell session, the ErrorAction
parameter applies to any cmdlet that supports common parameters. The ErrorAction
parameter accepts the same values that the $ErrorActionPreference
variable does.
The ErrorAction
parameter value takes precedence over the $ErrorActionPreference
value.
Let’s go back and use the same code in the previous example. But, this time, the ErrorAction
parameter is added to the Get-Content
line.
# Set the $ErrorActionPreference value to default (CONTINUE)
$ErrorActionPreference = "CONTINUE"
$file_list = Get-Content .\filelist.txt
foreach ($file in $file_list) {
Write-Output "Reading file $file"
# Use the -ErrorAction common parameter
Get-Content $file -ErrorAction STOP
}
After running the modified code, you’ll see that even though the $ErrorActionPreference
is set to Continue
, the script still terminated once it encountered an error. The script terminated because the PowerShell ErrorAction
parameter value in Get-Content
is set to STOP
.
Using PowerShell Try Catch Blocks
At this point, you’ve learned about PowerShell errors and how the $ErrorActionPreference
variable and PowerShell ErrorAction
parameters work. Now, it’s time you learn about the good stuff – the PowerShell Try Catch Finally
blocks.
PowerShell try catch
blocks (and optional finally block
) are a way to cast a net around a piece of code and catch any errors that return.
The code below shows the syntax of the Try
statement.
try {
<statement list>
}
catch [[<error type>][',' <error type>]*]{
<statement list>
}
finally {
<statement list>
}
The Try
block contains the code that you want PowerShell to “try” and monitor for errors. If the code in the Try
block encounters an error, the error is added to the $Error
variable and then passed to the Catch
block.
The Catch
block contains the actions to execute when it receives an error from the Try
block. There can be multiple Catch
blocks in a Try
statement.
The Finally
block contains that code that will at the end of the Try
statement. This block runs whether or not an error was uncounted.
Catching Non-Specific Errors (Catch-All) with PowerShell ErrorAction
A simple Try
statement contains a Try
and a Catch
block. The Finally
block is optional.
For example, to catch a non-specific exception, the Catch
parameter should be empty. The example code below is using the same script that was used in the The $ErrorActionPreference Variable section but modified to use the Try Catch
blocks.
As you can see from the code below, this time, the foreach
statement is enclosed inside the Try
block. Then, a Catch
block contains the code to display the string An Error Occurred
if an error happened. The code in the Finally
block just clears the $Error
variable.
$file_list = Get-Content .\filelist.txt
try {
foreach ($file in $file_list) {
Write-Output "Reading file $file"
Get-Content $file -ErrorAction STOP
}
}
catch {
Write-Host "An Error Occured" -ForegroundColor RED
}
finally {
$Error.Clear()
}
The code above, after running in PowerShell, will give you this output shown below.
The output above shows that the script encountered an error, ran the code inside the Catch
block, and then terminated.
The error was handled, which was the point of error handling. However, the error displayed was too generic. To show a more descriptive error, you could access the Exception
property of the error that was passed by the Try
block.
The code below is modified, specifically the code inside the Catch
block, to display the exception message from the current error that was passed down the pipeline – $PSItem.Exception.Message
$file_list = Get-Content .\filelist.txt
try {
foreach ($file in $file_list) {
Write-Output "Reading file $file"
Get-Content $file -ErrorAction STOP
}
}
catch {
Write-Host $PSItem.Exception.Message -ForegroundColor RED
}
finally {
$Error.Clear()
}
This time, when the modified code above is run, the message displayed is a lot more descriptive.
Catching Specific Errors
There are times when a catch-all error handling is not the most appropriate approach. Perhaps, you want your script to perform an action that is dependent on the type of error that is encountered.
How do you determine the error type? By checking the TypeName
value of the Exception
property of the last error. For example, to find the error type from the previous example, use this command:
$Error[0].Exception | Get-Member
The result of the code above would look like the screenshot below. As you can see, the TypeName
value is displayed – System.Management.Automation.ItemNotFoundException
.
Now that you know the error type that you need to intercept, modify the code to catch it specifically. As you see from the modified code below, there are now two Catch
blocks. The first Catch
block intercepts a specific type of error (System.Management.Automation.ItemNotFoundException
). In contrast, the second Catch
block contains the generic, catch-all error message.
$file_list = Get-Content .\filelist.txt
try {
foreach ($file in $file_list) {
Write-Output "Reading file $file"
Get-Content $file -ErrorAction STOP
}
}
catch [System.Management.Automation.ItemNotFoundException]{
Write-Host "The file $file is not found." -ForegroundColor RED
}
catch {
Write-Host $PSItem.Exception.Message -ForegroundColor RED
}
finally {
$Error.Clear()
}
The screenshot below shows the output of the modified code above.
Conclusion
In this article, you’ve learned about errors in PowerShell, its properties, and how you can determine an error’s specific type. You’ve also learned the difference between how the $ErrorActionPreference
variable and the PowerShell ErrorAction
parameter affects how PowerShell treats non-terminating errors.
You have also learned how to use the PowerShell Try Catch Finally
blocks to perform error handling, whether for specific errors or a catch-all approach.
The examples that are shown in this article only demonstrates the basics of how the Try Catch Finally
blocks work. The knowledge that I hope you have gained in this article should give you the starting blocks to start applying error handling in your scripts.