PowerShell Try-Catch: Handle Errors Gracefully

An unhandled error in the middle of a script doesn’t just fail — it leaves things in an unknown state. Files half-copied, services half-started, databases half-migrated. PowerShell try catch blocks are insurance against this: they intercept errors, let you log them, clean up gracefully, and either recover or exit clearly. This guide covers the full try/catch/finally syntax, the critical difference between terminating and non-terminating errors, and patterns for real-world error handling.
Quick Answer / TL;DR
try {
Get-Item 'C:\Missing\file.txt' -ErrorAction Stop
} catch {
Write-Error "Failed: $($_.Exception.Message)"
}
Try-Catch Basic Syntax
The try block contains the code that might fail. The catch block runs if an error is thrown:
try {
$content = Get-Content 'C:\Logs\app.log' -ErrorAction Stop
Write-Output "Read $($content.Count) lines"
} catch {
Write-Error "Failed to read file: $($_.Exception.Message)"
}
# Basic catch with error info
try {
Remove-Item 'C:\NonExistent\path\file.txt' -ErrorAction Stop
} catch {
Write-Warning "Caught: $($_.Exception.GetType().Name)"
Write-Warning "Message: $($_.Exception.Message)"
}
Caught: DirectoryNotFoundException
Message: Could not find a part of the path 'C:\NonExistent\path\file.txt'.
Inside the catch block, $_ is the error record. Access the exception with $_.Exception and its message with $_.Exception.Message. The error type is in $_.Exception.GetType().FullName.
Terminating vs Non-Terminating Errors
This is the most important concept for try/catch in PowerShell. By default, many cmdlets emit non-terminating errors — they display the error but don’t stop execution, and they don’t trigger catch blocks:
# Non-terminating error — catch block does NOT fire
try {
Get-Item 'C:\MissingFile.txt' # Displays error but doesn't throw
Write-Output 'This still runs' # Executes after the error
} catch {
Write-Output 'This never runs' # NOT triggered
}
# To make the error terminating, add -ErrorAction Stop
try {
Get-Item 'C:\MissingFile.txt' -ErrorAction Stop
Write-Output 'This does NOT run after a Stop error'
} catch {
Write-Output 'Now catch fires' # This runs
}
Get-Item: Cannot find path 'C:\MissingFile.txt' because it does not exist.
This still runs
Now catch fires
Remember: -ErrorAction Stop is required on non-terminating cmdlets if you want catch to work. Without it, the error is recorded but execution continues past the try block without entering catch.
Accessing Error Details with $_.Exception
try {
$connection = [System.Net.Sockets.TcpClient]::new('db01.corp.local', 5432)
} catch {
$ex = $_.Exception
Write-Output "Exception type: $($ex.GetType().FullName)"
Write-Output "Message: $($ex.Message)"
Write-Output "Inner exception: $($ex.InnerException?.Message)"
Write-Output "Stack trace:"
Write-Output $ex.StackTrace
# Log to file
$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
"[$timestamp] ERROR: $($ex.Message)" | Add-Content 'C:\Logs\errors.log'
}
Exception type: System.Net.Sockets.SocketException
Message: No connection could be made because the target machine actively refused it
Inner exception:
Using Finally for Cleanup
The finally block always runs — whether the try succeeded, failed, or was caught. Use it for cleanup:
$stream = $null
try {
$stream = [System.IO.FileStream]::new('C:\Logs\app.log', 'Open')
# ... process the stream
Write-Output "File processed"
} catch {
Write-Error "Error: $($_.Exception.Message)"
} finally {
# Always runs — even if catch ran
if ($null -ne $stream) {
$stream.Close()
$stream.Dispose()
Write-Output "Stream closed"
}
}
File processed
Stream closed
Catching Specific Exception Types
Use typed catch blocks to handle different errors differently:
try {
Invoke-WebRequest 'https://api.example.com/data' -ErrorAction Stop
} catch [System.Net.WebException] {
Write-Warning "Network error: $($_.Exception.Message)"
} catch [System.UnauthorizedAccessException] {
Write-Error "Access denied — check credentials"
} catch {
# Generic catch — handles anything not matched above
Write-Error "Unexpected error: $($_.Exception.GetType().FullName) — $($_.Exception.Message)"
}
More specific catch blocks must come before the generic catch block. PowerShell evaluates catch blocks top to bottom and uses the first match.
Re-Throwing Errors
In some cases, you want to log the error and then let it propagate up to a higher-level handler:
function Deploy-Application {
[CmdletBinding()]
param([string] $PackagePath)
try {
if (-not (Test-Path $PackagePath)) {
throw [System.IO.FileNotFoundException]::new("Package not found: $PackagePath")
}
# ... deployment logic
} catch {
Write-Error "Deployment failed: $($_.Exception.Message)"
throw # Re-throw the same error to the caller
}
}
try {
Deploy-Application -PackagePath 'C:\Missing\app.msi'
} catch {
Write-Output "Outer handler: $($_.Exception.Message)"
}
Common Errors and Fixes
-
Non-terminating errors bypass catch — use -ErrorAction Stop: The most common try/catch mistake in PowerShell. Add
-ErrorAction Stopto every cmdlet inside a try block that you want catch to intercept, or set$ErrorActionPreference = 'Stop'at the top of the script. -
Empty catch block swallows errors silently: A bare
catch { }with no body suppresses the error completely — you’ll never know it happened. Always at minimum log the error:catch { Write-Warning $_.Exception.Message }.
Related Cmdlets / See Also
Wrapping Up
Try/catch is the foundation of robust PowerShell scripts. The critical rule: add -ErrorAction Stop to cmdlets whose errors you want to catch — without it, non-terminating errors bypass catch silently. Use finally for guaranteed cleanup. Catch specific exception types for granular handling. Your next step: find the first script you wrote that has no error handling and add try/catch around its most likely failure points.


