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:
- A cmdlet encounters an error.
- PowerShell writes the error to the Error stream (Stream 2).
- Execution continues with the next command.
- This is the default behavior for many cmdlets.
Terminating Errors:
- An error occurs during execution.
- PowerShell immediately stops the current operation.
- If the error is inside a
tryblock, thecatchblock can handle it. - Otherwise, execution may stop entirely.
Some examples:
Write-Output "Step 1: Commencing process query..."
Get-Process -Name "FakeProcess_12345"
Write-Output "Step 2: Script execution continued successfully."pwsh ./error-behavior-demo.ps1Step 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
try {
Get-Item -Path "C:\MissingFile.txt" -ErrorAction Stop
Write-Output "File retrieved successfully."
}
catch {
Write-Warning "Caught trapped error: $($_.Exception.Message)"
}where:
-ErrorAction StopforcesGet-Itemto throw a terminating exception when the path is missing.catchintercepts the error and executes the recovery block.Write-Output "File retrieved..."is skipped.
pwsh ./force-terminating-error.ps1WARNING: 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.
$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.
try {
[int]"not-a-number"
}
catch [System.FormatException] {
Write-Output "Formatting problem: could not convert value."
}
catch {
Write-Output "Unexpected error: $($_.Exception.Message)"
}./specific-catch.ps1Unexpected 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
catchblock 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:
throw "Disk utilization exceeded critical threshold (95%)."
throw [System.IO.DirectoryNotFoundException]::new("Target repository not found.")Exception: Disk utilization exceeded critical threshold (95%).When PowerShell encounters throw, execution stops immediately unless an enclosing try/catch intercepts the exception.
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)"
}./throw-example.ps1Validation failed: Age cannot be negative: -5The $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.
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().Namereveals the specific .NET exception class.$Error[0].InvocationInfoisolates execution context, identifying the calling cmdlet and line number.
pwsh ./inspect-error-history.ps1ExceptionType ErrorMessage FailedCommand LineNumber
------------- ------------ ------------- ----------
ItemNotFoundException Cannot find path 'C:\DoesNotExist.log' because it does not exist. Get-Item 1To clear historical errors from the current session buffer:
$Error.Clear()
$Error.Count0Practical Examples
Example 1: Robust File Processing
Combining these concepts produces a script that handles failures gracefully instead of crashing outright.
$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"ensuresGet-Contentfailures become terminating and are caught bycatch.- Each failure is logged to a file rather than halting the entire batch.
finallyguarantees a status message prints for every file, regardless of success or failure.
./process-files.ps1Processed 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: 1Example 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:
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 Stopguarantees that failures inNew-Item,Get-Content, andCopy-Itemimmediately jump to thecatchblocks.catch [System.IO.FileNotFoundException]catches missing files specifically, separating them from permission errors.finallyensures 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:
pwsh ./archive-configuration.ps1 -SourcePath "C:\Missing\appsettings.json"Initiating safe archival for: C:\Missing\appsettings.json
WARNING: File Missing Error: Specified configuration file was not found.
Executing finally cleanup block...