Error Handling & Basic Debugging
PowerShell Fundamentals
Chapter 9 · Error Handling & Basic Debugging
try/catch looks exactly like it does in Java, JavaScript, or C# — which is precisely what makes PowerShell's own version so easy to get wrong the first time. Most built-in cmdlets don't actually stop and throw the way an exception would in those languages; they print a red error and simply move on to the next thing. A catch block wrapped around one of those cmdlets can sit there, correctly written, and never run at all. This chapter is about why, and about the one setting that fixes it.
Two Kinds of Errors: Terminating vs. Non-Terminating
PowerShell has two genuinely different error categories, and the distinction isn't cosmetic:
Most cmdlet errors — a missing file, an unreachable path, a permission failure — are non-terminating by default: PowerShell displays the error and simply continues to the next statement, exactly as if nothing had interrupted it. try/catch only intercepts terminating errors — ones that actually stop execution at that point. A non-terminating error inside a try block just runs its course and lets the rest of the block keep going, right past the catch entirely.
-ErrorAction Stop: Making try/catch Actually Work
Nearly every cmdlet accepts an -ErrorAction parameter, and Stop is the one that converts a non-terminating error into a genuine terminating one catch can intercept:
| -ErrorAction value | Behavior |
|---|---|
| Continue | Default — display the error, keep running (non-terminating; catch can't see it) |
| Stop | Turns the error terminating — the only value that reliably makes catch fire |
| SilentlyContinue | Suppress the display, keep running — the error still lands in $Error below |
| Ignore | Suppress the display and skip $Error too — the error leaves no trace at all |
| Inquire | Prompt interactively for what to do |
try/catch block around a cmdlet call is not, by itself, real error handling — it's only real error handling once you've confirmed that cmdlet's failure is actually terminating, almost always by adding -ErrorAction Stop yourself. Skipping this is the single most common reason a PowerShell script's error handling silently does nothing: the code looks defensive, reads cleanly, and simply never activates.
$_ Inside catch: A Completely Different Meaning
$_ as "the current object flowing through the pipeline," inside Where-Object or ForEach-Object. Inside a catch block, $_ means something entirely different: the error record that was just caught — not your data at all. $_.Exception.Message is how you pull the human-readable error text out of it. There's no relationship between these two uses beyond sharing the same variable name; which one applies is decided purely by which kind of block you're currently inside.
$Error: The Session's Own Error History
$Error collects every error PowerShell has recorded this session — including SilentlyContinue ones from the table above — regardless of whether anything actually caught them. It's the automatic variable Chapter 5 flagged but deferred; this is its real, practical use.
finally: Always Runs
finally runs regardless of outcome — success, a caught error, or even an uncaught one — which makes it the right place for cleanup that must always happen (closing a connection, releasing a lock) rather than cleanup that only matters on failure.
Write-Verbose: The Safe Status Channel Promised in Chapter 7
Chapter 7 flagged Write-Verbose as a safer alternative to a bare, uncaptured status message — here's why: it's completely hidden by default, and only appears when the caller explicitly opts in with -Verbose:
Unlike Write-Output (Chapter 7), a Write-Verbose call never joins the function's return value under any circumstances — it writes to a genuinely separate stream that only -Verbose reveals.
try/catch around a built-in cmdlet, add -ErrorAction Stop to that cmdlet call as a reflex — before assuming your error handling works, not after debugging why it silently didn't.
Hands-On Exercises
Explain why wrapping Get-Item C:\does-not-exist.txt in try/catch (with no -ErrorAction) never triggers the catch block. What single change fixes it?
Explain why $_ means something completely different inside a catch block versus inside a Where-Object script block, referencing Chapter 4's own original meaning for $_.
Write a try/catch/finally block that attempts Get-Content -ErrorAction Stop on a file, prints a friendly message using $_.Exception.Message in catch, and always prints "Done" in finally regardless of success or failure.
Chapter 9 Quick Reference
- Non-terminating vs. terminating errors — most cmdlet errors are non-terminating by default; only terminating errors reach
catch -ErrorAction Stop— converts a non-terminating error into onecatchcan actually intercept-ErrorAction SilentlyContinue / Ignore / Inquire— suppress display (still logs to$Error) / suppress entirely / prompt$_.Exception.Message— the readable error text, inside acatchblock only$_insidecatch— the caught error record, a completely different meaning from$_insideWhere-Object/ForEach-Object(Chapter 4)$Error[0]/$Error.Count/$Error.Clear()— the session's running error historyfinally— always runs, success or failure — the right place for guaranteed cleanupWrite-Verbose— hidden by default, shown with-Verbose, never joins the return value- Next chapter: Execution Policy & Script Security Basics