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()
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