Introduction to Error Handling in PowerShell
Many PowerShell scripts are designed to run unattended, so it is vital to ensure that they can handle errors smoothly. Proper error handling helps prevent a wide variety of issues, from incomplete operations and system downtime to data loss. Error handling needs to account for both terminating errors, which stop script execution, and non-terminating errors, which allow the script to continue running but can affect the output.
One of the most useful mechanisms for error handling in PowerShell is the Try-Catch-Finally block. The Try block contains code that could throw an exception. If that happens, control is transferred to the Catch block, which handles the error by taking actions such as skipping a problematic file or invalid input, as well as logging the event. The Finally block always executes, performing cleanup tasks such as closing files, releasing resources or logging information. For example, creating detailed error messages for Try Catch in PowerShell can enable you to take appropriate actions to resolve issues.
This document provides a deep dive into Try-Catch-Finally blocks. Along the way, we will also cover some other error handling methods in PowerShell, including using the ErrorAction parameter to control how cmdlets respond to errors and using the $Error variable to store the history of errors.
What are Errors in PowerShell?
Errors are events that can affect the normal flow of execution of a script or can stop it entirely. Common causes of errors include incorrect syntax, missing files, insufficient permissions or unavailable resources. There are two primary types of errors, terminating errors and non-terminating errors.
Terminating Errors: Critical Errors that Stop the Script
Terminating errors immediately stop the script execution unless they are properly handled. These errors occur when PowerShell faces an issue it cannot recover from, such as a syntax error, calling a variable or file that does not exist, or lack of sxufficient permissions to perform the requested operation.
For example, the script below attempts to get the file users.txt:
Get-Content C:\users.txt | Get-ADUser -Properties name | Select-Object -Property SamAccountName,name
If this file does not exist at the specified location or the account running the script does not have permissions to access it, the script will generate a terminating error, halting script operation as shown here:
Non-Terminating Errors: Errors that Allow the Script to Continue
Non-terminating errors in PowerShell are errors that do not stop the script’s execution. These errors are commonly generated by cmdlets that attempt to process multiple objects, where one failure does not necessarily stop the entire script.
For example, suppose we run the script used in the previous example but now the file does exist and we have permissions to access it. However, we do not have connectivity to a domain controller. In this case, the first command will run and get the contents of the file. However, the next command will fail, generating non-terminating errors for each of the three objects in the file.
Now suppose domain controller connectivity is restored, but one object in the file has an invalid username. Running the script will generate the non-terminating error shown below for the object, but the other objects will be processed by the script.
Where Errors Are Stored: the $Error Variable
$Error is an automatic array that stores recent errors encountered during script execution, which makes it very useful in debugging and troubleshooting script. The most recent error is stored at index [0].
For example, when the following script attempts to fetch a non-existent file, it retrieves the resulting error from $Error and displays it with a friendly message:
# trying to get a file which doesn't exist, and an error is generated.
Get-Item "C:\file44.txt"
# Display a message along with the last error.
Write-Host "Oops! Something went wrong Here's the error: $($Error[0])"
Understanding Try, Catch and Finally Blocks
A good way to smoothly handle errors is to implement Try-Catch-Finally blocks.
Try Block: Where Potentially Error-Prone Code Resides
The Try block encloses code that might produce an error. For example, the following script is designed to stop the Notepad process. The Try block will stop that process if it is currently running, but if it is not running, execution will jump to the Catch block, preventing the script from crashing.
try {
# Attempt to stop a process that may not be running
Stop-Process -Name "Notepad" -ErrorAction Stop
# If no error occurs, this line will execute
Write-Host "Notepad process stopped successfully."
}
catch {
# Handle the error gracefully
Write-Host "Oops! Could not stop the process. Error: $($_.Exception.Message)"
}
Catch Block: Handling Errors When They Occur
The Catch block is used to handle exceptions that occur in the Try block. In the following PowerShell Try Catch example, the script prompts the user to enter a number, divides 10 by that number, and prints the result. However, if the input number is 0, the attempt at division will result in an error, since it’s not possible to divide by zero. In that case, the Catch block will execute and print an error message instead of a number.
try {
$number = [int](Read-Host "Enter a number")
Write-Output "Result: $(10 / $number)"
}
catch {
Write-Output "Error: Invalid input or division by zero!"
}
finally {
Write-Output "Program execution completed."
}
Finally Block: Code that Always Runs, Whether or Not an Error Occurs
The Finally block will always execute regardless of whether an exception was thrown.
For instance, the following script defines an array with just three elements but attempts to print the fifth element; this exception is handled in the Catch block, which prints an error. However, the Finally block is also executed, so an additional message is printed.
try {
# Define an array
$numbers = @(1, 2, 3)
# Access an invalid index using a .NET method that throws an exception
$value = $numbers.GetValue(5)
Write-Host "Value: $value"
}
catch [System.IndexOutOfRangeException] {
# Handle the specific exception
Write-Host "Error: Index out of range exception caught."
}
finally {
# This block always executes
Write-Host "Execution completed."
}
Advanced Example: Using Multiple Catch Blocks for Different Types of Exceptions
A script can include multiple Catch blocks to handle different types of exceptions, allowing for more granular error handling. Indeed, the .Net framework provides a rich set of PowerShell Try Catch exception message types, including the following:
- System.DivideByZeroException — Occurs when attempting to divide any number by zero
- System.FormatException — Arises when attempting to convert a non-numeric input into a number
- System.ArgumentNullException — Occurs when a passed arguments is null but should never be null
- System.IO.IOException — Thrown when an I/O error occurs
For instance, the following script asks for the user to input two numbers, converts them to integer format, divides the first number by the second, and prints the result. The first Catch block handles the possibility that the user’s input cannot be converted into integer format, and the second Catch block handles the possibility of attempting to divide by zero.
try {
# Prompt user for first input
$num1 = Read-Host "Enter the first number"
$num1 = [int]$num1 # Convert to integer (may cause FormatException)
# Prompt user for second input
$num2 = Read-Host "Enter the second number"
$num2 = [int]$num2 # Convert to integer (may cause FormatException)
# Perform division (may cause DivideByZeroException)
$result = $num1 / $num2
# Print result
Write-Host "Result: $result"
}
catch [System.FormatException] {
# Handling invalid input error
Write-Host "Error: Please enter only numeric values!"
}
catch [System.DivideByZeroException] {
# Handling division by zero error
Write-Host "Error: Cannot divide by zero!"
}
catch {
# Handling unexpected errors
Write-Host "An unexpected error occurred: $_"
}
finally {
# Code that always runs
Write-Host "Execution completed."
}
Advanced Error Handling Techniques
To create more robust and maintainable scripts, consider using the following error handling techniques in combination with PowerShell Try and Catch blocks:
- Use $ErrorActionPreference = “Stop” to ensure that all errors are treated as terminating, which makes them easier to catch.
- Use -ErrorAction Stop on individual commands to enable more granular control without affecting the entire script.
- Use $_.Exception.Message and $PSItem to access error details.
Catching Specific Exceptions
The following script is intended to convert input from user into a user into an integer and then run a command. The first Catch block handles the possibility of invalid user input, and the second handles attempts to execute an invalid command. If either exception occurs, the script prints a custom message.
try {
# Attempt to convert user input to an integer (may throw FormatException)
$number = Read-Host "Enter a number"
$number = [int]$number
# Try running a non-existent command (may throw CommandNotFoundException)
NonExistent-Command
}
catch [System.FormatException] {
Write-Host "Error: Invalid input. Please enter a valid number."
}
catch [System.Management.Automation.CommandNotFoundException] {
Write-Host "Error: Command not found. Please check the command name."
}
catch {
Write-Host "An unexpected error occurred: $_"
}
finally {
Write-Host "Execution completed."
}
Using $_.Exception.Message and $PSItem to Access Error Details
To access error details within a Catch block, you can use the following variables:
- $_.Exception.Message provides a clear, user-friendly Try Catch PowerShell error message, with only the relevant part of the error.
- $PSItem holds the full error object, including the error type, source and stack trace.
The following script illustrates the difference in the output provided by these variables:
try {
# Attempt to open a non-existent file
Get-Content "C:\File1.txt" -ErrorAction Stop
}
catch {
# Using $_.Exception.Message to get detailed error information
Write-Host "Error occurred: $($_.Exception.Message)"
# Using $PSItem (same as $_) to display full error details
Write-Host "Full Error Details: $PSItem"
}
finally {
Write-Host "Execution completed."
}
Using Trap Statements
A Trap statement defines code that is executed when a specific error occurs, regardless of where in the script the error occurs. As a result, it provides a mechanism for handling errors at a higher (global) level than individual PowerShell Try-Catch blocks.
The following script uses Try-Catch in PowerShell to handle the possibility of an attempt to divide by zero. The Trap statement will catch any other error that might happen, which in this case is using the Get-Item cmdlet with a file that doesn’t exist.
# Global error handler using trap
trap {
Write-Host "Global Error Handler (trap): $_"
continue # Allows the script to continue execution
}
function Test-TryCatch {
try {
Write-Host "Inside Try Block"
1 / 0 # This will cause a division by zero error
Write-Host "This line will not execute due to the error above."
} catch {
Write-Host "Caught in Try-Catch: $_"
}
}
Write-Host "Before function call"
Test-TryCatch
Write-Host "After function call"
# Triggering another error outside try-catch to see if trap works
Write-Host "Triggering an error outside Try-Catch"
Get-Item "C:\NonExistentFile.txt" -ErrorAction Stop # Force a terminating error
Write-Host "Script completed"
Handling Non-Terminating Errors in PowerShell
Catch blocks are designed to handle terminating errors; since non-terminating errors don’t stop script execution, they don’t trigger Catch block. However, we can convert non-terminating errors into terminating errors using the -ErrorAction Stop parameter. This parameter forces the script to treat non-terminating error as terminating errors, which will allow Catch blocks to handle them appropriately.
Suppose we run the following script but the folder it tries to access does not exist. The Try block uses -ErrorAction Stop to convert this non-terminating error into terminating error so it will be handled by the Catch block.
$directoryPath = "C:\NonExistentFolder"
try {
Write-Host "Checking if directory exists..."
Get-ChildItem -Path $directoryPath -ErrorAction Stop # Force a terminating error if the directory doesn't exist
Write-Host "Directory found."
} catch {
Write-Host "Error: The directory '$directoryPath' was not found."
}
Best Practices for Using Try-Catch in PowerShell
To maximize the clarity, efficiency and maintainability of your code, craft your error handling block carefully. In particular, PowerShell Try-Catch should be used for genuine exceptions only. By limit the code in Try block to only those operations that might throw and exception makes it easier to identify the source of exceptions and debug the script. In other cases, consider if-else or switch statements.
Other key best practices include the following:
- Make sure that the code inside the Try block follows the single responsibility principle, which means that each piece of code performs a specific task.
- Use separate Catch blocks to handle different types of exceptions.
- Always include a Finally block to clean up code and release resources.
The following script illustrates some of these best practices. It uses an if statement with test-path to check whether a file exists before attempting to access it. If the file doesn’t exist, the error is displayed using the else statement. The Try-Catch block is used only to read the content of the file; if the script encounters an error like lack of permissions, the Catch block will handle the exception and display an error message.
# Define the path to the file
$filePath = "D:\NonExistentFile.txt"
# Check if the file exists
if (Test-Path -Path $filePath) {
try {
# Try to read the file contents
$fileContents = Get-Content -Path $filePath -ErrorAction Stop
Write-Host "File contents: $fileContents"
}
catch {
# Handle any exceptions that occur while reading the file
Write-Host "Error: Unable to read the file. Exception Message: $_"
}
} else {
# If the file does not exist, display an error message
Write-Host "Error: The file does not exist."
}
PowerShell Try-Catch and Script Continuation
Using Continue Statements
When a script encounters an error within a Try-Catch block, the default behavior is for the script to handle the exception in the Catch block and stop executing. However, there are scenarios where we want to continue execution after handling the error, such as when processing a batch of files or testing connections to multiple servers.
To ensure that a single failure does not halt the entire iteration, use a Continue statement. For example, the following script tests the connection of multiple servers; the Continue statement ensures that execution will continue even if an error occurs on one server.
# List of server names (replace with actual server names or IPs)
$servers = @("lhehost9", "lhehost11", "lhehost10")
foreach ($server in $servers) {
try {
Write-Host "Attempting to connect to $server..."
# Simulating a remote connection (Replace with actual connection command)
Test-Connection -ComputerName $server -Count 2 -ErrorAction Stop
Write-Host "Successfully connected to $server.`n"
} catch {
Write-Host "Error: Failed to connect to $server. Skipping to next server...`n"
continue # Continue to the next server in the loop
}
}
Write-Host "Script execution completed."
Using the $ErrorActionPreference Variable
Another way to force script execution to continue after an error is to use the $ErrorActionPreference variable. While Try-Catch blocks enable localized error handling, $ErrorActionPreference is a global setting that controls PowerShell’s response to non-terminating errors.
We already saw that you can set $ErrorActionPreference to “Stop” in order to ensure that all errors are treated as terminating and thereby make them easier to catch. However, this variable has additional settings, including Continue (the default), Silentlycontinue, Inquire and Ignore. By choosing the setting for this variable, you can define how errors are managed across the script.
PowerShell Finally Block: Ensuring Resource Cleanup
As noted earlier, the Finally block executes after the Try and Catch blocks, regardless of whether an error occurred during script execution. It is often used for cleanup process such as closing files, terminating connections and releasing resources.
For instance, the following script opens a file and writes “hello world” into it. If an error occurs, the Catch block will print a message. In any case, the Finally block will make sure that file stream is closed to prevent any resource leakage.
try {
$filePath = "D:\Office\Backup\eventviewer_logs.txt"
$fileStream = [System.IO.StreamWriter]::new($filePath)
$fileStream.WriteLine("Hello, World!")
}
catch {
Write-Host "An error occurred: $_"
}
finally {
if ($fileStream) {
$fileStream.Close()
Write-Host "File stream closed successfully."
}
}
Handling Multiple Errors in PowerShell
Variables that Store and Count Errors
PowerShell provides two variables that are quite helpful in auditing and debugging scripts that encounter multiple exceptions:
- $Error keeps a list of the errors encountered during a session.
- $Error.count provides the total number of errors recorded in current session.
For accurate tracking, it may be necessary to clear the list of errors from time to time during a session. Using $Error.clear() will remove all stored errors.
Using Nested Try-Catch Blocks to Handle Multiple Errors
The following script includes two nested Try-Catch blocks to handle two different potential errors, attempting to divide by zero and attempting to access a non-existent file:
try {
Write-Host "Starting First try block..."
try {
Write-Host "Attempting to divide by zero..."
$result = 1 / 0 # This will cause a divide-by-zero exception
}
catch [System.DivideByZeroException] {
Write-Host "Inner catch: Division by zero error occurred"
throw "Re-throwing error to outer try block"
}
try {
Write-Host "Accessing a file that doesn't exist..."
Get-Content -Path "C:\File1.txt" -ErrorAction Stop
}
catch [System.IO.FileNotFoundException] {
Write-Host "Inner catch: File not found error occurred"
}
}
catch {
Write-Host "Outer catch: Handling an error from inner blocks - $($_.Exception.Message)"
}
finally {
Write-Host "Executing final block for cleanup..."
}
Error Messages and Debugging in PowerShell
Accessing Detailed Error Information
When errors occur during script execution, simply knowing that an error occurred is not enough; we need detailed information to diagnose and fix the problem. As noted earlier, the $Error variable keeps an array of error records, with the most recent error at $Error[0]. Specific error properties include Exception, CategoryInfo, InvocationInfo and ScriptStackTrace.
The ScriptStackTrace property is invaluable because it provides the detailed call stack, showing the sequence of functions call and script locations that led to the error. To access this detailed information for the most recent error, use $Error[0].ScriptStackTrace.
Providing Custom Error Messages
Displaying meaningful and user-friendly error messages enhances script usability and debugging. Custom messages can provide users with insight into what went wrong and sometimes help them correct the error, such as by fixing an incorrect file path, incorrect process name or insufficient permissions.
To display custom messages, we can use Write-host or write-error in a Catch block. Include $_.Exception.message to provide additional details.
For example, when an error (division by zero) occurs during execution of the following script, a detailed notification is both printed to the console and emailed to the administrator:
$SMTPServer = "lvkex.ca.lo"
$SMTPPort = 587
$From = "aadministrator@ca.lo"
$To = "administrator@ca.lo"
$Subject = "PowerShell Script Error Alert"
try {
Write-Host "Executing script..."
# Simulate an error (divide by zero)
$result = 1 / 0
}
catch {
# Capture actual error message details
$ErrorMessage = @"
An error occurred in the PowerShell script.
Message: $($_.Exception.Message)
Script: $($MyInvocation.ScriptName)
Line: $($_.InvocationInfo.ScriptLineNumber)
Date: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
"@
# Log error to console and send email
Write-Host "An error occurred. Sending email notification..."
Send-MailMessage -SmtpServer $SMTPServer -Port $SMTPPort -From $From -To $To -Subject $Subject -Body $ErrorMessage
}
}
Common Use Cases for PowerShell Try-Catch
Updating User Profiles in Bulk While Handling Errors in Data Entry
Administrators often use a PowerShell to automatically update user information in Active Directory based on a csv file. Since the input file might contain invalid data, it’s important to use Try-Catch to handle and log errors.
The following script imports a csv file containing a list of users and iterates through it to update their descriptions in AD. It prints the results on the console and records any errors in a log file.
# Define file paths
$CsvFile = "C:\Members (7).csv"
$LogFile = "C:\logs.txt"
# Import CSV
$Users = Import-Csv -Path $CsvFile
foreach ($User in $Users) {
try {
# Attempt to find the user in AD using CN or SamAccountName
$ADUser = Get-ADUser -LDAPFilter "(cn=$($User.cn))" -Properties SamAccountName, Description -ErrorAction SilentlyContinue
if ($ADUser -eq $null) {
# Log missing users
$ErrorMessage = "$(Get-Date): User '$($User.cn)' not found in AD."
Write-Host $ErrorMessage
Add-Content -Path $LogFile -Value $ErrorMessage
continue # Skip to the next user
}
# Define new description
$NewDescription = "Test update on $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
# Update AD user description
Set-ADUser -Identity $ADUser.SamAccountName -Description $NewDescription -ErrorAction Stop
# Log success
Write-Host "Updated: $($ADUser.SamAccountName) - New Description: $NewDescription"
}
catch {
# Capture and log errors
$ErrorMessage = "$(Get-Date): Error updating $($User.cn) ($($ADUser.SamAccountName)) - $($_.Exception.Message)"
Write-Host $ErrorMessage
Add-Content -Path $LogFile -Value $ErrorMessage
}
}
Handling Issues with Server or Network Connections
Try-Catch blocks are also useful for handling server and network connection issues. For example, the following script illustrates how to test the connection to a server, once with the correct name and once with incorrect name:
# Define server details
$Server = "ada1.adatum.local"
$LogFile = "C:\log.txt"
try {
# Check if the server responds to a ping
$PingTest = Test-NetConnection -ComputerName $Server
if (-not $PingTest.PingSucceeded) {
throw "Server $Server is not responding to ping."
}
# Check RDP connectivity (port 3389)
$RDPTest = Test-NetConnection -ComputerName $Server -Port 3389
if (-not $RDPTest.TcpTestSucceeded) {
throw "Server $Server is reachable but RDP port 3389 is not open."
}
# If both checks pass
Write-Host "Server $Server is reachable, and RDP is accessible."
}
catch {
# Capture and log the error
$ErrorMessage = "$(Get-Date): Error connecting to server $Server - $($_.Exception.Message)"
Write-Host $ErrorMessage
Add-Content -Path $LogFile -Value $ErrorMessage
}
Conclusion
Try-Catch-Finally blocks are an invaluable tool for handling errors in PowerShell scripts. By combining them with mechanisms such as the ErrorAction parameter and the $ErrorActionPreference and $Error variables, you can create robust scripts that run smoothly even when unexpected issues arise. As a result, you can automate tasks with confidence that you will not suffer problems like incomplete operation, system downtime or data loss.
FAQ
How do I use Try-Catch to handle both terminating and non-terminating errors?
Terminating errors halt the normal flow of script, so they trigger the Catch block automatically, but non-terminating errors do not. To force all errors to trigger the Catch block, set the ?ErrorAction parameter to Stop. For example, the following code attempts to get a process that doesn’t exist; the ?ErrorAction parameter converts the error into a terminating error to ensure that an error is printed on the console:
Try {
# Attempting to get a process that may not exist (non-terminating error)
Get-Process -Name "NonExistentProcess" -ErrorAction Stop
}
Catch {
Write-Host "An error occurred: $($_.Exception.Message)"
}
What is the difference between Trap and Try-Catch?
The main difference between Trap and Try-Catch is how they handle errors. Try-Catch allows handles specific exceptions within a specific code segment. Trap is defined on a global level and gets executed for the whole script in scope for any type of error; it allows the script to continue execution unless a Break statement is used.
In other words, Try-Catch provides more granularity in handling specific errors, whereas Trap is useful for catching errors where specific error handling is not required.
Can I use Try-Catch to handle specific types of PowerShell errors?
Yes. PowerShell errors are objects with a rich set of properties, and PowerShell provides a robust set of exceptions that reference those properties, which can be used to handle different types of errors appropriately. Common exceptions include System.IO.FileNotFoundException, System.Net.WebException, System.UnauthorizedAccessException and System.DivideByZeroException.
How do I log errors effectively in PowerShell scripts?
In a Catch block, you can log errors effectively using commands like Write-Error, Write-Warning and Write-Verbose. To include relevant information in log messages, you can use the variables $_.Exception.Message or $_.CategoryInfo. To export errors to a file, use the Out-file and Export-Csv commands.