Skip to content

Error Handling (Try-Catch-Finally)

Note

Learn how to handle errors gracefully using try/catch/finally blocks and error handling best practices.

Overview

Error handling lets your scripts respond to problems instead of crashing. Without error handling, a single failure can stop your entire script. With proper error handling, you can catch errors, log them, retry operations, or gracefully exit while cleaning up resources.

Basic Syntax

try {
    # Code that might fail
    Get-Item "C:\DoesNotExist.txt"
}
catch {
    # Code that runs if an error occurs
    Write-Output "Error: $($_.Exception.Message)"
}
finally {
    # Code that ALWAYS runs (error or not)
    # Good for cleanup: closing files, connections, etc.
    Write-Output "Cleanup complete"
}

Key Points

  • try block contains code that might fail
  • catch block runs only if an error occurs in the try block
  • finally block ALWAYS runs, whether there was an error or not
  • Not all errors are catchable by default (see ErrorAction)
  • Use $_ or $PSItem in catch block to access error details

Error Types

Terminating vs Non-Terminating Errors

# NON-TERMINATING ERROR (by default)
# Shows error but continues running
Get-Item "C:\DoesNotExist.txt"
Write-Output "Script continues..."  # This still runs

# TERMINATING ERROR
# Stops execution - can be caught
try {
    Get-Item "C:\DoesNotExist.txt" -ErrorAction Stop  # Force terminating
    Write-Output "This won't run"
}
catch {
    Write-Output "Caught the error!"
}

Making Errors Catchable

# Won't catch - non-terminating error
try {
    Get-Item "C:\DoesNotExist.txt"  # Error shown but not caught
}
catch {
    Write-Output "This won't run"
}

# WILL catch - forced to terminating
try {
    Get-Item "C:\DoesNotExist.txt" -ErrorAction Stop
}
catch {
    Write-Output "Caught it!"  # This runs
}

# Alternative: Set preference for all commands
$ErrorActionPreference = "Stop"
try {
    Get-Item "C:\DoesNotExist.txt"  # Now catchable
}
catch {
    Write-Output "Caught it!"
}

Basic Error Handling

Simple Try-Catch

try {
    $content = Get-Content -Path "C:\config.txt" -ErrorAction Stop
    Write-Output "File loaded successfully"
}
catch {
    Write-Output "Failed to load file: $($_.Exception.Message)"
}

Try-Catch-Finally

$file = $null
try {
    $file = [System.IO.File]::Open("C:\data.txt", "Open")
    # Do something with file
    Write-Output "Processing file..."
}
catch {
    Write-Error "Failed to process file: $_"
}
finally {
    # Always clean up, even if error occurred
    if ($null -ne $file) {
        $file.Close()
        Write-Output "File closed"
    }
}

Accessing Error Information

Error Object Properties

try {
    Get-Item "C:\DoesNotExist.txt" -ErrorAction Stop
}
catch {
    # $_ or $PSItem contains the error

    Write-Output "Error Message: $($_.Exception.Message)"
    Write-Output "Error Type: $($_.Exception.GetType().FullName)"
    Write-Output "Failed Item: $($_.TargetObject)"
    Write-Output "Script Line: $($_.InvocationInfo.ScriptLineNumber)"
    Write-Output "Line Content: $($_.InvocationInfo.Line)"

    # Full error details
    Write-Output "`nFull Error:"
    Write-Output $_ | Format-List * -Force
}

Output Example:

Error Message: Cannot find path 'C:\DoesNotExist.txt'
Error Type: System.Management.Automation.ItemNotFoundException
Failed Item: C:\DoesNotExist.txt
Script Line: 2
Line Content:     Get-Item "C:\DoesNotExist.txt" -ErrorAction Stop

Catching Specific Errors

Catch Different Error Types

try {
    $number = [int]"NotANumber"  # Type conversion error
}
catch [System.InvalidCastException] {
    Write-Output "Can't convert to number"
}
catch [System.IO.FileNotFoundException] {
    Write-Output "File not found"
}
catch {
    Write-Output "Some other error: $_"
}

Real-World Specific Catches

try {
    $user = Get-ADUser -Identity "nonexistent" -ErrorAction Stop
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
    Write-Output "User not found in Active Directory"
    # Maybe create the user?
}
catch [System.UnauthorizedAccessException] {
    Write-Output "Insufficient permissions to query AD"
}
catch {
    Write-Output "Unexpected error: $($_.Exception.GetType().Name)"
    Write-Output $_.Exception.Message
}

Common Use Cases

Use Case 1: File Operations with Error Handling

What it does: Safely read a file with fallback options

function Get-ConfigFile {
    param(
        [string]$Path = "C:\config.txt",
        [string]$BackupPath = "C:\config.backup.txt"
    )

    try {
        $content = Get-Content -Path $Path -ErrorAction Stop
        Write-Output "Loaded config from primary location"
        return $content
    }
    catch [System.IO.FileNotFoundException] {
        Write-Warning "Primary config not found, trying backup..."

        try {
            $content = Get-Content -Path $BackupPath -ErrorAction Stop
            Write-Output "Loaded config from backup location"
            return $content
        }
        catch {
            Write-Error "No config file found. Using defaults."
            return @("default=value")
        }
    }
    catch {
        Write-Error "Unexpected error loading config: $_"
        throw  # Re-throw to caller
    }
}

Use Case 2: Network Operations with Retry

What it does: Retry failed network operations

function Test-ServerConnection {
    param(
        [string]$ComputerName,
        [int]$MaxRetries = 3
    )

    $attempt = 0
    $success = $false

    while ($attempt -lt $MaxRetries -and -not $success) {
        $attempt++

        try {
            Write-Output "Attempt $attempt of $MaxRetries..."

            $result = Test-Connection -ComputerName $ComputerName -Count 1 -ErrorAction Stop

            Write-Output "Connection successful!"
            $success = $true
            return $true
        }
        catch {
            Write-Warning "Attempt $attempt failed: $($_.Exception.Message)"

            if ($attempt -lt $MaxRetries) {
                Write-Output "Waiting 5 seconds before retry..."
                Start-Sleep -Seconds 5
            }
        }
    }

    if (-not $success) {
        Write-Error "Failed to connect after $MaxRetries attempts"
        return $false
    }
}

Use Case 3: Database Connection Cleanup

What it does: Ensure database connections are always closed

function Invoke-DatabaseQuery {
    param(
        [string]$ConnectionString,
        [string]$Query
    )

    $connection = $null
    $command = $null

    try {
        # Open connection
        $connection = New-Object System.Data.SqlClient.SqlConnection($ConnectionString)
        $connection.Open()
        Write-Output "Database connection opened"

        # Execute query
        $command = $connection.CreateCommand()
        $command.CommandText = $Query
        $result = $command.ExecuteScalar()

        Write-Output "Query executed successfully"
        return $result
    }
    catch [System.Data.SqlClient.SqlException] {
        Write-Error "Database error: $($_.Exception.Message)"
        throw
    }
    catch {
        Write-Error "Unexpected error: $_"
        throw
    }
    finally {
        # Always cleanup, even if error occurred
        if ($null -ne $command) {
            $command.Dispose()
        }

        if ($null -ne $connection) {
            if ($connection.State -eq 'Open') {
                $connection.Close()
                Write-Output "Database connection closed"
            }
            $connection.Dispose()
        }
    }
}

Error Actions

ErrorAction Parameter

# SilentlyContinue - Suppress error, continue
Get-Item "C:\NoFile.txt" -ErrorAction SilentlyContinue
Write-Output "Script continues"

# Stop - Make it a terminating error (catchable)
try {
    Get-Item "C:\NoFile.txt" -ErrorAction Stop
}
catch {
    Write-Output "Caught!"
}

# Continue - Show error, continue (default)
Get-Item "C:\NoFile.txt" -ErrorAction Continue
Write-Output "Keeps running"

# Inquire - Ask user what to do
Get-Item "C:\NoFile.txt" -ErrorAction Inquire

# Ignore - Completely ignore (not even added to $Error)
Get-Item "C:\NoFile.txt" -ErrorAction Ignore

ErrorActionPreference Variable

# Set for entire script/session
$ErrorActionPreference = "Stop"  # All errors are now terminating

try {
    Get-Item "C:\NoFile.txt"  # Now catchable without -ErrorAction
}
catch {
    Write-Output "Caught without -ErrorAction Stop"
}

# Reset to default
$ErrorActionPreference = "Continue"

The $Error Variable

# $Error is an array of all errors in the session
$Error.Clear()  # Clear error history

Get-Item "C:\DoesNotExist.txt" -ErrorAction SilentlyContinue
Get-Process "NonExistentProcess" -ErrorAction SilentlyContinue

# View all errors
$Error.Count  # 2

# Most recent error
$Error[0]

# View all errors with details
$Error | ForEach-Object {
    Write-Output "Error: $($_.Exception.Message)"
}

# Clear errors
$Error.Clear()

Error Codes & Categories Reference

Exit Codes

Exit codes are returned when scripts complete or exit. Use exit <code> to return a specific code, and $LASTEXITCODE to check the result.

Exit Code Meaning Usage
0 Success Script completed without errors
1 General error Catch-all for non-specific errors
2 Misuse of shell command Invalid arguments or syntax
126 Command cannot execute Permission problem or command is not executable
127 Command not found Path issue or typo in command name
128 Invalid exit argument Exit code must be 0-255
130 Script terminated by Ctrl+C User interrupted execution
255 Exit status out of range Exit code outside 0-255 range

Example:

# Return exit code
if (Test-Path "C:\file.txt") {
    Write-Output "File exists"
    exit 0
} else {
    Write-Error "File not found"
    exit 1
}

# Check exit code from external program
& "C:\App\program.exe"
if ($LASTEXITCODE -eq 0) {
    Write-Output "Program succeeded"
} else {
    Write-Error "Program failed with code: $LASTEXITCODE"
}

Error Categories

PowerShell categorizes errors to help identify the type of failure. Access via $_.CategoryInfo.Category.

Category Description Common Scenarios
NotSpecified General/uncategorized error Default when no specific category applies
OpenError Failed to open resource File, network share, or database connection
CloseError Failed to close resource Cannot properly close file or connection
DeviceError Device-related failure Hardware, printer, or removable media issues
ReadError Failed to read data Cannot read file, registry, or stream
WriteError Failed to write data Cannot write to file, disk full, read-only
ResourceExists Resource already exists File exists when creating, duplicate key
ResourceUnavailable Cannot access resource File locked, service stopped, network down
InvalidOperation Operation not valid in current state Wrong order, precondition not met
InvalidData Data validation failed Corrupted file, invalid format, type mismatch
InvalidArgument Bad parameter value Out of range, wrong type, null when required
PermissionDenied Insufficient permissions Access denied, need elevation
ObjectNotFound Requested object doesn't exist File, process, user, registry key not found
ConnectionError Network/connection problem Timeout, refused, host unreachable
AuthenticationError Authentication failed Bad credentials, expired token

Example:

try {
    Get-Item "C:\NoFile.txt" -ErrorAction Stop
}
catch {
    Write-Output "Error Category: $($_.CategoryInfo.Category)"
    # Output: ObjectNotFound

    # Handle based on category
    switch ($_.CategoryInfo.Category) {
        "ObjectNotFound" { Write-Output "Resource doesn't exist" }
        "PermissionDenied" { Write-Output "Need admin rights" }
        "InvalidArgument" { Write-Output "Check your parameters" }
        default { Write-Output "Other error type" }
    }
}

Common Exception Types

These are the most frequently encountered .NET exception types when working with PowerShell.

Exception Type Description Common Causes
System.Management.Automation.ItemNotFoundException Item not found File, registry key, or path doesn't exist
System.Management.Automation.CommandNotFoundException Cmdlet/command not found Typo, module not loaded, path issue
System.Management.Automation.ParameterBindingException Parameter binding failed Wrong parameter type, missing required param
System.Management.Automation.RuntimeException General PowerShell runtime error Various runtime failures
System.IO.FileNotFoundException File not found Specified file doesn't exist
System.IO.DirectoryNotFoundException Directory not found Specified folder doesn't exist
System.IO.IOException I/O operation failed File locked, disk full, network share issue
System.UnauthorizedAccessException Access denied Insufficient permissions, need elevation
System.InvalidOperationException Operation not valid Wrong state, precondition not met
System.ArgumentException Invalid argument Bad parameter value
System.ArgumentNullException Required argument is null Missing required value
System.FormatException Format/parsing error Cannot convert string to number/date
System.InvalidCastException Type conversion failed Cannot cast type A to type B
System.Net.WebException Web request failed HTTP error, timeout, network issue
System.TimeoutException Operation timed out Long-running operation exceeded limit

Example:

try {
    $number = [int]"NotANumber"
}
catch [System.InvalidCastException] {
    Write-Output "Cannot convert to integer"
}
catch [System.FormatException] {
    Write-Output "Invalid number format"
}

# Inspect exception type
try {
    Get-Item "C:\NoFile.txt" -ErrorAction Stop
}
catch {
    $exceptionType = $_.Exception.GetType().FullName
    Write-Output "Exception Type: $exceptionType"
    # Output: System.Management.Automation.ItemNotFoundException
}

Real-World Examples

Example: Robust File Copy with Logging

Scenario: Copy files with comprehensive error handling and logging

function Copy-FileWithRetry {
param(
    [string]$Source,
    [string]$Destination,
    [int]$MaxRetries = 3
)

# Validate source exists
if (-not (Test-Path $Source)) {
    throw "Source file not found: $Source"
}

$attempt = 0
$success = $false

while ($attempt -lt $MaxRetries -and -not $success) {
    $attempt++

    try {
        Write-Output "[$attempt/$MaxRetries] Copying $Source to $Destination..."

        Copy-Item -Path $Source -Destination $Destination -ErrorAction Stop -Force

        # Verify copy
        if (Test-Path $Destination) {
            $sourceHash = (Get-FileHash -Path $Source).Hash
            $destHash = (Get-FileHash -Path $Destination).Hash

            if ($sourceHash -eq $destHash) {
                Write-Output "Copy successful and verified"
                $success = $true
            } else {
                throw "File copied but hash mismatch"
            }
        }
    }
    catch [System.IO.IOException] {
        Write-Warning "IO Error on attempt $attempt : $($_.Exception.Message)"

        if ($attempt -lt $MaxRetries) {
            Start-Sleep -Seconds ([math]::Pow(2, $attempt))
        }
    }
    catch [System.UnauthorizedAccessException] {
        Write-Error "Access denied: $($_.Exception.Message)"
        break  # Don't retry permission errors
    }
    catch {
        Write-Warning "Unexpected error on attempt $attempt : $_"

        if ($attempt -lt $MaxRetries) {
            Start-Sleep -Seconds 3
        }
    }
}

if (-not $success) {
    throw "Failed to copy file after $MaxRetries attempts"
}

return $true
}

Example: Service Management with Error Handling

Scenario: Start a service with comprehensive error handling

function Start-ServiceSafely {
param(
    [string]$ServiceName,
    [int]$TimeoutSeconds = 30
)

try {
    # Check if service exists
    $service = Get-Service -Name $ServiceName -ErrorAction Stop

    if ($service.Status -eq "Running") {
        Write-Output "$ServiceName is already running"
        return $true
    }

    Write-Output "Starting $ServiceName..."
    Start-Service -Name $ServiceName -ErrorAction Stop

    # Wait for service to start
    $service.WaitForStatus("Running", [TimeSpan]::FromSeconds($TimeoutSeconds))

    Write-Output "$ServiceName started successfully"
    return $true
}
catch [Microsoft.PowerShell.Commands.ServiceCommandException] {
    Write-Error "Service error: $($_.Exception.Message)"

    # Check if it's a dependency issue
    if ($_.Exception.Message -like "*dependencies*") {
        Write-Output "Checking service dependencies..."
        $service = Get-Service -Name $ServiceName
        $dependencies = $service.ServicesDependedOn

        foreach ($dep in $dependencies) {
            Write-Output "Dependency: $($dep.Name) - Status: $($dep.Status)"
        }
    }
    return $false
}
catch [System.TimeoutException] {
    Write-Error "Service did not start within $TimeoutSeconds seconds"
    return $false
}
catch {
    Write-Error "Unexpected error starting service: $_"
    return $false
}
}

Tips & Tricks

Always Use -ErrorAction Stop for Catchable Errors

# Won't catch
try {
Get-Item "C:\NoFile.txt"
} catch {
# Doesn't run
}

# Will catch
try {
Get-Item "C:\NoFile.txt" -ErrorAction Stop
} catch {
# Runs!
}

Use Finally for Cleanup

# Good pattern for resource cleanup
try {
$stream = [System.IO.File]::OpenRead("C:\file.txt")
# Work with stream
}
catch {
Write-Error "Error: $_"
}
finally {
if ($stream) {
    $stream.Dispose()  # Always cleanup
}
}

Re-throw Errors When Appropriate

function Get-Data {
try {
    # Something that might fail
    Get-Item "C:\data.txt" -ErrorAction Stop
}
catch {
    Write-Error "Failed to get data: $_"
    throw  # Re-throw to let caller handle it
}
}

# Caller can now handle it
try {
Get-Data
}
catch {
Write-Output "Caller caught re-thrown error"
}

Don't Catch Everything Silently

# BAD: Hides all errors
try {
# Complex code
}
catch {
# Silent failure - no logging, no nothing
}

# GOOD: At least log it
try {
# Complex code
}
catch {
Write-Error "Error occurred: $_"
# Maybe log to file
Add-Content -Path "C:\Logs\errors.log" -Value "$(Get-Date): $_"
throw  # Re-throw if caller needs to know
}

Watch Out for Non-Terminating Errors

# These won't be caught without -ErrorAction Stop
try {
Get-Process "NoSuchProcess"  # Non-terminating
Get-Item "C:\NoFile.txt"     # Non-terminating
Write-Error "Custom error"   # Non-terminating
}
catch {
# Doesn't run!
}

# Fix: Use -ErrorAction Stop
try {
Get-Process "NoSuchProcess" -ErrorAction Stop
Get-Item "C:\NoFile.txt" -ErrorAction Stop
Write-Error "Custom error" -ErrorAction Stop
}
catch {
# Now it runs
}

$ErrorActionPreference Scope

# Setting this affects EVERYTHING
$ErrorActionPreference = "Stop"

# Now ALL cmdlets behave differently
Get-Item "C:\NoFile.txt"  # Now throws, was non-terminating

# Be careful - might affect modules/functions you call
# Better to use -ErrorAction on individual cmdlets

Additional Resources