PowerShell Try-Catch: Handle Errors Gracefully

PowerShell Try-Catch: Handle Errors Gracefully

PowerShell Tips Editor 4 min read
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 Stop to 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.

Send-Item -To