PowerShell Error Handling
Error handling is the practice of anticipating, detecting, and responding to problems in a script. Without error handling, a single unexpected failure can crash an entire script, leaving systems in an incomplete or broken state. Proper error handling makes scripts robust, safe, and informative even when things go wrong.
Types of Errors in PowerShell
| Error Type | Description | Script Behavior |
|---|---|---|
| Terminating Error | Stops execution immediately | Script halts unless caught with try-catch |
| Non-Terminating Error | Displays an error but continues | Script keeps running by default |
# Non-terminating error – script continues
Get-Item "C:\DoesNotExist\file.txt"
Write-Host "This still runs after the error"
# Terminating error – script stops (e.g., syntax error or thrown exception)
throw "Critical failure"
Write-Host "This line never runs"
$ErrorActionPreference
$ErrorActionPreference controls how PowerShell responds to non-terminating errors. Setting it to "Stop" converts non-terminating errors into terminating ones, making them catchable.
| Value | Behavior |
|---|---|
| Continue | Show error and keep running (default) |
| Stop | Treat all errors as terminating — halts script |
| SilentlyContinue | Suppress error messages and keep running |
| Inquire | Ask the user what to do after each error |
| Ignore | Ignore error completely — nothing recorded in $Error |
# Set globally for the script
$ErrorActionPreference = "Stop"
# Override for a single cmdlet
Get-Item "C:\missing.txt" -ErrorAction SilentlyContinue
Get-Item "C:\missing.txt" -ErrorAction Stop
try-catch-finally
The try-catch-finally block is the standard structured error handling pattern.
try {
# Code that might fail
}
catch {
# Runs only if an error occurs in try
}
finally {
# ALWAYS runs, regardless of error or success
}
Basic try-catch
try {
$file = Get-Item "C:\Reports\missing.csv" -ErrorAction Stop
Write-Host "File found: $($file.FullName)"
}
catch {
Write-Host "Error: Could not find the file." -ForegroundColor Red
Write-Host "Details: $($_.Exception.Message)"
}
Output:
Error: Could not find the file.
Details: Cannot find path 'C:\Reports\missing.csv' because it does not exist.
try-catch-finally
try {
Write-Host "Opening database connection..."
# Simulate connection failure
throw "Database server unreachable"
Write-Host "Connection established."
}
catch {
Write-Host "Connection failed: $($_.Exception.Message)" -ForegroundColor Red
}
finally {
Write-Host "Cleanup complete. Resources released." -ForegroundColor Yellow
}
Output:
Opening database connection...
Connection failed: Database server unreachable
Cleanup complete. Resources released.
Catching Specific Exception Types
Multiple catch blocks can target specific exception types — handling each kind of error differently.
try {
$content = Get-Content -Path "C:\Config\settings.xml" -ErrorAction Stop
}
catch [System.IO.FileNotFoundException] {
Write-Host "File does not exist. Creating default config..." -ForegroundColor Yellow
Set-Content -Path "C:\Config\settings.xml" -Value "dev "
}
catch [System.UnauthorizedAccessException] {
Write-Host "Permission denied. Run as Administrator." -ForegroundColor Red
}
catch {
Write-Host "Unexpected error: $($_.Exception.Message)" -ForegroundColor Red
}
The $_ Error Object Inside catch
Inside a catch block, $_ is the error object. It holds full details about what went wrong.
try {
1 / 0 # Division by zero
}
catch {
Write-Host "Exception Type: $($_.Exception.GetType().FullName)"
Write-Host "Message: $($_.Exception.Message)"
Write-Host "Script Line: $($_.InvocationInfo.ScriptLineNumber)"
Write-Host "Command: $($_.InvocationInfo.Line.Trim())"
}
Output:
Exception Type: System.DivideByZeroException
Message: Attempted to divide by zero.
Script Line: 2
Command: 1 / 0
throw – Raise a Custom Error
Use throw to deliberately raise an error with a custom message.
function Set-UserAge {
param ([int]$Age)
if ($Age -lt 1 -or $Age -gt 120) {
throw "Invalid age: $Age. Must be between 1 and 120."
}
Write-Host "Age set to: $Age"
}
try {
Set-UserAge -Age 150
}
catch {
Write-Host "Input error: $($_.Exception.Message)" -ForegroundColor Red
}
Output:
Input error: Invalid age: 150. Must be between 1 and 120.
$Error Variable
PowerShell stores all errors from the current session in the automatic $Error variable. $Error[0] is the most recent error.
# Generate an error
Get-Item "C:\missing.txt" -ErrorAction SilentlyContinue
# Inspect the last error
Write-Host "Last error: $($Error[0].Exception.Message)"
# Show all errors this session
$Error | ForEach-Object { Write-Host $_.Exception.Message }
# Clear the error list
$Error.Clear()
ErrorVariable – Capture Errors from a Single Cmdlet
Get-Item "C:\missing.txt" -ErrorAction SilentlyContinue -ErrorVariable myError
if ($myError) {
Write-Host "Caught error: $($myError[0].Exception.Message)"
}
Trap – Legacy Error Handling
trap is an older error handling mechanism. It intercepts errors in a script block. Modern scripts use try-catch instead, but trap appears in older code.
trap {
Write-Host "Trapped error: $($_.Exception.Message)"
continue # Continue execution after the error
}
Get-Item "C:\missing.txt" -ErrorAction Stop
Write-Host "Script continues after trap"
Real-World Example – Robust File Processing Script
function Process-LogFile {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string]$FilePath
)
$logPath = "C:\Logs\process_errors.log"
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
try {
Write-Verbose "Checking if file exists: $FilePath"
if (-not (Test-Path $FilePath)) {
throw [System.IO.FileNotFoundException] "File not found: $FilePath"
}
$lines = Get-Content -Path $FilePath -ErrorAction Stop
Write-Host "Processing $($lines.Count) lines from $FilePath"
foreach ($line in $lines) {
# Process each line
Write-Verbose "Processing: $line"
}
Write-Host "Processing complete." -ForegroundColor Green
}
catch [System.IO.FileNotFoundException] {
$msg = "[$timestamp] FILE NOT FOUND: $FilePath"
Add-Content -Path $logPath -Value $msg
Write-Host $msg -ForegroundColor Yellow
}
catch [System.UnauthorizedAccessException] {
$msg = "[$timestamp] ACCESS DENIED: $FilePath"
Add-Content -Path $logPath -Value $msg
Write-Host $msg -ForegroundColor Red
}
catch {
$msg = "[$timestamp] UNEXPECTED ERROR: $($_.Exception.Message)"
Add-Content -Path $logPath -Value $msg
Write-Host $msg -ForegroundColor Red
}
finally {
Write-Verbose "Function complete for: $FilePath"
}
}
Process-LogFile -FilePath "C:\Logs\app.log" -Verbose
Process-LogFile -FilePath "C:\Logs\missing.log"
Summary
Error handling in PowerShell uses the try-catch-finally pattern as its backbone. The try block contains potentially failing code. The catch block handles specific or general errors gracefully. The finally block always runs for cleanup. $ErrorActionPreference and the per-cmdlet -ErrorAction parameter control how errors surface. The throw statement raises custom errors inside validation logic. The $Error variable and -ErrorVariable parameter capture errors for inspection and logging. Well-structured error handling is what separates basic scripts from production-quality automation.
