🎁 New User? Get 20% off your first purchase with code NEWUSER20 Β· ⚑ Instant download Β· πŸ”’ Secure checkout Register Now β†’
Menu

Categories

PowerShell Error Handling Mastery: Try/Catch/Finally and ErrorActionPreference (2026)

PowerShell Error Handling Mastery: Try/Catch/Finally and ErrorActionPreference (2026)
PowerShell error handling guide

The first thing that surprises a C# developer writing PowerShell is that try/catch often does nothing. Get-Item C:\does-not-exist writes a red error to the console but the script keeps running, and the catch block never fires. That is by design β€” PowerShell distinguishes terminating from non-terminating errors, and most cmdlets default to non-terminating so a long pipeline can survive one bad item. Once you understand the model, controlling it is easy.

This guide covers the full PowerShell error story: terminating vs non-terminating, $ErrorActionPreference, the -ErrorAction parameter, try/catch/finally, typed catches, $Error, the trap statement, and the patterns you should use in production. Free PDF cheat sheet at the bottom.

Terminating vs non-terminating errors

A terminating error halts the current pipeline and is catchable by try/catch. A non-terminating error writes to the error stream but does not stop execution. Most cmdlets emit non-terminating errors by default β€” that is what lets Remove-Item *.log keep deleting the rest of the files even if one of them is locked.

To make a non-terminating error terminating, set -ErrorAction Stop on the cmdlet:

# Non-terminating - try/catch does nothing
try { Get-Item C:\does-not-exist }
catch { Write-Host "caught" }
# Output: red error from Get-Item, no "caught"

# Terminating - try/catch fires
try { Get-Item C:\does-not-exist -ErrorAction Stop }
catch { Write-Host "caught" }
# Output: "caught"

-ErrorAction and $ErrorActionPreference

-ErrorAction is a common parameter available on every advanced cmdlet. The values:

  • Continue β€” write the error and keep going (default)
  • Stop β€” promote to terminating, raise an exception
  • SilentlyContinue β€” suppress the error message and keep going
  • Ignore β€” suppress AND do not add to $Error
  • Inquire β€” prompt the user

$ErrorActionPreference sets the default for the current scope. Setting it to Stop at the top of a script is one of the highest-value things you can do β€” every non-terminating error becomes catchable, and surprises become impossible:

$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest

try {
    Get-Item C:\maybe-here     # would have been non-terminating
    Remove-Item C:\file.txt    # same
}
catch {
    Write-Host "Failed: $_" -ForegroundColor Red
    exit 1
}

try / catch / finally

Standard structure:

try {
    # Code that might fail
}
catch {
    # Handle the error - $_ or $PSItem is the ErrorRecord
}
finally {
    # ALWAYS runs - cleanup
}

Inside catch, the special variable $_ (also $PSItem) is the ErrorRecord. The most useful properties:

catch {
    $_.Exception.Message        # The human-readable message
    $_.Exception.GetType()      # The .NET exception type
    $_.ScriptStackTrace         # Where it happened
    $_.InvocationInfo.Line      # The actual offending line
    $_.CategoryInfo.Category    # PowerShell error category
    $_.FullyQualifiedErrorId    # Unique error identifier
}

Catching specific exception types

Multiple catch blocks let you handle different exception types differently β€” same as in C#:

try {
    $r = Invoke-RestMethod $url -ErrorAction Stop
}
catch [System.Net.WebException] {
    Write-Host "Network error: $($_.Exception.Message)"
}
catch [System.IO.FileNotFoundException] {
    Write-Host "Missing file: $($_.Exception.Message)"
}
catch {
    # Generic fallback
    Write-Host "Unexpected: $($_.Exception.GetType().FullName) - $($_.Exception.Message)"
    throw  # re-raise
}

Find the right type to catch by triggering the error once and inspecting $Error[0].Exception.GetType().FullName.

The $Error variable

$Error is an automatic ArrayList that holds the last 256 errors (configurable via $MaximumErrorCount) β€” even non-terminating ones, and even ones suppressed with SilentlyContinue (but not Ignore). The most recent error is $Error[0].

Get-Item C:\nope -ErrorAction SilentlyContinue
$Error[0].Exception          # Inspect even though it was silenced

$Error.Clear()               # Reset the buffer

-ErrorVariable

The -ErrorVariable common parameter captures the errors of a single command into a named variable, without affecting $Error:

Get-ChildItem C:\maybe -Recurse -ErrorAction SilentlyContinue -ErrorVariable scanErrors
if ($scanErrors) {
    $scanErrors | ForEach-Object {
        Write-Host "Could not scan: $($_.TargetObject)" -ForegroundColor Yellow
    }
}

Useful when you genuinely want to keep going on most failures but log the ones you saw.

The trap statement

trap is a script-level fallback. It catches any terminating error in the current scope and runs the trap body. Combined with continue or break you can decide whether to keep going or unwind:

trap {
    Write-Host "Trapped: $_" -ForegroundColor Red
    continue   # keep running the script
}

# Anywhere in this scope, terminating errors hit the trap
Get-Item C:\nope -ErrorAction Stop
Write-Host "still here"

In modern code, prefer try/catch for clarity. trap is occasionally useful as a script-wide safety net at the very top.

throw and Write-Error

To raise a terminating error from your own code, use throw:

throw "Cannot continue: configuration file is missing"

# Better - throw a typed exception
throw [System.IO.FileNotFoundException]::new("Config not found", $cfgPath)

For non-terminating errors (which a caller can choose to escalate with -ErrorAction Stop), use Write-Error:

Write-Error -Message "Skipped $name: not eligible" `
            -Category InvalidOperation `
            -ErrorId 'NotEligible' `
            -TargetObject $name

Real-world patterns

The script-top safety block

[CmdletBinding()]
param()

$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest

try {
    # Whole script body here
}
catch {
    Write-Error "FAILED: $($_.Exception.Message)" -ErrorAction Continue
    Write-Error "AT: $($_.ScriptStackTrace)" -ErrorAction Continue
    exit 1
}
finally {
    # Always-run cleanup
}

Per-item resilience

foreach ($item in $items) {
    try {
        Process-Item $item -ErrorAction Stop
    } catch {
        Write-Warning "Item $($item.Id) failed: $($_.Exception.Message)"
        $failed += $item
        continue
    }
}
Write-Host "$($failed.Count) of $($items.Count) failed"

Resource cleanup with finally

$conn = $null
try {
    $conn = [System.Data.SqlClient.SqlConnection]::new($cs)
    $conn.Open()
    # ... query ...
}
catch {
    Write-Error "DB error: $($_.Exception.Message)"
    throw
}
finally {
    if ($conn) { $conn.Dispose() }
}

Cheat sheet

Every flag, every variable, the patterns above on a single PDF: PowerShell Error Handling Cheat Sheet.

FAQ

Why does try/catch around Get-Item not catch errors?

Get-Item produces non-terminating errors by default. Add -ErrorAction Stop to promote them to terminating, or set $ErrorActionPreference = 'Stop' at the top of the script.

What is the difference between throw and Write-Error?

throw raises a terminating error (callable code can catch it). Write-Error writes to the error stream β€” non-terminating by default, escalatable to terminating by the caller via -ErrorAction Stop. Use throw when the error is fatal to your function; Write-Error when it is a recoverable issue the caller might choose to ignore.

Should I use Set-StrictMode?

Yes, in any script you maintain. Set-StrictMode -Version Latest turns silent typos and uninitialized variables into errors. Combined with $ErrorActionPreference = 'Stop' it eliminates most "why did this run successfully and produce nothing" surprises.

How do I see the full exception chain?

$_.Exception.InnerException walks the chain. For a deep dump: $_ | Format-List * -Force; $_.Exception | Format-List * -Force.

Does try/catch work across scopes (e.g. inside a function called from try)?

Yes. Terminating errors propagate up the call stack until caught.

What about non-cmdlet calls β€” native binaries?

Native commands (like git, kubectl) do not raise PowerShell errors at all β€” they set $LASTEXITCODE. Wrap with: git pull; if ($LASTEXITCODE -ne 0) { throw "git pull failed" }.

Can I rethrow with extra context?

Yes β€” throw "Wrapping: $($_.Exception.Message)" creates a new error with your message, or use $PSCmdlet.ThrowTerminatingError($_) from inside an advanced function to keep full context.

Related reading

Share this article:
Dargslan Editorial Team (Dargslan)
About the Author

Dargslan Editorial Team (Dargslan)

Collective of Software Developers, System Administrators, DevOps Engineers, and IT Authors

Dargslan is an independent technology publishing collective formed by experienced software developers, system administrators, and IT specialists.

The Dargslan editorial team works collaboratively to create practical, hands-on technology books focused on real-world use cases. Each publication is developed, reviewed, and...

Programming Languages Linux Administration Web Development Cybersecurity Networking

Stay Updated

Subscribe to our newsletter for the latest tutorials, tips, and exclusive offers.