Error Handling: try/catch and ErrorAction
Reviewed & published by Brayan K
By the end of this lesson you'll know the crucial difference between terminating and non-terminating errors, wrap risky code in try/catch/finally, promote errors with -ErrorAction Stop, raise your own with throw, and inspect what went wrong with $Error and -ErrorVariable — so your scripts fail gracefully instead of crashing.
Part of the free PowerShell course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- Tell terminating from non-terminating errors
- Wrap risky code in try / catch / finally
- Promote errors with -ErrorAction Stop and $ErrorActionPreference
- Raise your own errors with throw
- Catch specific exception types in [brackets]
- Inspect failures with $Error and -ErrorVariable
💡 Real-World Analogy
Think of error handling like a circuit breaker in your house. A non-terminating error is a flickering light — annoying, but the power stays on and everything keeps running. A terminating error is a real fault that trips the breaker and stops the circuit. try is the wiring you're testing; catch is the breaker that trips safely instead of starting a fire; and finally is the inspector who always shows up afterward to tidy up, fault or no fault. -ErrorAction Stop is you deciding that a particular flicker is serious enough to trip the breaker on purpose.
1. Terminating vs Non-Terminating Errors
This distinction is the key to everything. A non-terminating error reports a problem but lets the command (and pipeline) keep going — this is the default for most cmdlets. A terminating error stops the current command dead. The catch is that try/catch only reacts to terminating errors. To make a non-terminating error catchable, promote it with -ErrorAction Stop. Read this worked example, then run it.
# PowerShell has TWO kinds of error, and the difference decides everything.
# NON-TERMINATING: the command reports a problem but the pipeline keeps going.
# Get-Item on a missing path writes an error, then moves to the next item.
Get-Item 'C:\nope.txt', 'C:\Windows' # error for nope.txt, then lists Windows
# TERMINATING: stops the current command dead. try/catch ONLY catches these.
# Force a non-terminating error to terminate with -ErrorAction Stop:
try {
Get-Item 'C:\nope.txt' -ErrorAction Stop
}
catch {
"Caught it: the file was not found."
}2. try / catch / finally
The structure is simple: try {} holds the risky code, catch {} runs only if a terminating error occurs, and finally {} always runs afterward for cleanup. Inside catch, the automatic variable $_ is the ErrorRecord; $_.Exception.Message gives you the human-readable text.
# try {} runs the risky code. catch {} runs ONLY if a terminating error happens.
# finally {} ALWAYS runs afterwards — perfect for cleanup.
try {
Write-Output "Opening the file..."
$content = Get-Content 'C:\missing.txt' -ErrorAction Stop # will fail
Write-Output "This line never runs."
}
catch {
# $_ is the ErrorRecord. .Exception.Message is the human-readable text.
Write-Output "Something went wrong: $($_.Exception.Message)"
}
finally {
Write-Output "Cleanup runs no matter what."
}Now you try. Fill in the three blanks using the hints in the comments, then run it.
# 🎯 YOUR TURN — replace each ___ then run it.
# Goal: try to read a missing file, catch the error, and always say 'Done'.
___ { # 👉 the keyword is: try
Get-Content 'C:\nope.txt' -ErrorAction ___ # 👉 promote to terminating: Stop
}
catch {
"Handled: $($_.Exception.Message)"
}
___ { # 👉 the always-runs block: finally
"Done"
}
# ✅ Expected output:
# Handled: Cannot find path 'C:\nope.txt' because it does not exist.
# Done3. -ErrorAction & $ErrorActionPreference
Every cmdlet accepts the common parameter -ErrorAction, which controls how that one command reacts to an error. Stop makes it terminating (catchable), SilentlyContinue suppresses it entirely, and Continue (the default) shows it but carries on. To change the default for an entire script or session, set the preference variable $ErrorActionPreference.
# -ErrorAction controls ONE command. Common values:
# Stop -> terminate (so try/catch can catch it)
# SilentlyContinue -> suppress the error, keep going
# Continue -> the default: show the error, keep going
Get-Item 'C:\nope.txt' -ErrorAction SilentlyContinue # nothing printed
Write-Output "Still running."
# $ErrorActionPreference sets the DEFAULT for the whole session/script:
$ErrorActionPreference = 'Stop' # now any error terminates by default
# ... risky code ...
$ErrorActionPreference = 'Continue' # put it back when you're donePro Tip
Prefer a per-command -ErrorAction Stop over flipping $ErrorActionPreference globally. The per-command form affects only the line you intend, so you won't accidentally make an unrelated command terminate. Reach for the global preference only when you genuinely want strict behaviour across a whole script.
Putting It Together: throw, typed catch, $Error
Now combine the rest of the toolkit: throw raises your own terminating error, a typed catch [Type] handles one specific exception, -ErrorVariable captures a command's errors into your own variable, and $Error is the session-wide history with $Error[0] as the newest.
# throw raises YOUR OWN terminating error.
function Test-Age {
param([int]$Age)
if ($Age -lt 0) { throw "Age cannot be negative." }
"Age is $Age"
}
try { Test-Age -Age -5 }
catch { "Rejected: $($_.Exception.Message)" }
# Catch a SPECIFIC exception type by naming it in [brackets]:
try {
Get-Content 'C:\nope.txt' -ErrorAction Stop
}
catch [System.Management.Automation.ItemNotFoundException] {
"That file does not exist."
}
catch {
"Some other error: $($_.Exception.Message)"
}
# Capture a command's errors into your OWN variable with -ErrorVariable:
Get-Item 'C:\nope.txt' -ErrorAction SilentlyContinue -ErrorVariable myErr
"Errors captured: $($myErr.Count)"
# $Error is the session-wide list; $Error[0] is the most recent.
"Newest error: $($Error[0].Exception.Message)"To find an exception's type for a typed catch, run the code once and inspect $Error[0].Exception.GetType().FullName — then paste that type into your catch [Type].
Common Errors (and the fix)
- "My try/catch never catches anything" — the error is non-terminating. Add -ErrorAction Stop to the risky command so it terminates and the catch can fire.
- Catching too broadly — a bare catch {} hides real bugs. Add a typed catch [System.IO.FileNotFoundException] for the expected case, and keep the general catch as a fallback.
- Using Write-Error when you meant to stop — Write-Error is non-terminating by default. Use throw, or Write-Error … -ErrorAction Stop, when execution truly must halt.
- Cleanup that doesn't always run — putting cleanup at the end of try skips it on error. Move it into finally {} so it runs every time.
- Reading $Error and getting an old error — $Error[0] is the most recent error, but the list persists across commands. Clear it with $Error.Clear() if you need a clean slate.
📋 Quick Reference — Error Handling
| Tool | Purpose | Example |
|---|---|---|
| try/catch | Handle terminating errors | try {} catch {} |
| finally | Always-run cleanup | finally {} |
| -ErrorAction Stop | Promote to terminating | Get-Item x -ErrorAction Stop |
| throw | Raise your own error | throw 'bad input' |
| $Error | Recent error history | $Error[0] |
| -ErrorVariable | Capture errors | -ErrorVariable myErr |
Mini-Challenge: a safe file reader
No blanks this time — just a brief and an outline to keep you on track. Build it, run it, and check your output against the example in the comments. Writing functions that fail gracefully is exactly what production scripts demand.
# 🎯 MINI-CHALLENGE: a safe file reader
# No code is filled in — just an outline. Build it and check the output.
#
# 1. function Read-Safe with a [string]$Path parameter.
# 2. Inside a try {} block, run Get-Content $Path -ErrorAction Stop and
# return its contents.
# 3. catch [System.Management.Automation.ItemNotFoundException] -> output
# "File not found: <Path>".
# 4. A general catch {} -> output "Unexpected: <message>".
# 5. A finally {} block -> output "Read attempt finished.".
#
# Try it with a path that exists AND one that doesn't.
#
# ✅ Example output (missing file):
# File not found: C:\nope.txt
# Read attempt finished.
# your code here🎉 Lesson Complete
- ✅ Only terminating errors trigger catch; promote others with -ErrorAction Stop
- ✅ try / catch / finally handle and clean up failures
- ✅ $ErrorActionPreference sets the session-wide default
- ✅ throw raises your own error; typed catch [Type] handles specific ones
- ✅ $Error and -ErrorVariable let you inspect what went wrong
Practice quiz
Which type of error can be caught by a try/catch block by default?
- A terminating error
- Any warning
- A verbose message
- A non-terminating error
Answer: A terminating error. Only terminating errors trigger catch. Non-terminating errors must be promoted with -ErrorAction Stop first.
What does -ErrorAction Stop do to a non-terminating error?
- Hides it completely
- Logs it to a file
- Turns it into a terminating error
- Retries the command
Answer: Turns it into a terminating error. -ErrorAction Stop promotes a non-terminating error into a terminating one so try/catch can handle it.
Which block always runs whether or not an error occurred?
- ensure
- finally
- catch
- trap
Answer: finally. The finally block runs after try (and any catch), making it ideal for cleanup like closing files.
What does -ErrorAction SilentlyContinue do?
- Stops the script
- Prompts the user
- Throws immediately
- Suppresses the error and continues
Answer: Suppresses the error and continues. SilentlyContinue suppresses the error display and lets the command continue without halting.
Which automatic variable holds the collection of recent errors?
- $Error
- $LastError
- $ErrorList
- $Exception
Answer: $Error. $Error is an array of the most recent errors, with $Error[0] being the newest.
How do you raise your own terminating error in PowerShell?
- stop 'message'
- throw 'message'
- raise 'message'
- error 'message'
Answer: throw 'message'. The throw keyword raises a terminating error that can be caught by an enclosing try/catch.
Which preference variable sets the default error behaviour for the session?
- $ErrorMode
- $OnError
- $DefaultError
- $ErrorActionPreference
Answer: $ErrorActionPreference. $ErrorActionPreference controls the default action (Continue, Stop, SilentlyContinue) for the whole session.
Inside a catch block, which variable holds the current error?
- $Error
- $Caught
- $_
- $Result
Answer: $_. Inside catch, $_ (the current object) is the ErrorRecord that was thrown.
How can you catch only a specific exception type?
- catch -Type
- catch [System.IO.FileNotFoundException]
- catch when (type)
- try -Only
Answer: catch [System.IO.FileNotFoundException]. You write the .NET type in square brackets after catch to handle only that exception type.
What does the -ErrorVariable parameter do?
- Renames $Error
- Stores the command's errors in a named variable
- Sets the exit code
- Clears all errors
Answer: Stores the command's errors in a named variable. -ErrorVariable myErr captures that command's errors into $myErr without affecting $Error handling.
Continue this course
- Previous: The Pipeline: Where-Object, Select-Object, Sort-Object
- Next: Modules & the PowerShell Gallery — Find, install and import modules — and author your own .psm1/.psd1
Frequently asked questions
Why doesn't my try/catch catch the error?
Almost always because the error is non-terminating. By default most cmdlets emit non-terminating errors that report a problem but let execution continue — and catch only fires on terminating errors. The fix is to add -ErrorAction Stop to the risky command (or set $ErrorActionPreference = 'Stop'), which promotes the error to terminating so your catch block runs. This is the single most common error-handling surprise in PowerShell.
What is the difference between throw and Write-Error?
throw raises a terminating error that stops the current command and is caught by an enclosing try/catch — use it when execution genuinely cannot continue. Write-Error writes a non-terminating error to the error stream and, by default, lets the script keep going — use it to report a problem without halting. If you want Write-Error to terminate, give the command -ErrorAction Stop or use throw instead.
When should I use finally?
Use finally for cleanup that must happen whether the code succeeded or failed: closing a file handle, disposing a database connection, removing a temporary file, or releasing a lock. The finally block runs after the try block completes normally AND after any catch block handles an error, so the resource is always released. If there's nothing to clean up, you can omit finally entirely — try/catch alone is fine.
How do I catch only one kind of error?
Put the .NET exception type in square brackets after catch, e.g. catch [System.IO.FileNotFoundException] { ... }. You can stack several typed catch blocks, and PowerShell uses the first one whose type matches; a final untyped catch { ... } acts as the catch-all. Order matters: list specific types before the general catch. To discover an error's type, inspect $Error[0].Exception.GetType().FullName after it happens.
What is $ErrorActionPreference and should I change it?
$ErrorActionPreference is a preference variable that sets the DEFAULT -ErrorAction for every command in the session or script. Its normal value is 'Continue' (show the error, keep going). Setting it to 'Stop' makes errors terminating everywhere, which is handy at the top of a script so try/catch reliably catches things. Change it deliberately and consider resetting it afterward; a per-command -ErrorAction is often cleaner because it affects only the one command you intend.