Edit

Share via


about_Error_Handling

Short description

Describes the types of errors in PowerShell and the mechanisms for handling them.

Long description

PowerShell distinguishes three categories of errors:

  • Non-terminating errors
  • Statement-terminating errors
  • Script-terminating errors

Understanding the distinction is essential for writing reliable scripts and modules, because each category has different default behavior and requires different handling techniques.

Additionally, external (native) programs report failure through exit codes, which PowerShell tracks separately from its own error system.

Types of errors

Non-terminating errors

A non-terminating error reports a problem but doesn't stop the pipeline. The command continues processing subsequent input objects. Non-terminating errors are generated by:

  • The Write-Error cmdlet
  • The $PSCmdlet.WriteError() method in advanced functions
  • Cmdlets that encounter recoverable failures on individual input objects

By default, PowerShell displays the error message and continues execution.

# Non-terminating error: the pipeline continues after the failure
'file1.txt', 'noSuchFile.txt', 'file3.txt' | ForEach-Object {
    Get-Content $_ -ErrorAction Continue
}

In this example, Get-Content reports a non-terminating error for noSuchFile.txt and then continues processing file3.txt.

Non-terminating errors do not trigger catch or trap by default.

Statement-terminating errors

A statement-terminating error stops the current statement (pipeline) from running, but execution continues at the next statement in the script. Statement-terminating errors are generated by:

  • The $PSCmdlet.ThrowTerminatingError() method in advanced functions and compiled cmdlets
  • Engine errors such as CommandNotFoundException (calling a command that doesn't exist) and ParameterBindingException (invalid parameter arguments)
  • .NET method calls that throw exceptions, such as [int]::Parse('abc')
# Statement-terminating error: Get-Item fails, but the next statement runs
Get-Item -Path 'C:\NoSuchFile.txt'
Write-Output 'This still runs'

Statement-terminating errors can be caught by try/catch and trap.

Note

.ThrowTerminatingError() doesn't consult the -ErrorAction parameter (except for the Break value, which enters the debugger). However, $ErrorActionPreference does apply to statement-terminating errors through the engine's statement-level handler. For example, $ErrorActionPreference = 'SilentlyContinue' can suppress a statement-terminating error so that the script continues at the next statement. The -ErrorAction parameter can't do this. For details, see The $ErrorActionPreference asymmetry.

Script-terminating errors

A script-terminating error unwinds the entire call stack. Execution stops completely unless the error is caught by a try/catch block or trap statement. Script-terminating errors are generated by:

  • The throw keyword
  • Parse errors (syntax errors that prevent the script from being compiled)
  • Non-terminating errors escalated by -ErrorAction Stop or $ErrorActionPreference = 'Stop' in non-advanced contexts. For more information, see How escalation works.
  • Certain critical engine failures
# Script-terminating error: throw unwinds the call stack
function Test-Throw {
    throw 'Critical failure'
    Write-Output 'This never runs'
}

Test-Throw
Write-Output 'This never runs either (unless caught)'

The throw keyword generates a script-terminating error by default. However, $ErrorActionPreference can suppress throw when set to SilentlyContinue or Ignore. When calling an advanced function with -ErrorAction SilentlyContinue, the parameter translates to a scope-local $ErrorActionPreference value, so it also suppresses throw inside that function.

Note

Even with $ErrorActionPreference = 'Ignore', a throw that's suppressed still records an entry in $Error. The Ignore value only prevents $Error recording for non-terminating errors.

Important

The terms statement-terminating and script-terminating describe the scope of impact, not the severity of the error. A statement-terminating error stops one statement. A script-terminating error stops the entire script and its callers. Both can be caught by try/catch.

External program errors

External (native) programs don't participate in PowerShell's error system directly. They report failure through a non-zero exit code, which PowerShell stores in the $LASTEXITCODE automatic variable.

git clone https://example.com/nonexistent.git 2>$null
if ($LASTEXITCODE -ne 0) {
    Write-Error "git failed with exit code $LASTEXITCODE"
}

By default, a non-zero exit code from a native program:

  • Sets $? to $false
  • Does not generate an ErrorRecord in $Error
  • Does not trigger catch or trap

PowerShell 7.3 added the experimental preference variable $PSNativeCommandUseErrorActionPreference, which became a stable feature in 7.4. When you set this variable to $true, it causes a non-zero exit code to emit a non-terminating error whose message states the specific exit code (a NativeCommandExitException). This error respects $ErrorActionPreference, so setting it to Stop promotes the error to a script-terminating error that can be caught with try/catch.

Error state variables

PowerShell maintains several automatic variables that reflect the current error state.

$?

Contains $true if the last operation succeeded and $false if it produced any error (non-terminating or terminating). For native commands, $? is set based on the exit code: $true for exit code 0, $false otherwise.

Get-Item -Path 'C:\NoSuchFile.txt' 2>$null
$?  # False

$Error

An ArrayList that stores the most recent error records, with the most recent error at index 0. The list holds up to $MaximumErrorCount entries (default 256).

All terminating errors are added to $Error. For terminating errors, Ignore suppresses display but still records the error in $Error. All non-terminating are added to $Error unless -ErrorAction Ignore is used on non-terminating errors, which prevents both display and recording.

$LASTEXITCODE

Contains the exit code of the last native program that ran. A value of 0 conventionally indicates success. Any non-zero value indicates failure. This variable isn't affected by PowerShell cmdlet errors.

Control error behavior

The -ErrorAction common parameter

The -ErrorAction common parameter overrides $ErrorActionPreference for a single command. It controls how PowerShell responds to non-terminating errors from that command.

Value Behavior
Continue Display the error and continue (default)
SilentlyContinue Suppress display, add to $Error, continue
Ignore Suppress display and don't add to $Error
Stop Escalate to a terminating error (see How escalation works)
Inquire Prompt the user for a decision
Break Enter the debugger

-ErrorAction doesn't change the behavior of errors generated by $PSCmdlet.ThrowTerminatingError(). Those errors are always statement-terminating regardless of the caller's preference.

The $ErrorActionPreference variable

The $ErrorActionPreference preference variable applies to all commands in the current scope and child scopes. It accepts the same values as -ErrorAction.

$ErrorActionPreference = 'Stop'
# All non-terminating errors in this scope now become terminating
Write-Error 'This now throws'   # Generates ActionPreferenceStopException

When -ErrorAction is specified on a command, it takes precedence over $ErrorActionPreference for that command.

How escalation works

When -ErrorAction Stop or $ErrorActionPreference = 'Stop' is in effect, PowerShell converts non-terminating errors into terminating errors using the following mechanism:

  1. A cmdlet calls WriteError() internally to emit a non-terminating error.
  2. The engine checks the effective ErrorAction preference for the command.
  3. Because the preference is Stop, the engine creates an ActionPreferenceStopException that wraps the original error record.
  4. If caught by catch, the original error information is accessible through $_.Exception.ErrorRecord.

The scope of the escalated error depends on context:

  • In non-advanced scripts, functions, or script blocks, setting $ErrorActionPreference = 'Stop' escalates to a script-terminating error. The error propagates up the call stack.
  • In advanced functions and script blocks (those with [CmdletBinding()]), the error remains statement-terminating. Execution continues at the next statement after the call.
  • Passing -ErrorAction Stop to an advanced function has the same effect as setting $ErrorActionPreference = 'Stop' inside it, because -ErrorAction translates to a scope-local $ErrorActionPreference value.

Escalation examples

  • NON-advanced: script-terminating ('after' does NOT print)

    & {
        param()
        $ErrorActionPreference = 'Stop'
        Get-Item 'NoSuchPath'
    } 2>$null
    'after'
    
  • ADVANCED: statement-terminating ('after' DOES print)

    & {
        [CmdletBinding()]
        param()
        $ErrorActionPreference = 'Stop'; Get-Item 'NoSuchPath'
    } 2>$null
    'after'
    
  • Without -ErrorAction Stop: non-terminating, catch doesn't run

    try {
        Write-Error 'This is non-terminating'
        Write-Output 'Execution continues'
    } catch {
        Write-Output "Caught: $_"   # Not reached
    }
    
  • With -ErrorAction Stop: escalated to terminating

    try {
        Write-Error 'This becomes terminating' -ErrorAction Stop
    } catch {
        Write-Output "Caught: $_"   # Reached
    }
    

Escalated errors can be caught by their original exception type. The engine unwraps the ActionPreferenceStopException to find the underlying exception:

try {
    Get-Item -Path 'C:\NoSuchFile.txt' -ErrorAction Stop
} catch [System.Management.Automation.ItemNotFoundException] {
    Write-Output "File not found: $($_.Exception.Message)"
}

The $ErrorActionPreference asymmetry

The -ErrorAction parameter and the $ErrorActionPreference variable behave differently with terminating errors. It's important to understand this asymmetry:

  • -ErrorAction only affects non-terminating errors. When a cmdlet calls $PSCmdlet.ThrowTerminatingError(), the -ErrorAction parameter is ignored (except for Break, which enters the debugger). The error is always thrown.

  • $ErrorActionPreference affects both non-terminating and statement-terminating errors. The engine's statement-level error handler reads $ErrorActionPreference (not the -ErrorAction parameter) and can suppress a statement-terminating error when the value is SilentlyContinue or Ignore.

function Test-Asymmetry {
    [CmdletBinding()]
    param()
    $er = [System.Management.Automation.ErrorRecord]::new(
        [System.InvalidOperationException]::new('test error'),
        'TestError',
        [System.Management.Automation.ErrorCategory]::InvalidOperation,
        $null
    )
    $PSCmdlet.ThrowTerminatingError($er)
}

# -ErrorAction SilentlyContinue does NOT suppress the error:
Test-Asymmetry -ErrorAction SilentlyContinue   # Error is still thrown

# $ErrorActionPreference DOES suppress the error:
$ErrorActionPreference = 'SilentlyContinue'
Test-Asymmetry   # Error is silently suppressed, script continues
$ErrorActionPreference = 'Continue'

Important

$ErrorActionPreference can't suppress errors that have SuppressPromptInInterpreter set to true. These always propagate regardless of the preference variable. Examples of this type of error include:

  • ActionPreferenceStopException from -ErrorAction Stop escalation
  • Errors inside PowerShell class methods
  • PipelineStoppedException

Handle errors

try/catch/finally

Use try/catch/finally to handle statement-terminating and script-terminating errors. When an error occurs inside a try block, PowerShell searches for a matching catch block. The finally block always runs, whether or not an error occurred.

try {
    $result = Get-Content -Path 'data.txt' -ErrorAction Stop
}
catch [System.Management.Automation.ItemNotFoundException] {
    Write-Warning 'Data file not found, using defaults.'
    $result = 'default'
}
catch {
    Write-Warning "Unexpected error: $_"
}
finally {
    Write-Verbose 'Cleanup complete.' -Verbose
}

Inside a try block, the engine sets an internal flag that causes non- terminating errors escalated by -ErrorAction Stop or $ErrorActionPreference = 'Stop' to propagate to the catch block. This is designed behavior, not a special case.

For full syntax details, see about_Try_Catch_Finally.

trap

The trap statement handles terminating errors at the scope level. When an error occurs anywhere in the enclosing scope, the trap block runs.

  • Default (no break or continue): The error is displayed and execution continues at the next statement after the one that caused the error.
  • continue in the trap: Suppresses the error message and resumes at the next statement.
  • break in the trap: The error propagates to the parent scope.
trap [System.Management.Automation.CommandNotFoundException] {
    Write-Warning "Command not found: $($_.TargetObject)"
    continue
}

NonsenseCommand   # Trap fires, execution continues
Write-Output 'This runs because the trap used continue'

For full syntax details, see about_Trap.

Reporting errors in functions and scripts

When writing functions and scripts, choose the error-reporting mechanism that matches the severity of the failure.

Non-terminating - use Write-Error

Use Write-Error when the function can continue processing other input. This is appropriate for pipeline functions that process multiple objects and encounter failures on individual items.

function Test-Path-Safe {
    [CmdletBinding()]
    param([Parameter(ValueFromPipeline)][string]$Path)
    process {
        if (-not (Test-Path $Path)) {
            Write-Error "Path not found: $Path"
            return
        }
        $Path
    }
}

Note

In advanced functions (those with [CmdletBinding()]), use $PSCmdlet.WriteError() instead of Write-Error to ensure that $? is properly set to $false in the caller's scope. The Write-Error cmdlet doesn't always set $? correctly.

Statement-terminating - use $PSCmdlet.ThrowTerminatingError()

Use $PSCmdlet.ThrowTerminatingError() when the function can't continue at all but the caller should decide how to handle the failure. This is the recommended approach in advanced functions.

function Get-Config {
  [CmdletBinding()]
  param([string]$Path)

  if (-not (Test-Path $Path)) {
    $er = [System.Management.Automation.ErrorRecord]::new(
      [System.IO.FileNotFoundException]::new("Config file not found: $Path"),
      'ConfigNotFound',
      [System.Management.Automation.ErrorCategory]::ObjectNotFound,
      $Path
    )
    $PSCmdlet.ThrowTerminatingError($er)
  }

  Get-Content $Path | ConvertFrom-Json
}

After the error leaves the function, the caller treats it as a non-terminating error by default. The caller can escalate it with -ErrorAction Stop.

Script-terminating - use throw

Use throw when recovery isn't possible and the entire script should stop.

$config = Get-Content 'config.json' -ErrorAction SilentlyContinue |
    ConvertFrom-Json

if (-not $config) {
    throw 'Cannot proceed without a valid configuration file.'
}

Which mechanism to use

  • When processing multiple inputs where some may fail, use Write-Error or $PSCmdlet.WriteError().
  • If the function can't continue, use $PSCmdlet.ThrowTerminatingError() and let the caller decide how to handle it.
  • If the entire script must stop immediately, use throw.

Summary of error types

The following tables summarize the properties and behaviors of the different error types in PowerShell.

Non-terminating error

Non-terminating errors can be generated by Write-Error or $PSCmdlet.WriteError().

Attribute Description
Scope of impact Pipeline continues
Caught by catch No (unless escalated)
Caught by trap No (unless escalated)
Added to $Error Yes (unless Ignore)
Sets $? to $false Yes
Affected by -ErrorAction Yes
Affected by $ErrorActionPreference Yes

Statement-terminating error

Statement-terminating errors can be generated by ThrowTerminatingError(), engine errors, .NET method exceptions, or -ErrorAction Stop in advanced contexts.

Attribute Description
Scope of impact Current statement stops; script continues
Caught by catch Yes
Caught by trap Yes
Added to $Error Yes
Sets $? to $false Yes
Affected by -ErrorAction No (Break only)
Affected by $ErrorActionPreference Yes (can suppress)

Script-terminating error

Script-terminating errors can be generated by throw, parse errors, or -ErrorAction Stop in non-advanced contexts.

Attribute Description
Scope of impact Call stack unwinds
Caught by catch Yes
Caught by trap Yes
Added to $Error Yes
Sets $? to $false Yes
Affected by -ErrorAction No
Affected by $ErrorActionPreference throw: Yes (can suppress)
Affected by $ErrorActionPreference Escalated: depends on context

See also