Error Handling¶
PowerShell has two kinds of errors: terminating (stop execution) and non-terminating (reported but execution continues). Understanding the difference is key to robust scripts.
Terminating vs. Non-Terminating Errors¶
| Type | Default Behavior | Examples |
|---|---|---|
| Non-terminating | Writes to error stream, continues | Get-Item on missing file, Get-Process on missing name |
| Terminating | Stops the current command/script | throw, $ErrorActionPreference = 'Stop', divide by zero |
Making non-terminating errors terminating¶
# Per-command
Get-Item .\missing.txt -ErrorAction Stop
# For the entire script
$ErrorActionPreference = 'Stop'
$ErrorActionPreference¶
Controls the default behavior for non-terminating errors in the current scope:
| Value | Behavior |
|---|---|
Continue |
(default) display error and continue |
SilentlyContinue |
suppress error output and continue |
Stop |
treat as terminating error |
Inquire |
prompt user to continue/break/etc. |
Ignore |
suppress completely — no error variable update |
Break |
break into the debugger |
# Suppress errors silently (use sparingly)
Get-Item .\maybe-exists.txt -ErrorAction SilentlyContinue
# Script-wide strict error handling
$ErrorActionPreference = 'Stop'
Try / Catch / Finally¶
The fundamental mechanism for handling terminating errors:
try {
$content = Get-Content .\config.json -ErrorAction Stop | ConvertFrom-Json
Write-Host "Config loaded: $($content.AppName)"
}
catch [System.IO.FileNotFoundException] {
Write-Warning "Config file not found. Using defaults."
$content = @{ AppName = "MyApp"; Debug = $false }
}
catch [System.Management.Automation.PSInvalidCastException] {
Write-Error "Config file is not valid JSON"
exit 1
}
catch {
# Catch-all for any other error
Write-Error "Unexpected error: $($_.Exception.Message)"
throw # re-throw to propagate up
}
finally {
# Always runs — use for cleanup
Write-Verbose "Config load attempt complete"
}
The $_ variable in catch¶
Inside a catch block, $_ is the ErrorRecord object:
catch {
$_.Exception.Message # human-readable message
$_.Exception.GetType() # exception type
$_.ScriptStackTrace # where it happened
$_.CategoryInfo # category, activity, target
$_.FullyQualifiedErrorId # unique error ID
$_.InvocationInfo.Line # the line that failed
}
Throw¶
Use throw to raise a terminating error from your own code:
function Get-Config {
param ([string]$Path)
if (-not (Test-Path $Path)) {
throw "Config file '$Path' does not exist"
}
Get-Content $Path -Raw | ConvertFrom-Json
}
Throw with an exception object¶
The $Error Variable¶
$Error is an automatic array of the most recent errors (default last 256):
$Error[0] # most recent error
$Error[0].Exception # the exception
$Error.Count # number of recorded errors
$Error.Clear() # clear the error list
Checking Command Success¶
# $? is $true if last command succeeded
git pull
if (-not $?) {
Write-Error "git pull failed"
}
# $LASTEXITCODE holds the exit code of the last native (non-PS) command
& cmd /c exit 2
$LASTEXITCODE # 2
Strict mode for native commands (PS 7.2+)¶
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
# Now any native command with a non-zero exit code throws
Common Patterns¶
Ignore errors, check result¶
$proc = Get-Process -Name notepad -ErrorAction SilentlyContinue
if ($null -eq $proc) {
"Notepad is not running"
}
Trap errors in a loop and continue¶
$servers = Import-Csv .\servers.csv
$results = foreach ($s in $servers) {
try {
[PSCustomObject]@{
Server = $s.Name
Online = (Test-Connection $s.Name -Count 1 -Quiet -ErrorAction Stop)
Error = $null
}
} catch {
[PSCustomObject]@{
Server = $s.Name
Online = $false
Error = $_.Exception.Message
}
}
}
$results | Format-Table
Retry with error handling¶
function Invoke-WithRetry {
param(
[scriptblock] $ScriptBlock,
[int] $MaxRetries = 3,
[int] $DelaySeconds = 2
)
for ($i = 1; $i -le $MaxRetries; $i++) {
try {
return (& $ScriptBlock)
} catch {
if ($i -eq $MaxRetries) { throw }
Write-Warning "Attempt $i failed: $_. Retrying in ${DelaySeconds}s..."
Start-Sleep -Seconds $DelaySeconds
}
}
}
Invoke-WithRetry { Invoke-RestMethod https://api.example.com/data }
Strict Mode¶
Set-StrictMode catches common scripting mistakes:
Set-StrictMode -Version Latest
# Now these will throw instead of silently returning $null:
$undeclaredVariable # error: undefined variable
$array[99] # error: index out of bounds
$hash.NonExistentKey # error: property does not exist
| Version | What's checked |
|---|---|
1.0 |
Variables must be initialized |
2.0 |
+ calls to non-existent functions, arrays without index |
Latest |
All checks from the latest version |