Skip to main content
PowerShell · lesson 7 of 12
PowerShell intermediate 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

powershell error-handling try-catch exceptions

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.

Frequently Asked Questions

What's the actual difference between a terminating and non-terminating error?
A terminating error stops the current command (or script) immediately and is the only kind try/catch can intercept. A non-terminating error is written to the error stream and execution just continues to the next line — most built-in cmdlets, including Get-Item and Get-ChildItem, produce non-terminating errors by default for exactly this reason: one bad file in a thousand shouldn't abort the whole batch.
Why didn't my try/catch catch a red error message I could clearly see on screen?
Seeing red text doesn't mean an error was terminating — it only means something wrote to the error stream, which non-terminating errors do too. Add -ErrorAction Stop to the specific cmdlet call, so that one call's errors become terminating and therefore catchable; leave the rest of the script's other cmdlets alone unless they need the same treatment.
Should I set $ErrorActionPreference = "Stop" globally instead of adding -ErrorAction Stop everywhere?
It's a legitimate choice for a script written entirely around try/catch, since it makes every cmdlet's errors terminating without repeating -ErrorAction Stop on each line — but it's a global, mutable setting that affects code you didn't write too, like an imported module's cmdlets. Reset it (or scope the change to a function) rather than leaving it changed for the rest of a session.
What does throw do differently from just letting an error happen?
throw always raises a terminating error immediately, on demand — it's how you signal a failure condition your own code detects (a bad parameter combination, a business-rule violation) rather than one a cmdlet happened to run into. It's the PowerShell equivalent of raise in Python or throw in Java/C#, and it's always catchable, with no -ErrorAction involved.
Can I catch different exception types differently, like catch (SpecificError) versus catch (Exception) in other languages?
Yes — catch [System.Management.Automation.ItemNotFoundException] { ... } matches only that type, and PowerShell checks multiple typed catch blocks in order, falling through to an untyped catch { ... } for anything that doesn't match a more specific one. This is the same pattern as typed except clauses in Python or catch blocks in Java.