Terminating vs Non-Terminating Errors
PowerShell distinguishes two categories of errors. Non-terminating errors (the default for most cmdlets, like Get-ChildItem hitting an access-denied folder) are written to the error stream and execution continues with the next statement. Terminating errors (thrown by throw, or by a cmdlet called with -ErrorAction Stop) immediately halt the current scope and can only be intercepted by try/catch. This distinction matters because try/catch does nothing for a cmdlet's default non-terminating errors unless you force them to become terminating with -ErrorAction Stop.
Cricket analogy: Like a dropped catch (non-terminating) that the umpire logs but play continues, versus a serious injury (terminating) that stops the match entirely until medical staff intervene, the difference between routine and match-halting incidents.
Forcing Errors to Be Terminating
Because most cmdlets default to non-terminating errors, wrapping them in try/catch alone won't catch a typical failure like Get-Content on a missing file — the error is written to $Error and execution continues past the catch block untouched. Adding -ErrorAction Stop to the specific cmdlet call converts that call's errors to terminating ones that try/catch will intercept, which is the recommended, surgical approach over globally setting $ErrorActionPreference = 'Stop' for an entire script (which affects every cmdlet and can hide intended non-terminating behavior elsewhere).
Cricket analogy: Like a captain explicitly calling for DRS on one specific decision (-ErrorAction Stop) rather than making every single delivery automatically reviewable, which would slow the whole match down unnecessarily.
Catch Blocks, Exception Types, and Finally
A try block can be followed by multiple catch blocks, each optionally typed to a specific .NET exception class, e.g. catch [System.IO.FileNotFoundException] { ... } catch [System.UnauthorizedAccessException] { ... } catch { ... }, and PowerShell matches the first catch whose type matches (or is a base type of) the thrown exception, falling through to an untyped catch as a general handler. Inside any catch block, $_ (or $PSItem) holds the ErrorRecord, exposing $_.Exception.Message, $_.Exception.GetType().FullName, and $_.ScriptStackTrace for diagnostics. A finally block, if present, always runs — whether the try succeeded, an error was caught, or an uncaught error propagated further up — making it the right place for cleanup like closing a file handle or database connection.
Cricket analogy: Like separate protocols for different injury types — a hamstring strain gets one response, a concussion gets a stricter one, anything else falls to the general medical team — while the ground staff (finally) always covers the pitch at the end of play regardless of outcome.
Throwing Custom Errors and Trap
throw raises a terminating error, and can throw a plain string message, a custom exception object, or re-throw the current error inside a catch block with a bare throw to preserve the original stack trace. Write-Error, by contrast, produces a non-terminating error by default unless combined with -ErrorAction Stop. The older trap keyword is a scope-level error handler that catches any terminating error in its scope without the explicit try boundary that try/catch requires, but it's largely superseded by try/catch/finally in modern scripts because trap's control flow (continue vs break) is less intuitive than structured exception handling.
Cricket analogy: Like a captain formally lodging an official complaint (throw) with full match details preserved, versus a fielder just muttering a complaint (Write-Error) that gets noted but doesn't stop play, and the old-style umpire's blanket ground rule (trap) that covers the whole session rather than one specific incident.
function Import-ConfigFile {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$Path
)
try {
$content = Get-Content -Path $Path -Raw -ErrorAction Stop
$config = $content | ConvertFrom-Json -ErrorAction Stop
}
catch [System.Management.Automation.ItemNotFoundException] {
Write-Error "Config file not found at '$Path'."
throw
}
catch [System.ArgumentException] {
Write-Warning "Config file at '$Path' is not valid JSON: $($_.Exception.Message)"
$config = $null
}
catch {
Write-Error "Unexpected error loading config: $($_.Exception.GetType().FullName) - $($_.Exception.Message)"
throw
}
finally {
Write-Verbose "Import-ConfigFile finished processing '$Path' at $(Get-Date -Format 'HH:mm:ss')"
}
return $config
}The automatic $Error variable is an array of the most recent error records for the whole session, with $Error[0] being the most recent. Inside a catch block, prefer $_ (or $PSItem) over $Error[0] since $_ is guaranteed to be the exact error that triggered that specific catch, while $Error[0] could theoretically be stale in edge cases involving nested error handling.
Setting $ErrorActionPreference = 'Stop' at the top of a script makes every cmdlet's non-terminating errors become terminating, which can be convenient for quick scripts but silently changes the behavior of every cmdlet call in scope — including third-party functions you didn't write — and can cause a script to abort on an error that was previously safe to ignore, like a single failed item in a bulk Get-ADUser query.
- Non-terminating errors let execution continue; terminating errors halt the current scope and are the only kind try/catch intercepts by default.
- Use -ErrorAction Stop on individual cmdlet calls to convert their errors into terminating ones catchable by try/catch.
- Multiple typed catch blocks can handle different .NET exception types, with an untyped catch as the fallback.
- $_ (or $PSItem) inside a catch block exposes the ErrorRecord, including Exception.Message and Exception.GetType().FullName.
- finally always runs regardless of success, caught error, or propagating error, and is the right place for cleanup.
- A bare throw inside a catch block re-throws the original error while preserving its stack trace.
- trap is an older scope-level error handler largely superseded by the clearer structure of try/catch/finally.
Practice what you learned
1. By default, do most PowerShell cmdlets raise terminating or non-terminating errors?
2. What is required to make try/catch intercept a typical cmdlet's default error?
3. What does the finally block guarantee?
4. What does a bare 'throw' with no arguments do inside a catch block?
5. What variable inside a catch block holds the ErrorRecord that triggered it?
Was this page helpful?
You May Also Like
Functions and Parameters
Learn to write reusable PowerShell functions with typed, validated, pipeline-aware parameters, and understand how advanced functions behave like native cmdlets.
Script Blocks and Scopes
Understand PowerShell script blocks as first-class values, how scoping (global, script, local, private) governs variable visibility, and how closures capture variables.
Loops in PowerShell
Master PowerShell's iteration constructs — foreach, for, while, do-while, and pipeline-based ForEach-Object — along with break and continue for controlling repetition.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics