Error Handling (Try-Catch-Finally)
Note
Learn how to handle errors gracefully using try/catch/finally blocks and error handling best practices.
Overview
Error handling lets your scripts respond to problems instead of crashing. Without error handling, a single failure can stop your entire script. With proper error handling, you can catch errors, log them, retry operations, or gracefully exit while cleaning up resources.
Basic Syntax
try {
# Code that might fail
Get-Item "C:\DoesNotExist.txt"
}
catch {
# Code that runs if an error occurs
Write-Output "Error: $($_.Exception.Message)"
}
finally {
# Code that ALWAYS runs (error or not)
# Good for cleanup: closing files, connections, etc.
Write-Output "Cleanup complete"
}
Key Points
tryblock contains code that might failcatchblock runs only if an error occurs in the try blockfinallyblock ALWAYS runs, whether there was an error or not- Not all errors are catchable by default (see ErrorAction)
- Use
$_or$PSItemin catch block to access error details
Error Types
Terminating vs Non-Terminating Errors
# NON-TERMINATING ERROR (by default)
# Shows error but continues running
Get-Item "C:\DoesNotExist.txt"
Write-Output "Script continues..." # This still runs
# TERMINATING ERROR
# Stops execution - can be caught
try {
Get-Item "C:\DoesNotExist.txt" -ErrorAction Stop # Force terminating
Write-Output "This won't run"
}
catch {
Write-Output "Caught the error!"
}
Making Errors Catchable
# Won't catch - non-terminating error
try {
Get-Item "C:\DoesNotExist.txt" # Error shown but not caught
}
catch {
Write-Output "This won't run"
}
# WILL catch - forced to terminating
try {
Get-Item "C:\DoesNotExist.txt" -ErrorAction Stop
}
catch {
Write-Output "Caught it!" # This runs
}
# Alternative: Set preference for all commands
$ErrorActionPreference = "Stop"
try {
Get-Item "C:\DoesNotExist.txt" # Now catchable
}
catch {
Write-Output "Caught it!"
}
Basic Error Handling
Simple Try-Catch
try {
$content = Get-Content -Path "C:\config.txt" -ErrorAction Stop
Write-Output "File loaded successfully"
}
catch {
Write-Output "Failed to load file: $($_.Exception.Message)"
}
Try-Catch-Finally
$file = $null
try {
$file = [System.IO.File]::Open("C:\data.txt", "Open")
# Do something with file
Write-Output "Processing file..."
}
catch {
Write-Error "Failed to process file: $_"
}
finally {
# Always clean up, even if error occurred
if ($null -ne $file) {
$file.Close()
Write-Output "File closed"
}
}
Accessing Error Information
Error Object Properties
try {
Get-Item "C:\DoesNotExist.txt" -ErrorAction Stop
}
catch {
# $_ or $PSItem contains the error
Write-Output "Error Message: $($_.Exception.Message)"
Write-Output "Error Type: $($_.Exception.GetType().FullName)"
Write-Output "Failed Item: $($_.TargetObject)"
Write-Output "Script Line: $($_.InvocationInfo.ScriptLineNumber)"
Write-Output "Line Content: $($_.InvocationInfo.Line)"
# Full error details
Write-Output "`nFull Error:"
Write-Output $_ | Format-List * -Force
}
Output Example:
Error Message: Cannot find path 'C:\DoesNotExist.txt'
Error Type: System.Management.Automation.ItemNotFoundException
Failed Item: C:\DoesNotExist.txt
Script Line: 2
Line Content: Get-Item "C:\DoesNotExist.txt" -ErrorAction Stop
Catching Specific Errors
Catch Different Error Types
try {
$number = [int]"NotANumber" # Type conversion error
}
catch [System.InvalidCastException] {
Write-Output "Can't convert to number"
}
catch [System.IO.FileNotFoundException] {
Write-Output "File not found"
}
catch {
Write-Output "Some other error: $_"
}
Real-World Specific Catches
try {
$user = Get-ADUser -Identity "nonexistent" -ErrorAction Stop
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
Write-Output "User not found in Active Directory"
# Maybe create the user?
}
catch [System.UnauthorizedAccessException] {
Write-Output "Insufficient permissions to query AD"
}
catch {
Write-Output "Unexpected error: $($_.Exception.GetType().Name)"
Write-Output $_.Exception.Message
}
Common Use Cases
Use Case 1: File Operations with Error Handling
What it does: Safely read a file with fallback options
function Get-ConfigFile {
param(
[string]$Path = "C:\config.txt",
[string]$BackupPath = "C:\config.backup.txt"
)
try {
$content = Get-Content -Path $Path -ErrorAction Stop
Write-Output "Loaded config from primary location"
return $content
}
catch [System.IO.FileNotFoundException] {
Write-Warning "Primary config not found, trying backup..."
try {
$content = Get-Content -Path $BackupPath -ErrorAction Stop
Write-Output "Loaded config from backup location"
return $content
}
catch {
Write-Error "No config file found. Using defaults."
return @("default=value")
}
}
catch {
Write-Error "Unexpected error loading config: $_"
throw # Re-throw to caller
}
}
Use Case 2: Network Operations with Retry
What it does: Retry failed network operations
function Test-ServerConnection {
param(
[string]$ComputerName,
[int]$MaxRetries = 3
)
$attempt = 0
$success = $false
while ($attempt -lt $MaxRetries -and -not $success) {
$attempt++
try {
Write-Output "Attempt $attempt of $MaxRetries..."
$result = Test-Connection -ComputerName $ComputerName -Count 1 -ErrorAction Stop
Write-Output "Connection successful!"
$success = $true
return $true
}
catch {
Write-Warning "Attempt $attempt failed: $($_.Exception.Message)"
if ($attempt -lt $MaxRetries) {
Write-Output "Waiting 5 seconds before retry..."
Start-Sleep -Seconds 5
}
}
}
if (-not $success) {
Write-Error "Failed to connect after $MaxRetries attempts"
return $false
}
}
Use Case 3: Database Connection Cleanup
What it does: Ensure database connections are always closed
function Invoke-DatabaseQuery {
param(
[string]$ConnectionString,
[string]$Query
)
$connection = $null
$command = $null
try {
# Open connection
$connection = New-Object System.Data.SqlClient.SqlConnection($ConnectionString)
$connection.Open()
Write-Output "Database connection opened"
# Execute query
$command = $connection.CreateCommand()
$command.CommandText = $Query
$result = $command.ExecuteScalar()
Write-Output "Query executed successfully"
return $result
}
catch [System.Data.SqlClient.SqlException] {
Write-Error "Database error: $($_.Exception.Message)"
throw
}
catch {
Write-Error "Unexpected error: $_"
throw
}
finally {
# Always cleanup, even if error occurred
if ($null -ne $command) {
$command.Dispose()
}
if ($null -ne $connection) {
if ($connection.State -eq 'Open') {
$connection.Close()
Write-Output "Database connection closed"
}
$connection.Dispose()
}
}
}
Error Actions
ErrorAction Parameter
# SilentlyContinue - Suppress error, continue
Get-Item "C:\NoFile.txt" -ErrorAction SilentlyContinue
Write-Output "Script continues"
# Stop - Make it a terminating error (catchable)
try {
Get-Item "C:\NoFile.txt" -ErrorAction Stop
}
catch {
Write-Output "Caught!"
}
# Continue - Show error, continue (default)
Get-Item "C:\NoFile.txt" -ErrorAction Continue
Write-Output "Keeps running"
# Inquire - Ask user what to do
Get-Item "C:\NoFile.txt" -ErrorAction Inquire
# Ignore - Completely ignore (not even added to $Error)
Get-Item "C:\NoFile.txt" -ErrorAction Ignore
ErrorActionPreference Variable
# Set for entire script/session
$ErrorActionPreference = "Stop" # All errors are now terminating
try {
Get-Item "C:\NoFile.txt" # Now catchable without -ErrorAction
}
catch {
Write-Output "Caught without -ErrorAction Stop"
}
# Reset to default
$ErrorActionPreference = "Continue"
The $Error Variable
# $Error is an array of all errors in the session
$Error.Clear() # Clear error history
Get-Item "C:\DoesNotExist.txt" -ErrorAction SilentlyContinue
Get-Process "NonExistentProcess" -ErrorAction SilentlyContinue
# View all errors
$Error.Count # 2
# Most recent error
$Error[0]
# View all errors with details
$Error | ForEach-Object {
Write-Output "Error: $($_.Exception.Message)"
}
# Clear errors
$Error.Clear()
Error Codes & Categories Reference
Exit Codes
Exit codes are returned when scripts complete or exit. Use exit <code> to return a specific code, and $LASTEXITCODE to check the result.
| Exit Code | Meaning | Usage |
|---|---|---|
0 | Success | Script completed without errors |
1 | General error | Catch-all for non-specific errors |
2 | Misuse of shell command | Invalid arguments or syntax |
126 | Command cannot execute | Permission problem or command is not executable |
127 | Command not found | Path issue or typo in command name |
128 | Invalid exit argument | Exit code must be 0-255 |
130 | Script terminated by Ctrl+C | User interrupted execution |
255 | Exit status out of range | Exit code outside 0-255 range |
Example:
# Return exit code
if (Test-Path "C:\file.txt") {
Write-Output "File exists"
exit 0
} else {
Write-Error "File not found"
exit 1
}
# Check exit code from external program
& "C:\App\program.exe"
if ($LASTEXITCODE -eq 0) {
Write-Output "Program succeeded"
} else {
Write-Error "Program failed with code: $LASTEXITCODE"
}
Error Categories
PowerShell categorizes errors to help identify the type of failure. Access via $_.CategoryInfo.Category.
| Category | Description | Common Scenarios |
|---|---|---|
NotSpecified | General/uncategorized error | Default when no specific category applies |
OpenError | Failed to open resource | File, network share, or database connection |
CloseError | Failed to close resource | Cannot properly close file or connection |
DeviceError | Device-related failure | Hardware, printer, or removable media issues |
ReadError | Failed to read data | Cannot read file, registry, or stream |
WriteError | Failed to write data | Cannot write to file, disk full, read-only |
ResourceExists | Resource already exists | File exists when creating, duplicate key |
ResourceUnavailable | Cannot access resource | File locked, service stopped, network down |
InvalidOperation | Operation not valid in current state | Wrong order, precondition not met |
InvalidData | Data validation failed | Corrupted file, invalid format, type mismatch |
InvalidArgument | Bad parameter value | Out of range, wrong type, null when required |
PermissionDenied | Insufficient permissions | Access denied, need elevation |
ObjectNotFound | Requested object doesn't exist | File, process, user, registry key not found |
ConnectionError | Network/connection problem | Timeout, refused, host unreachable |
AuthenticationError | Authentication failed | Bad credentials, expired token |
Example:
try {
Get-Item "C:\NoFile.txt" -ErrorAction Stop
}
catch {
Write-Output "Error Category: $($_.CategoryInfo.Category)"
# Output: ObjectNotFound
# Handle based on category
switch ($_.CategoryInfo.Category) {
"ObjectNotFound" { Write-Output "Resource doesn't exist" }
"PermissionDenied" { Write-Output "Need admin rights" }
"InvalidArgument" { Write-Output "Check your parameters" }
default { Write-Output "Other error type" }
}
}
Common Exception Types
These are the most frequently encountered .NET exception types when working with PowerShell.
| Exception Type | Description | Common Causes |
|---|---|---|
System.Management.Automation.ItemNotFoundException | Item not found | File, registry key, or path doesn't exist |
System.Management.Automation.CommandNotFoundException | Cmdlet/command not found | Typo, module not loaded, path issue |
System.Management.Automation.ParameterBindingException | Parameter binding failed | Wrong parameter type, missing required param |
System.Management.Automation.RuntimeException | General PowerShell runtime error | Various runtime failures |
System.IO.FileNotFoundException | File not found | Specified file doesn't exist |
System.IO.DirectoryNotFoundException | Directory not found | Specified folder doesn't exist |
System.IO.IOException | I/O operation failed | File locked, disk full, network share issue |
System.UnauthorizedAccessException | Access denied | Insufficient permissions, need elevation |
System.InvalidOperationException | Operation not valid | Wrong state, precondition not met |
System.ArgumentException | Invalid argument | Bad parameter value |
System.ArgumentNullException | Required argument is null | Missing required value |
System.FormatException | Format/parsing error | Cannot convert string to number/date |
System.InvalidCastException | Type conversion failed | Cannot cast type A to type B |
System.Net.WebException | Web request failed | HTTP error, timeout, network issue |
System.TimeoutException | Operation timed out | Long-running operation exceeded limit |
Example:
try {
$number = [int]"NotANumber"
}
catch [System.InvalidCastException] {
Write-Output "Cannot convert to integer"
}
catch [System.FormatException] {
Write-Output "Invalid number format"
}
# Inspect exception type
try {
Get-Item "C:\NoFile.txt" -ErrorAction Stop
}
catch {
$exceptionType = $_.Exception.GetType().FullName
Write-Output "Exception Type: $exceptionType"
# Output: System.Management.Automation.ItemNotFoundException
}
Real-World Examples
Example: Robust File Copy with Logging
Scenario: Copy files with comprehensive error handling and logging
function Copy-FileWithRetry {
param(
[string]$Source,
[string]$Destination,
[int]$MaxRetries = 3
)
# Validate source exists
if (-not (Test-Path $Source)) {
throw "Source file not found: $Source"
}
$attempt = 0
$success = $false
while ($attempt -lt $MaxRetries -and -not $success) {
$attempt++
try {
Write-Output "[$attempt/$MaxRetries] Copying $Source to $Destination..."
Copy-Item -Path $Source -Destination $Destination -ErrorAction Stop -Force
# Verify copy
if (Test-Path $Destination) {
$sourceHash = (Get-FileHash -Path $Source).Hash
$destHash = (Get-FileHash -Path $Destination).Hash
if ($sourceHash -eq $destHash) {
Write-Output "Copy successful and verified"
$success = $true
} else {
throw "File copied but hash mismatch"
}
}
}
catch [System.IO.IOException] {
Write-Warning "IO Error on attempt $attempt : $($_.Exception.Message)"
if ($attempt -lt $MaxRetries) {
Start-Sleep -Seconds ([math]::Pow(2, $attempt))
}
}
catch [System.UnauthorizedAccessException] {
Write-Error "Access denied: $($_.Exception.Message)"
break # Don't retry permission errors
}
catch {
Write-Warning "Unexpected error on attempt $attempt : $_"
if ($attempt -lt $MaxRetries) {
Start-Sleep -Seconds 3
}
}
}
if (-not $success) {
throw "Failed to copy file after $MaxRetries attempts"
}
return $true
}
Example: Service Management with Error Handling
Scenario: Start a service with comprehensive error handling
function Start-ServiceSafely {
param(
[string]$ServiceName,
[int]$TimeoutSeconds = 30
)
try {
# Check if service exists
$service = Get-Service -Name $ServiceName -ErrorAction Stop
if ($service.Status -eq "Running") {
Write-Output "$ServiceName is already running"
return $true
}
Write-Output "Starting $ServiceName..."
Start-Service -Name $ServiceName -ErrorAction Stop
# Wait for service to start
$service.WaitForStatus("Running", [TimeSpan]::FromSeconds($TimeoutSeconds))
Write-Output "$ServiceName started successfully"
return $true
}
catch [Microsoft.PowerShell.Commands.ServiceCommandException] {
Write-Error "Service error: $($_.Exception.Message)"
# Check if it's a dependency issue
if ($_.Exception.Message -like "*dependencies*") {
Write-Output "Checking service dependencies..."
$service = Get-Service -Name $ServiceName
$dependencies = $service.ServicesDependedOn
foreach ($dep in $dependencies) {
Write-Output "Dependency: $($dep.Name) - Status: $($dep.Status)"
}
}
return $false
}
catch [System.TimeoutException] {
Write-Error "Service did not start within $TimeoutSeconds seconds"
return $false
}
catch {
Write-Error "Unexpected error starting service: $_"
return $false
}
}
Tips & Tricks
Always Use -ErrorAction Stop for Catchable Errors
Use Finally for Cleanup
Re-throw Errors When Appropriate
Don't Catch Everything Silently
# BAD: Hides all errors
try {
# Complex code
}
catch {
# Silent failure - no logging, no nothing
}
# GOOD: At least log it
try {
# Complex code
}
catch {
Write-Error "Error occurred: $_"
# Maybe log to file
Add-Content -Path "C:\Logs\errors.log" -Value "$(Get-Date): $_"
throw # Re-throw if caller needs to know
}
Watch Out for Non-Terminating Errors
# These won't be caught without -ErrorAction Stop
try {
Get-Process "NoSuchProcess" # Non-terminating
Get-Item "C:\NoFile.txt" # Non-terminating
Write-Error "Custom error" # Non-terminating
}
catch {
# Doesn't run!
}
# Fix: Use -ErrorAction Stop
try {
Get-Process "NoSuchProcess" -ErrorAction Stop
Get-Item "C:\NoFile.txt" -ErrorAction Stop
Write-Error "Custom error" -ErrorAction Stop
}
catch {
# Now it runs
}
$ErrorActionPreference Scope
Related Topics
- Functions - Adding error handling to functions
- Loops - Error handling in loops
- Variables & Data Types - Understanding $Error and error objects
- Logging - Logging errors for troubleshooting