PowerShell · lesson 7 of 12
Terminating vs Non-Terminating Errors
A try/catch around a missing-file error does nothing by default — most cmdlet errors in PowerShell are non-terminating, and catch only ever sees the terminating kind.
By SkillByExample · Published
A try/catch that appears to do nothing is one of the most common places a PowerShell
script’s error handling quietly fails — and it happens for a specific, learnable reason.
The bug: catch that never runs
try {
Get-Item -Path "C:\DoesNotExist\nope.txt"
"no exception was thrown, continuing past the missing file"
} catch {
"caught: $($_.Exception.Message)"
}
Get-Item: Cannot find path 'C:\DoesNotExist\nope.txt' because it does not exist.
no exception was thrown, continuing past the missing file
The error printed in red — and the script kept going anyway, straight past the line the
catch block exists to intercept. Get-Item produces a non-terminating error by default:
it writes to the error stream and moves on to the next statement, the same way a bad line in
a thousand-line CSV shouldn’t necessarily abort the whole import. try/catch only ever sees
terminating errors — a non-terminating one simply doesn’t qualify, no matter how visibly
red it is on screen.
The fix: -ErrorAction Stop
try {
Get-Item -Path "C:\DoesNotExist\nope.txt" -ErrorAction Stop
"no exception was thrown, continuing past the missing file"
} catch {
"caught: $($_.Exception.Message)"
}
caught: Cannot find path 'C:\DoesNotExist\nope.txt' because it does not exist.
-ErrorAction Stop tells this one cmdlet call to escalate its own error to terminating —
now catch actually intercepts it, and the line after Get-Item never runs. This is the
single most important habit in PowerShell error handling: try/catch around a cmdlet does
nothing useful unless that cmdlet’s error is either terminating by nature or made terminating
with -ErrorAction Stop.
Setting it globally instead
$ErrorActionPreference = "Stop"
try {
Get-Item -Path "C:\DoesNotExist\nope.txt"
"unreachable"
} catch {
"caught with global preference: $($_.Exception.Message)"
}
$ErrorActionPreference = "Continue"
caught with global preference: Cannot find path 'C:\DoesNotExist\nope.txt' because it does not exist.
Setting $ErrorActionPreference makes every cmdlet’s errors terminating for the rest of the
session — no repeated -ErrorAction Stop needed, but it’s a global switch that affects code
you didn’t write too. Reset it back afterward, as done here, rather than leaving a whole
session’s default error behavior changed by one script.
throw and finally
function Test-Divide {
param([int]$Numerator, [int]$Denominator)
if ($Denominator -eq 0) {
throw "Cannot divide by zero"
}
$Numerator / $Denominator
}
try {
Test-Divide -Numerator 10 -Denominator 0
} catch {
"caught: $($_.Exception.Message)"
} finally {
"cleanup ran regardless"
}
caught: Cannot divide by zero
cleanup ran regardless
throw always raises a terminating error — no -ErrorAction involved, because it’s a
deliberate signal from your own code, not a cmdlet’s default behavior. finally runs whether
try succeeded or catch fired, the usual place for cleanup that has to happen either way.
Catching by exception type
try {
Get-Item -Path "C:\DoesNotExist\nope.txt" -ErrorAction Stop
} catch [System.Management.Automation.ItemNotFoundException] {
"specifically caught: item not found"
} catch {
"caught something else: $($_.Exception.GetType().Name)"
}
specifically caught: item not found
PowerShell checks typed catch blocks in order and runs the first one whose type matches,
falling through to an untyped catch for anything else — the same pattern as typed except
in Python or a catch chain in Java.
Practice
1. Confirm that -ErrorAction SilentlyContinue hides the red text but still doesn't trigger catch.
try {
Get-Item -Path "C:\DoesNotExist\nope.txt" -ErrorAction SilentlyContinue
"no red text appeared, and this line still ran"
} catch {
"caught: $($_.Exception.Message)"
}
no red text appeared, and this line still ran
SilentlyContinue only changes whether the error is displayed — it’s still a non-terminating
error underneath, so catch still never runs. Hiding an error and making it catchable are
two completely different settings.
2. Read the real exception type behind an arithmetic error.
try {
1 / 0
} catch {
"type: $($_.Exception.GetType().Name)"
"message: $($_.Exception.Message)"
}
type: RuntimeException
message: Attempted to divide by zero.
Integer division by zero in PowerShell surfaces as a RuntimeException wrapping the
underlying .NET error — $_.Exception.GetType().Name is how to find out what you actually
caught instead of guessing from the message text.
3. Inspect the automatic $Error variable after a caught exception.
$Error.Clear()
try { 1 / 0 } catch { }
$Error[0].Exception.GetType().Name
RuntimeException
$Error is a running list of every error PowerShell has recorded in the session, most recent
first — useful for inspecting what happened after the fact, even outside a catch block.
$Error.Clear() resets it, handy at the start of a script that wants to check $Error.Count
cleanly afterward.
Next: modules and script parameters — packaging functions so other scripts can reuse them.