Skip to content

PowerShell Error Handling

How PowerShell's error handling model distinguishes between terminating and non-terminating errors, uses try/catch/finally blocks, and controls error behavior with -ErrorAction and $ErrorActionPreference for resilient automation.

Error handling allows a script to detect, respond to, and recover from failures gracefully, rather than crashing unexpectedly or silently producing incorrect results. PowerShell's error handling model is built on .NET exceptions, combined with a unique distinction between terminating and non-terminating errors. Errors are structured .NET objects rather than raw exit codes. Understanding these concepts, controlling error behavior with -ErrorAction, and writing structured try/catch/finally blocks enables you to build resilient, fault-tolerant automation.

Terminating vs. Non-Terminating Errors

PowerShell divides all runtime errors into two fundamental categories:

Error Type Behavior Example
Terminating Immediately stops execution of the current statement, function, or script (unless caught) Invalid cast, division by zero, throw statement
Non-Terminating Written to the error stream, but execution continues to the next statement File not found in Get-ChildItem, Get-Service on a non-existent service

Non-Terminating Errors:

  1. A cmdlet encounters an error.
  2. PowerShell writes the error to the Error stream (Stream 2).
  3. Execution continues with the next command.
  4. This is the default behavior for many cmdlets.

Terminating Errors:

  1. An error occurs during execution.
  2. PowerShell immediately stops the current operation.
  3. If the error is inside a try block, the catch block can handle it.
  4. Otherwise, execution may stop entirely.

Some examples:

error-behavior-demo.ps1powershell
Write-Output "Step 1: Commencing process query..."

Get-Process -Name "FakeProcess_12345"

Write-Output "Step 2: Script execution continued successfully."
Consolepowershell
pwsh ./error-behavior-demo.ps1
Outputplaintext
Step 1: Commencing process query...
Get-Process: C:\Users\Tom\Desktop\error-behavior-demo.ps1:3
Line |
   3 |  Get-Process -Name "FakeProcess_12345"
     |  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Cannot find a process with the name "FakeProcess_12345". Verify the process name and call the cmdlet again.
Step 2: Script execution continued successfully.

Controlling Error Actions: -ErrorAction and $ErrorActionPreference

You can change how PowerShell reacts to non-terminating errors globally using the $ErrorActionPreference variable, or individually on a per-cmdlet basis using the -ErrorAction common parameter (alias: -ea).

Supported Action Settings

Setting Behavior
Continue (Default) Emits the error message to Stream 2 and continues script execution.
Stop Promotes the non-terminating error into a terminating error, halting execution or triggering a catch block.
SilentlyContinue Suppresses the error message and continues execution without writing to the console. The error is still recorded in $Error.
Ignore Suppresses the error message, continues execution, and does not record the error in $Error.
Inquire Displays the error and prompts the user whether to continue, halt, or suspend execution.

Upgrading Non-Terminating Errors with -ErrorAction Stop

force-terminating-error.ps1powershell
try {
    Get-Item -Path "C:\MissingFile.txt" -ErrorAction Stop
    Write-Output "File retrieved successfully."
}
catch {
    Write-Warning "Caught trapped error: $($_.Exception.Message)"
}

where:

  • -ErrorAction Stop forces Get-Item to throw a terminating exception when the path is missing.
  • catch intercepts the error and executes the recovery block.
  • Write-Output "File retrieved..." is skipped.
Consolepowershell
pwsh ./force-terminating-error.ps1
Outputplaintext
WARNING: Caught trapped error: Cannot find path 'C:\MissingFile.txt' because it does not exist.

Setting a Global Default with $ErrorActionPreference

Rather than specifying -ErrorAction Stop on every single cmdlet, you can set the $ErrorActionPreference variable to change the default behavior for the entire script.

Consolepowershell
$ErrorActionPreference = "Stop"

Structured Exception Handling: Try, Catch, and Finally

Structured error handling in PowerShell uses try, catch, and finally blocks to intercept terminating errors and perform cleanup.

Syntax Layout

try {
} catch [System.IO.FileNotFoundException] {
} catch {
} finally {
}

The Catch Variable: $_

Inside a catch block, the automatic variable $_ (or $PSItem) contains the current System.Management.Automation.ErrorRecord object. You can access properties on this object to inspect the root cause:

  • $_.Exception.Message: The plain-text error message.
  • $_.Exception.GetType().FullName: The exact underlying .NET exception class name.
  • $_.InvocationInfo.Line: The exact script line that triggered the exception.
  • $_.InvocationInfo.ScriptLineNumber: The line number where the failure occurred.

Catching Specific Exception Types

You can chain multiple catch blocks to handle different exception types differently.

specific-catch.ps1powershell
try {
    [int]"not-a-number"
}
catch [System.FormatException] {
    Write-Output "Formatting problem: could not convert value."
}
catch {
    Write-Output "Unexpected error: $($_.Exception.Message)"
}
Consolepowershell
./specific-catch.ps1
Outputplaintext
Unexpected error: Cannot convert value "not-a-number" to type "System.Int32". Error: "Input string was not in a correct format."

where:

  • catch [System.FormatException] only handles that specific exception type.
  • The final, untyped catch block acts as a fallback for any other exception type.

The Role of the Finally Block

The finally block is guaranteed to run after the try and catch blocks complete, even if the script encountered an unhandled exception or called exit / return. It is used for releasing external resources:

  • Closing open file streams.
  • Disconnecting remote database or network sessions.
  • Deleting temporary staging folders.

Raising Custom Errors with throw

The throw statement creates a terminating error on demand. You can pass a string message, a custom error object, or a specific .NET exception class:

Consolepowershell
throw "Disk utilization exceeded critical threshold (95%)."

throw [System.IO.DirectoryNotFoundException]::new("Target repository not found.")
Outputplaintext
Exception: Disk utilization exceeded critical threshold (95%).

When PowerShell encounters throw, execution stops immediately unless an enclosing try/catch intercepts the exception.

throw-example.ps1powershell
function Test-Age {
    param([int]$Age)

    if ($Age -lt 0) {
        throw "Age cannot be negative: $Age"
    }

    return "Age is valid: $Age"
}

try {
    Test-Age -Age -5
}
catch {
    Write-Output "Validation failed: $($_.Exception.Message)"
}
Consolepowershell
./throw-example.ps1
Outputplaintext
Validation failed: Age cannot be negative: -5

The $Error Automatic Variable

PowerShell automatically logs every error that occurs during a session into the $Error collection.

$Error is an in-memory array (ArrayList) that acts as a circular buffer, storing up to $MaximumErrorCount items (default: 512).

  • $Error[0]: Represents the most recent error generated in the session.
  • $Error[1]: Represents the second most recent error.
inspect-error-history.ps1powershell
Get-Item "C:\DoesNotExist.log" -ErrorAction SilentlyContinue

[PSCustomObject]@{
    ExceptionType   = $Error[0].Exception.GetType().Name
    ErrorMessage    = $Error[0].Exception.Message
    FailedCommand   = $Error[0].InvocationInfo.MyCommand.Name
    LineNumber      = $Error[0].InvocationInfo.ScriptLineNumber
}

where:

  • $Error[0].Exception.GetType().Name reveals the specific .NET exception class.
  • $Error[0].InvocationInfo isolates execution context, identifying the calling cmdlet and line number.
Consolepowershell
pwsh ./inspect-error-history.ps1
Outputplaintext
ExceptionType         ErrorMessage                                                      FailedCommand LineNumber
-------------         ------------                                                      ------------- ----------
ItemNotFoundException Cannot find path 'C:\DoesNotExist.log' because it does not exist. Get-Item               1

To clear historical errors from the current session buffer:

Consolepowershell
$Error.Clear()
$Error.Count
Outputplaintext
0

Practical Examples

Example 1: Robust File Processing

Combining these concepts produces a script that handles failures gracefully instead of crashing outright.

process-files.ps1powershell
$ErrorActionPreference = "Stop"
$logPath = "C:\Logs\process-errors.log"

$files = @("C:\Data\report1.csv", "C:\Data\missing.csv", "C:\Data\report2.csv")

foreach ($file in $files) {
    try {
        $content = Get-Content -Path $file
        Write-Output "Processed $file successfully ($($content.Count) lines)."
    }
    catch {
        $errorMessage = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - Failed to process $file : $($_.Exception.Message)"
        Add-Content -Path $logPath -Value $errorMessage
        Write-Warning "Skipped $file due to an error. See log for details."
    }
    finally {
        Write-Output "Finished attempt for $file."
    }
}

Write-Output "Batch processing complete. Total errors this session: $($Error.Count)"

where:

  • $ErrorActionPreference = "Stop" ensures Get-Content failures become terminating and are caught by catch.
  • Each failure is logged to a file rather than halting the entire batch.
  • finally guarantees a status message prints for every file, regardless of success or failure.
Consolepowershell
./process-files.ps1
Outputplaintext
Processed C:\Data\report1.csv successfully (150 lines).
Finished attempt for C:\Data\report1.csv.
WARNING: Skipped C:\Data\missing.csv due to an error. See log for details.
Finished attempt for C:\Data\missing.csv.
Processed C:\Data\report2.csv successfully (200 lines).
Finished attempt for C:\Data\report2.csv.
Batch processing complete. Total errors this session: 1

Example 2: Resilient File Processing Engine

The following production script safely reads and archives configuration files. It uses -ErrorAction Stop, specific typed catch blocks, generic fallback handlers, throw statements, and guaranteed cleanup using finally:

archive-configuration.ps1powershell
param (
    [string]$SourcePath = "$PSScriptRoot\settings.json",
    [string]$BackupDir  = "$env:TEMP\BackupStaging"
)

$StagingCreated = $false

try {
    Write-Output "Initiating safe archival for: $SourcePath"

    if (-not (Test-Path -Path $SourcePath -PathType Leaf)) {
        throw [System.IO.FileNotFoundException]::new("Specified configuration file was not found.", $SourcePath)
    }

    if (-not (Test-Path -Path $BackupDir)) {
        $null = New-Item -ItemType Directory -Path $BackupDir -ErrorAction Stop
        $StagingCreated = $true
    }

    $Content = Get-Content -Path $SourcePath -Raw -ErrorAction Stop

    $Timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
    $DestinationFile = Join-Path -Path $BackupDir -ChildPath "settings_$Timestamp.bak"
    Copy-Item -Path $SourcePath -Destination $DestinationFile -ErrorAction Stop

    [PSCustomObject]@{
        Status          = "Success"
        OriginalFile    = $SourcePath
        ArchiveTarget   = $DestinationFile
        BytesTransferred = (Get-Item -Path $DestinationFile).Length
    }
}
catch [System.IO.FileNotFoundException] {
    Write-Warning "File Missing Error: $($_.Exception.Message)"
}
catch [System.UnauthorizedAccessException] {
    Write-Error "Access Denied: You do not have permissions to read the file or write to staging."
}
catch {
    Write-Error "Unexpected fatal failure: $($_.Exception.Message)"
}
finally {
    Write-Output "Executing finally cleanup block..."
    if ($StagingCreated -and (Test-Path -Path $BackupDir)) {
        Write-Output "Cleanup: Removing temporary directory $BackupDir"
        Remove-Item -Path $BackupDir -Recurse -Force -ErrorAction SilentlyContinue
    }
}

where:

  • throw [System.IO.FileNotFoundException]::new(...) triggers a specific typed exception when input is invalid.
  • -ErrorAction Stop guarantees that failures in New-Item, Get-Content, and Copy-Item immediately jump to the catch blocks.
  • catch [System.IO.FileNotFoundException] catches missing files specifically, separating them from permission errors.
  • finally ensures that any temporary staging directory created during the process is deleted, even if the copy operation fails halfway through.

Run the script against a non-existent file:

Consolepowershell
pwsh ./archive-configuration.ps1 -SourcePath "C:\Missing\appsettings.json"
Outputplaintext
Initiating safe archival for: C:\Missing\appsettings.json
WARNING: File Missing Error: Specified configuration file was not found.
Executing finally cleanup block...
Navigation