100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Error Handling with Try/Catch

Understand PowerShell's terminating vs non-terminating errors and how try/catch/finally, -ErrorAction, and $ErrorActionPreference work together to handle failures gracefully.

Control Flow & FunctionsIntermediate10 min readJul 10, 2026
Analogies

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.

powershell
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

Was this page helpful?

Topics covered

#Programming#PowerShellStudyNotes#ErrorHandlingWithTryCatch#Error#Handling#Try#Catch#ErrorHandling#StudyNotes#SkillVeris