aBmeSubscribe
PS-007·PS Track·Intermediate–Advanced·1–6 hrs saved

Turn "It Ran, But Did It Work?" into Automation That Fails Safely, Visibly, and On Purpose

An AI-assisted workflow to retrofit intentional error handling onto any PowerShell script — so recoverable failures recover, unrecoverable ones stop, and nothing ever reports success it didn't earn.

3Phases
10Prompts
1–6Hours saved
6Deliverables

Executive Brief

Your Challenge

Your script works flawlessly when the file exists, the server answers, and the token is valid. Then it runs unattended, hits a missing file or an expired credential, and does one of two equally bad things: it stops without telling you what failed, or it swallows the error and marches on in an inconsistent state. A scheduled task reports exit code 0 while half the users didn't get provisioned. You can't tell whether it's safe to rerun, whether anything changed before the failure, or whether the "success" you're looking at is real.

Common Obstacles

The usual fixes make it worse. Empty catch blocks hide the failure entirely. Broad catch-everything handling reports success after a required operation failed. try/catch gets added without -ErrorAction Stop, so non-terminating errors sail right past the block that was supposed to catch them. Retry loops repeat state-changing operations that create duplicate users or double-issued payments. Cleanup runs only on the success path, leaving sessions and locks behind whenever things go wrong. Each of these creates the appearance of resilience while making the automation less reliable.

The ABME Approach

This workflow does it in the right order: inventory every failure point, classify each one by type and recoverability, then decide the intentional response — continue, skip, retry, roll back, or terminate — before writing a single catch block. The prompt pack retrofits that decision into structured try/catch/finally with correct -ErrorAction Stop usage, bounded retries with backoff, per-object failure isolation, guaranteed cleanup, and consistent exit codes. A 24-point validation checklist and six test passes confirm the script fails predictably and never reports success it didn't earn.

Insight Summary

Reliable automation is not automation that never fails — it is automation that knows how to fail safely, visibly, and deliberately.
phase-1

Decide what should happen after each failure before writing the catch block. A catch block without a response decision is just a place where information goes to die.

phase-1

Error handling must reflect the operational consequences of each failure — retrying an invalid password five times does not improve reliability, it just delays the truth.

phase-2

A try/catch without -ErrorAction Stop is a trap: non-terminating cmdlets sail past the catch block, and the handler you wrote never runs.

phase-2

Never retry a state-changing operation unless repetition is provably safe — a retry that recreates a user or reissues a payment is a duplicate generator, not a recovery mechanism.

phase-3

Cleanup only on the success path is cleanup that never runs when you need it most; guaranteed paths like finally are the whole point.

tactical

Never report Succeeded when one or more mandatory operations failed — a scheduled task that returns exit code 0 after a critical failure is worse than one that crashes.

The Journey

Three phases; each lists the tools you'll use there.

1

Map and Classify Every Failure

Inventory the failure points, classify each by type and recoverability, and decide the intentional response before touching code.
  • Gather the script, its dependencies, and idempotency and rollback constraints
  • Identify every operation that can fail
  • Classify each failure by type using the classification model
  • Assign a continue, skip, retry, roll back, or terminate response to each
  • Note which operations are state-changing and unsafe to retry blindly
2

Retrofit Error Handling with AI

Use the prompt pack to add intentional try/catch/finally, correct -ErrorAction usage, bounded retries, and per-object isolation.
  • Run the primary prompt with the script and its intended behavior
  • Review the failure-point inventory and decision table before accepting code
  • Apply follow-up prompts for retry logic, isolation, cleanup, exit codes, and rollback
  • Preserve original error records and structured result objects
  • Confirm no secrets leak into error messages
3

Validate and Operationalize

Prove the script fails predictably, cleans up reliably, and never reports false success.
  • Run the 24-point validation checklist
  • Execute validation, dependency, permission, transient, state-change, and cleanup tests
  • Standardize exit codes and final statuses across the organization
  • Wire failure detection into scheduled and unattended execution

What's Inside the Execution Layer

Numbered deliverables grouped by phase. Membership unlocks every tool.

1. PHASE 1Checklistprotected

Prerequisites Checklist

Gather everything the AI needs to design correct error handling before the first prompt runs.
Use this to
  • Collect script context and dependencies before prompting
  • Surface idempotency and rollback constraints early
  • Identify known transient and permanent failures up front

Before using this workflow, gather:

2. PHASE 2Prompt Packprotected

PowerShell Error-Handling Prompt Pack

One primary prompt and nine targeted follow-ups that retrofit intentional error handling while preserving script behavior.
Use this to
  • Inventory and classify every failure point
  • Add bounded retries, isolation, cleanup, and rollback
  • Standardize exit codes and structured result objects

Primary Prompt

Start here with the full script and a description of its intended behavior.
You are a senior PowerShell automation engineer specializing in reliable enterprise automation.

I will provide a PowerShell script and a description of its intended behavior.

Your task is to identify every meaningful failure point and add robust, intentional error handling without changing the script's business purpose.

Before modifying the code:

1. Summarize the script's purpose and execution flow.

2. Identify every operation that can fail, including:
   • Parameter validation
   • File access
   • Module loading
   • Authentication
   • Authorization
   • Network communication
   • API calls
   • Remote execution
   • Data parsing
   • Data transformation
   • State-changing commands
   • Export or reporting
   • Cleanup
   • Notification

3. Classify each failure as:
   • Validation
   • Permission
   • Authentication
   • Dependency
   • Transient
   • Data
   • Logic
   • Partial completion
   • Unknown

4. For each failure, recommend one response:
   • Continue
   • Skip
   • Retry
   • Roll back
   • Terminate

5. Identify commands that produce non-terminating errors and may require:
   • -ErrorAction Stop
   • $ErrorActionPreference
   • Explicit result validation

6. Identify any catch blocks that:
   • Suppress useful details
   • Continue unsafely
   • Catch too broadly
   • Return misleading success
   • Fail to rethrow critical errors

7. Identify operations that are safe to retry.

8. Identify operations that are not safe to retry without idempotency protection.

9. Identify cleanup that must run regardless of success or failure.

10. Identify where partial completion must be tracked.

11. Identify rollback limitations.

12. Recommend final status values and exit codes.

Then revise the script using, where appropriate:
• Parameter validation
• Dependency checks
• try, catch, and finally blocks
• -ErrorAction Stop
• Specific exception handling
• Safe error messages
• Original error records
• Inner exception details where useful
• Retry limits
• Exponential backoff
• Jitter where appropriate
• Idempotency checks
• Per-object failure isolation
• Cleanup in finally blocks
• Partial-completion tracking
• Rollback attempts
• Meaningful terminating errors
• Consistent exit codes
• Structured result objects
• Integration with existing logging

Requirements:
• Preserve the original business behavior.
• Do not suppress errors merely to allow execution to continue.
• Do not retry authentication or permission failures without justification.
• Do not retry state-changing operations unless repetition is safe or protected.
• Do not expose passwords, tokens, secrets, or sensitive data in error messages.
• Do not replace useful PowerShell error records with generic text.
• Do not use empty catch blocks.
• Do not use catch blocks that report success after failure.
• Do not introduce global ErrorActionPreference changes without explaining their impact.
• Preserve public function output unless a change is explicitly documented.
• Clearly identify assumptions.
• Clearly identify any behavior that may change.

After the revised script, provide:
1. Failure-point inventory.
2. Error-handling decision table.
3. Retry policy.
4. Rollback and cleanup behavior.
5. Exit-code strategy.
6. Partial-completion behavior.
7. Sample failure output.
8. Testing recommendations.
9. Remaining risks.

Add Safe Retry Logic

For operations likely to experience transient failures.
Identify only the operations that are likely to experience transient failures and are safe to repeat.

Add bounded retry logic with:
• Maximum attempts
• Exponential backoff
• Optional jitter
• Retryable exception or status-code filtering
• Logging for each retry
• Immediate termination for permanent failures

Explain why each retried operation is safe to repeat.

Add Per-Object Failure Isolation

For batch-processing scripts where one object should not stop the rest.
Modify the batch-processing logic so one object failure does not terminate unrelated work.

For each object, record:
• Identifier
• Attempted operation
• Success or failure
• Safe error details
• Retry count
• Final disposition

Return a final status that distinguishes full success, partial success, and complete failure.

Preserve Original Error Records

When catch blocks discard diagnostic context.
Review the catch blocks and ensure the original PowerShell error record is preserved.

Include useful properties such as:
• Exception type
• Exception message
• FullyQualifiedErrorId
• CategoryInfo
• InvocationInfo
• ScriptStackTrace

Do not expose secrets or unnecessary sensitive data.

Add Cleanup with Finally

When resources must be released on every path.
Identify temporary files, sessions, connections, locks, mounts, transactions, or other resources that require cleanup.

Move cleanup into finally blocks where appropriate.

Ensure cleanup errors are reported without hiding the original failure.

Add Exit Codes

For unattended execution that needs distinguishable outcomes.
Design a consistent exit-code strategy for unattended execution.

Differentiate:
• Success
• Success with warnings
• Partial completion
• Validation failure
• Authentication or permission failure
• Dependency failure
• Unhandled failure

Ensure the script does not return exit code 0 after a critical failure.

Add Rollback Logic

When earlier changes must be reversible on later failure.
Identify which completed changes can be safely reversed if a later mandatory step fails.

Add rollback only where the reverse operation is known, safe, and testable.

Track rollback success and failure separately from the original operation failure.

Add Idempotency Checks

Before retrying any state-changing operation.
Review every state-changing operation and determine whether running it more than once could create duplicates, repeated notifications, conflicting settings, or other side effects.

Add precondition checks or idempotency protections where appropriate.

Replace Broad Catch Blocks

When generic catch blocks obscure specific failures.
Review each generic catch block.

Where practical, replace broad handling with specific exception types or explicit status-code handling.

Retain a final general catch for unexpected failures.

Add Structured Error Results

When callers need consistent per-operation result objects.
Create a consistent result object for each operation containing:
• Target
• Operation
• Status
• Attempt count
• Changed
• Error type
• Error message
• Timestamp
• Duration

Do not allow result objects to replace terminating errors when the entire workflow must stop.
3. PHASE 2Matrixprotected

Exit-Code Model

A standardized exit-code scheme so unattended runs and monitoring can distinguish outcomes consistently across every script.
Use this to
  • Standardize exit codes across the organization
  • Distinguish partial, validation, auth, and dependency failures
  • Ensure scheduled tasks never return 0 after a critical failure
Reference rows from the blueprint — downloads ship as an empty skeleton
Exit CodeMeaning
0Full success
1Unhandled or general failure
2Invalid input or configuration
3Dependency unavailable
4Authentication failure
5Authorization failure
6Partial completion
7Rollback failure
8Timeout
9Cleanup failure
10Success with warnings
RubricAn example exit-code model. Exit codes should be standardized across the organization. For reusable functions, prefer returning objects and throwing meaningful errors rather than calling exit; reserve exit for top-level scripts or process boundaries.
4. PHASE 2Matrixprotected

Final Status Model

A standardized set of final status values so a script's outcome is never ambiguous or falsely reported as success.
Use this to
  • Assign an accurate final status to every run
  • Distinguish partial success from full success and failure
  • Avoid reporting Succeeded when mandatory operations failed
Reference rows from the blueprint — downloads ship as an empty skeleton
Final Status
Succeeded
SucceededWithWarnings
PartiallySucceeded
Failed
RolledBack
RollbackFailed
Cancelled
RubricAvoid reporting Succeeded when one or more mandatory operations failed.
5. PHASE 3Checklistprotected

Validation Checklist

The 24-point acceptance gate an error-handling implementation must pass before it ships to production.
Use this to
  • Accept or reject the AI's error-handling implementation
  • Confirm behavior preservation and secret safety
  • Verify failures stay visible and status stays accurate

Before accepting the error-handling implementation:

6. PHASE 3Checklistprotected

Error-Handling Test Plan

Seven test passes — validation, dependency, permission, transient, state-change, cleanup, and Pester mocking — that prove failures are handled correctly.
Use this to
  • Test failure paths as thoroughly as success paths
  • Simulate transient, dependency, and permission failures
  • Confirm failed operations never report success

Test error handling independently across each failure category:

Validation Tests — test

Dependency Tests — simulate

Permission Tests — simulate

Transient Failure Tests — simulate

State-Change Tests — confirm

Cleanup Tests — verify cleanup after

Pester Mocking

🔒 The full execution layer — every checklist, matrix, and the prompt pack — is included with ABME membership.

Unlock Full Blueprint

Full Playbook

Overviewpublic

A PowerShell script may work reliably under ideal conditions while failing unpredictably when it encounters missing files, unavailable servers, expired credentials, malformed input, insufficient permissions, rate limits, or partial service outages.

Weak error handling often produces one of two outcomes:

  • The script stops without enough information to diagnose the failure.
  • The script suppresses the error and continues in an unsafe or inconsistent state.

This workflow uses AI to analyze an existing PowerShell script, identify its likely failure points, and add deliberate error-handling behavior. The resulting script should distinguish between recoverable and unrecoverable failures, provide useful diagnostic information, preserve meaningful PowerShell errors, and avoid leaving systems in an unknown state.

The goal is controlled failure instead of accidental failure.

Business Problempublic

PowerShell automation frequently contains inadequate error handling because:

  • Cmdlets emit non-terminating errors by default.
  • try and catch blocks are added without -ErrorAction Stop.
  • Catch blocks suppress useful exception details.
  • Scripts continue after critical operations fail.
  • Recoverable failures terminate the entire workflow.
  • Retry logic is missing or unsafe.
  • Cleanup runs only after successful execution.
  • Partial changes are not tracked.
  • Exit codes are inconsistent.
  • Scheduled tasks appear successful despite internal failures.
  • Errors are written to the wrong output stream.
  • Administrators cannot determine whether rerunning the script is safe.

Poor error handling can convert a small operational issue into incomplete provisioning, inconsistent configuration, duplicate changes, or data loss.

Typical Use Casespublic

Use this workflow when:

  • Preparing a script for unattended execution
  • Running automation through Scheduled Tasks
  • Building Azure Automation runbooks
  • Calling REST APIs
  • Processing large batches of users or devices
  • Managing Active Directory
  • Performing Microsoft 365 administration
  • Modifying Azure resources
  • Working with remote systems
  • Reading and writing files
  • Integrating with databases
  • Adding retry logic
  • Improving scripts that use empty catch blocks
  • Standardizing failure behavior across an IT team

Do NOT Use This Workflow Whenpublic

This workflow is not intended to:

  • Hide errors so a script appears successful
  • Retry every failure automatically
  • Continue after an unsafe or unknown failure
  • Replace validation
  • Replace logging
  • Guarantee transactional behavior across systems
  • Suppress security controls
  • Convert every error into a warning
  • Add complex recovery logic without business requirements
  • Certify a script as production-safe

Error handling must reflect the operational consequences of each failure.

Expected Outcomepublic

After completing this workflow, you should have:

  • A failure-point inventory
  • Errors classified by severity and recoverability
  • A clear continue, retry, skip, rollback, or terminate decision for each failure
  • Improved try, catch, and finally behavior
  • Appropriate use of -ErrorAction Stop
  • Input and dependency validation
  • Safe retry logic where justified
  • Meaningful error records
  • Consistent exit behavior
  • Cleanup that runs reliably
  • Partial-completion tracking
  • Recommendations for rollback and recovery
  • Tests for major failure scenarios

🔒 The complete playbook — reference models, worked examples, and operational guidance — is included with ABME membership.

Unlock Full Blueprint

Error-Handling Objectivesprotected

A production script should make it possible to answer:

  1. What failed?
  2. Why did it fail?
  3. Which operation was in progress?
  4. Which object or resource was affected?
  5. Did anything change before the failure?
  6. Can the script continue safely?
  7. Should the operation be retried?
  8. Is the failure transient or permanent?
  9. Is rollback required?
  10. Did cleanup complete?
  11. Is rerunning the script safe?
  12. What final status and exit code should be returned?

Failure Response Optionsprotected

Continue

Use when the failed operation is optional and does not affect the integrity of the remaining workflow.
  • Optional reporting endpoint is unavailable.

Skip

Use when one object cannot be processed but other objects can proceed independently.
  • One user record is malformed in a batch of 5,000 records.

Retry

Use when the failure is likely temporary and the operation is safe to repeat.
  • HTTP 429 rate limit
  • Temporary network timeout
  • Service unavailable response
  • Transient database connection failure

Roll Back

Use when earlier changes must be reversed to avoid an inconsistent state.
  • A user account was created, but mandatory group assignment failed.

Terminate

Use when continuing would be unsafe, misleading, or impossible.
  • Authentication fails
  • Required configuration is missing
  • Input integrity cannot be verified
  • A critical dependency is unavailable
  • The script cannot determine current state

Error Classificationprotected

Validation Error

The input does not meet required conditions. Usually: reject before beginning state-changing work.
  • Missing mandatory value
  • Invalid file path
  • Unsupported format
  • Value outside an approved range

Permission Error

The executing identity lacks required access. Usually: terminate or skip, depending on scope.
  • Access denied
  • Unauthorized API response
  • Insufficient role assignment
  • File permission failure

Authentication Error

The script cannot establish or maintain a valid authenticated session. Usually: terminate unless a safe reauthentication process exists.
  • Expired token
  • Invalid client secret
  • MFA requirement
  • Certificate failure

Dependency Error

A required module, service, endpoint, file, or command is unavailable. Usually: terminate for required dependencies; warn for optional dependencies.

Transient Error

A temporary condition may resolve on retry. Usually: retry with limits and backoff.
  • Timeout
  • Rate limit
  • Temporary service outage
  • Connection reset

Data Error

Input or returned data is missing, malformed, duplicated, or inconsistent. Usually: validate, skip, quarantine, or terminate depending on impact.

Logic Error

The script reaches an impossible, unsupported, or unhandled state. Usually: terminate and investigate.

Partial-Completion Error

Some operations succeeded before another failed. Usually: record completed changes, determine rollback needs, and return a distinct final status.

Example Inputprotected

param(
    [string]$ServerName,
    [string]$ServiceName
)

Restart-Service -InputObject (
    Get-Service -ComputerName $ServerName -Name $ServiceName
)

Write-Host "Service restarted."

Example Analysisprotected

Potential failure points include:

  1. ServerName is empty or invalid.
  2. ServiceName is empty or invalid.
  3. DNS resolution fails.
  4. The server is offline.
  5. The service does not exist.
  6. Remote service management is blocked.
  7. The executing identity lacks permission.
  8. The service cannot stop.
  9. The service cannot start.
  10. The service enters a pending state indefinitely.
  11. The command emits a non-terminating error.
  12. The success message is displayed even if the restart failed.

The operation should not be retried blindly because restarting a service is state-changing and may affect availability. A retry may be appropriate only after determining the current service state and confirming the previous attempt did not succeed.

Example Refactored Scriptprotected

[CmdletBinding()]
param(
    [Parameter(Mandatory)]
    [ValidateNotNullOrEmpty()]
    [string]$ServerName,
    [Parameter(Mandatory)]
    [ValidateNotNullOrEmpty()]
    [string]$ServiceName,
    [Parameter()]
    [ValidateRange(5, 300)]
    [int]$TimeoutSeconds = 60
)

$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()

try {
    $service = Get-Service `
        -ComputerName $ServerName `
        -Name $ServiceName `
        -ErrorAction Stop

    Restart-Service `
        -InputObject $service `
        -ErrorAction Stop

    $deadline = (Get-Date).AddSeconds($TimeoutSeconds)
    do {
        Start-Sleep -Seconds 2
        $service = Get-Service `
            -ComputerName $ServerName `
            -Name $ServiceName `
            -ErrorAction Stop
        if ($service.Status -eq 'Running') {
            break
        }
    }
    while ((Get-Date) -lt $deadline)

    if ($service.Status -ne 'Running') {
        throw [System.TimeoutException]::new(
            "Service '$ServiceName' on '$ServerName' did not reach the Running state within $TimeoutSeconds seconds."
        )
    }

    [pscustomobject]@{
        ServerName  = $ServerName
        ServiceName = $ServiceName
        Status      = 'Succeeded'
        FinalState  = $service.Status
        DurationMs  = $stopwatch.ElapsedMilliseconds
    }
}
catch [Microsoft.PowerShell.Commands.ServiceCommandException] {
    $message = "The service operation failed for '$ServiceName' on '$ServerName': $($_.Exception.Message)"
    Write-Error `
        -Message $message `
        -Exception $_.Exception `
        -Category OperationStopped `
        -ErrorId 'ServiceOperationFailed' `
        -TargetObject "$ServerName\$ServiceName"
    throw
}
catch [System.TimeoutException] {
    Write-Error `
        -Message $_.Exception.Message `
        -Exception $_.Exception `
        -Category OperationTimeout `
        -ErrorId 'ServiceRestartTimeout' `
        -TargetObject "$ServerName\$ServiceName"
    throw
}
catch {
    $message = "Unexpected failure while restarting '$ServiceName' on '$ServerName': $($_.Exception.Message)"
    Write-Error `
        -Message $message `
        -Exception $_.Exception `
        -Category NotSpecified `
        -ErrorId 'UnexpectedServiceRestartFailure' `
        -TargetObject "$ServerName\$ServiceName"
    throw
}
finally {
    $stopwatch.Stop()
}

Example Behavior Improvementsprotected

Original BehaviorRevised Behavior
Parameters accepted empty valuesMandatory non-empty validation
Non-terminating errors may continue-ErrorAction Stop enables catch handling
No final-state validationConfirms the service reaches Running
Success message always displayedSuccess object returned only after validation
All failures appear the sameKnown timeout and service errors are distinguished
No duration informationRuntime is measured
Error context may be lostOriginal exception is preserved
Failure may appear successfulError is rethrown

Retry Strategyprotected

Reasonable Retry Candidates

Retry logic should be based on failure type, not convenience.
  • HTTP 408
  • HTTP 429
  • HTTP 500
  • HTTP 502
  • HTTP 503
  • HTTP 504
  • Temporary DNS failures
  • Connection resets
  • Temporary database unavailability
  • Service-busy responses
  • Short-lived file locks

Usually Do Not Retry Automatically

  • HTTP 400
  • HTTP 401
  • HTTP 403
  • Invalid credentials
  • Missing mandatory input
  • Unsupported configuration
  • Malformed data
  • Insufficient permissions
  • Resource not found when it is required
  • Certificate trust failures
  • Business-rule rejection

State-Changing Operations

State-changing operations require additional caution. Before retrying, determine:
  • Whether the first attempt succeeded
  • Whether the operation is idempotent
  • Whether a duplicate would be harmful
  • Whether the system supports an idempotency key
  • Whether current state can be queried
  • Whether rollback is possible

Example Retry Functionprotected

function Invoke-WithRetry {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [scriptblock]$Operation,
        [Parameter()]
        [ValidateRange(1, 10)]
        [int]$MaximumAttempts = 3,
        [Parameter()]
        [ValidateRange(1, 300)]
        [int]$InitialDelaySeconds = 2,
        [Parameter()]
        [scriptblock]$ShouldRetry = { $true }
    )
    for ($attempt = 1; $attempt -le $MaximumAttempts; $attempt++) {
        try {
            return & $Operation
        }
        catch {
            $isRetryable = & $ShouldRetry $_
            if (-not $isRetryable -or $attempt -eq $MaximumAttempts) {
                throw
            }
            $baseDelay = $InitialDelaySeconds * [math]::Pow(2, $attempt - 1)
            $jitter = Get-Random -Minimum 0 -Maximum 1000
            $delayMilliseconds = ([int]($baseDelay * 1000)) + $jitter
            Write-Warning (
                "Attempt $attempt failed. Retrying in " +
                "$delayMilliseconds milliseconds. Error: $($_.Exception.Message)"
            )
            Start-Sleep -Milliseconds $delayMilliseconds
        }
    }
}

Usage should include a failure-specific retry decision:

$result = Invoke-WithRetry `
    -MaximumAttempts 4 `
    -Operation {
        Invoke-RestMethod `
            -Uri $Uri `
            -Method Get `
            -ErrorAction Stop
    } `
    -ShouldRetry {
        param($errorRecord)
        $statusCode = $errorRecord.Exception.Response.StatusCode.value__
        $statusCode -in 408, 429, 500, 502, 503, 504
    }

The exact exception properties vary by PowerShell version and HTTP implementation and must be validated in the target environment.

Error Record Guidanceprotected

A PowerShell error record may contain useful diagnostic data:

catch {
    $errorDetails = [ordered]@{
        ExceptionType          = $_.Exception.GetType().FullName
        ExceptionMessage       = $_.Exception.Message
        FullyQualifiedErrorId  = $_.FullyQualifiedErrorId
        Category               = $_.CategoryInfo.Category
        TargetName             = $_.CategoryInfo.TargetName
        TargetType             = $_.CategoryInfo.TargetType
        ScriptName             = $_.InvocationInfo.ScriptName
        ScriptLineNumber       = $_.InvocationInfo.ScriptLineNumber
        PositionMessage        = $_.InvocationInfo.PositionMessage
        ScriptStackTrace       = $_.ScriptStackTrace
    }
}

Do not assume every field is safe to log. Command lines, request data, paths, or invocation details may include sensitive values.

ErrorAction Guidanceprotected

-ErrorAction Stop — Use when a command failure must transfer control to a catch block.

Get-Item -LiteralPath $Path -ErrorAction Stop

$ErrorActionPreference = 'Stop' — This can simplify scripts but may change the behavior of every command in the current scope. Use only after reviewing:

  • Commands expected to emit recoverable errors
  • Third-party module behavior
  • Existing catch blocks
  • Functions that override preferences
  • Native command behavior

A scoped approach may be safer:

& {
    $ErrorActionPreference = 'Stop'
    # Commands within this scope
}

Even then, document the decision.

Explicit Result Validation — Some commands may not throw even when the outcome is unusable.

$result = Get-Something -ErrorAction Stop
if ($null -eq $result) {
    throw 'The command completed without returning the required result.'
}

Batch Processing Patternprotected

$results = foreach ($item in $items) {
    $startTime = Get-Date
    try {
        $operationResult = Invoke-ItemOperation `
            -Item $item `
            -ErrorAction Stop
        [pscustomobject]@{
            Target       = $item.Id
            Status       = 'Succeeded'
            Changed      = $true
            AttemptCount = 1
            ErrorType    = $null
            ErrorMessage = $null
            DurationMs   = ((Get-Date) - $startTime).TotalMilliseconds
        }
    }
    catch {
        [pscustomobject]@{
            Target       = $item.Id
            Status       = 'Failed'
            Changed      = $false
            AttemptCount = 1
            ErrorType    = $_.Exception.GetType().FullName
            ErrorMessage = $_.Exception.Message
            DurationMs   = ((Get-Date) - $startTime).TotalMilliseconds
        }
    }
}

After processing:

$successCount = @($results | Where-Object Status -eq 'Succeeded').Count
$failureCount = @($results | Where-Object Status -eq 'Failed').Count
$finalStatus = if ($failureCount -eq 0) {
    'Succeeded'
}
elseif ($successCount -eq 0) {
    'Failed'
}
else {
    'PartiallySucceeded'
}

Cleanup Patternprotected

$session = $null
$originalFailure = $null
try {
    $session = New-PSSession `
        -ComputerName $ComputerName `
        -ErrorAction Stop
    Invoke-Command `
        -Session $session `
        -ScriptBlock $Operation `
        -ErrorAction Stop
}
catch {
    $originalFailure = $_
    throw
}
finally {
    if ($session) {
        try {
            Remove-PSSession `
                -Session $session `
                -ErrorAction Stop
        }
        catch {
            Write-Warning "Session cleanup failed: $($_.Exception.Message)"
            if (-not $originalFailure) {
                throw
            }
        }
    }
}

Cleanup failure should not replace the original error unless cleanup itself is the only failure.

Exit-Code Strategyprotected

For reusable functions, prefer returning objects and throwing meaningful errors rather than calling exit. Reserve exit for top-level scripts or process boundaries. Exit codes should be standardized across the organization. See the Exit-Code Model tool for the reference scheme.

Security Considerationsprotected

Error messages and exception records may expose:

  • Usernames
  • Internal server names
  • File paths
  • Tenant identifiers
  • API endpoints
  • Query parameters
  • Tokens
  • Request bodies
  • Authentication headers
  • Connection strings
  • Customer data

Before logging or displaying an exception:

  • Review the message
  • Redact secrets
  • Avoid dumping entire request or response objects
  • Limit information returned to untrusted users
  • Preserve detailed records only in approved secure logs

Do not fix errors by weakening controls such as:

  • Certificate validation
  • MFA
  • Execution policies
  • Authentication requirements
  • Role assignments
  • Input validation
  • TLS requirements

Common Mistakesprotected

Catching Errors That Never Terminate

Weak:

try {
    Get-Item 'C:\MissingFile.txt'
}
catch {
    Write-Warning 'File was not found.'
}

The cmdlet may produce a non-terminating error, so the catch block may not run.

Better:

try {
    Get-Item 'C:\MissingFile.txt' -ErrorAction Stop
}
catch {
    Write-Warning "File access failed: $($_.Exception.Message)"
}

Empty Catch Blocks

Weak:

catch {}

This hides failure and makes troubleshooting difficult.

Catching and Continuing Automatically

Weak:

catch {
    Write-Warning 'Something failed.'
}
Write-Output 'Success'

The script reports success even though a required operation failed.

Generic Error Replacement

Weak:

catch {
    throw 'Failed.'
}

This discards important diagnostic context.

Better:

catch {
    throw "User provisioning failed for '$UserName': $($_.Exception.Message)"
}

Where possible, preserve the original exception as well.

Retrying Permanent Failures

Retrying an invalid password five times does not improve reliability. Classify the failure before retrying.

Infinite Retries

Every retry loop should have:

  • Maximum attempts
  • Maximum duration
  • Delay strategy
  • Final failure behavior

Retrying Non-Idempotent Operations

Repeatedly creating a user, issuing a payment, sending a message, or submitting a job may produce duplicates. Check state or use an idempotency mechanism first.

Global Error Preference Changes

Setting this at the top of an inherited script:

$ErrorActionPreference = 'SilentlyContinue'

can hide nearly every failure. Setting it to Stop can also alter behavior unexpectedly if the script was not designed for terminating errors.

Cleanup Only on Success

Resources should be cleaned up in finally blocks or other guaranteed paths.

Using exit Inside Reusable Functions

Calling exit inside a module function can terminate the caller's entire PowerShell process. Throw an error or return a structured result instead.

Automation Opportunitiesprotected

  • PowerShell script templates
  • Shared internal modules
  • Pull-request reviews
  • PSScriptAnalyzer custom rules
  • GitHub Actions
  • Azure DevOps pipelines
  • Azure Automation runbooks
  • Microsoft Intune scripts
  • RMM platforms
  • Scheduled Tasks
  • Operational readiness reviews
  • MSP automation standards
  • A mature PowerShell standard could require: input validation, dependency validation, explicit terminating-error handling, failure classification, bounded retries, idempotency checks, partial-completion tracking, cleanup guarantees, accurate final status, consistent exit codes, structured logging, and tested failure paths.

Pro Tipsprotected

  • Decide what should happen after each failure before writing the catch block.
  • Use -ErrorAction Stop intentionally rather than assuming every error is terminating.
  • Preserve original error records whenever possible.
  • Retry only failures that are both transient and safe to repeat.
  • Add jitter when many systems may retry the same service simultaneously.
  • Validate input and dependencies before making changes.
  • Track successful changes before a later failure occurs.
  • Distinguish full success, partial success, and complete failure.
  • Put cleanup in a path that runs after both success and failure.
  • Avoid exit inside reusable functions.
  • Test failure paths as thoroughly as success paths.
  • Ask whether rerunning the script is safe after every possible interruption.
  • Combine this workflow with PS-006 so every failure decision is observable.
  • Use PS-004 to create tests before modifying critical production scripts.

Brian Diamond

Founder, BrianOnAI

Twenty-five years designing, operating, and governing enterprise infrastructure — from MSP operations across dozens of client environments to enterprise infrastructure leadership. This blueprint codifies the operating model he's implemented in production, not theory.

⚠ Normalization Warnings — 13 for review

  • CLASSIFICATION TO CONFIRM: 'Prerequisites' classified as a checklist TOOL (gather-before-start items are completable). Alternative: body/prose. Mirrors PS-006 golden decision.
  • CLASSIFICATION TO CONFIRM: 'Testing Recommendations' classified as a checklist TOOL named 'Error-Handling Test Plan' (all items are verifiable/simulatable). Display name changed from source heading — confirm. The 'Pester Mocking Example' code block was summarized into the checklist rather than reproduced verbatim; the full Describe/It block lives in the source and could alternatively be split into a body/example. Confirm whether verbatim Pester code should be restored as a separate example section.
  • RESTRUCTURE: 'Primary Prompt' and 'Follow-Up Prompts' (two source H1s) combined into one prompt_pack tool with 10 prompts; 'when' guidance lines are editorial additions, prompt text verbatim.
  • CLASSIFICATION TO CONFIRM: 'Exit-Code Strategy' table extracted into a matrix TOOL ('Exit-Code Model') because it is a standardized reference scheme the practitioner copies/adapts; the surrounding prose (return objects vs. exit) kept as a short body/prose section. Alternative: keep entirely as body/reference.
  • CLASSIFICATION TO CONFIRM: 'Final Status Model' extracted into a matrix TOOL (single-column list of standardized status values the practitioner adopts). Alternative: body/reference. Single-column matrix is unusual — confirm or convert to reference tiers.
  • CLASSIFICATION: 'Failure Response Options' and 'Error Classification' classified as body/reference (consulted taxonomies with tiered definitions), not tools.
  • CLASSIFICATION: 'Retry Strategy' classified as body/reference (consulted retry-candidate taxonomy). Its subsections were flattened into reference tiers.
  • RESTRUCTURE: 'Common Mistakes' kept as a body GROUP (subsection-rich with code examples) rather than a playbook flat list; playbook.common_mistakes intentionally empty. Matches PS-006 golden treatment.
  • CLASSIFICATION: 'Security Considerations' kept as body/prose (single section, no code subsections) rather than a group; playbook.security_considerations intentionally empty.
  • AUTOMATION OPPORTUNITIES: the 'mature PowerShell standard' ordered list appended as a final combined item to preserve it; consider splitting into a separate reference section on review.
  • PS-007 has no Quick Wins or Roadmap sections (unlike ARC/SEC docs); playbook.quick_wins and roadmap intentionally empty.
  • Example code blocks preserved verbatim; the Primary Prompt source text ran together without line breaks in extraction — reconstructed with the numbered/bulleted structure evident in the content. Prompt wording unchanged.
  • Related Workflows mapped to slugs ps-001 through ps-010 excluding self (ps-007).

SEO Block

  • Title tag: Add Robust Error Handling to PowerShell Scripts | ABME (54 chars)
  • Meta: Retrofit intentional error handling onto any PowerShell script — failure classification, bounded retries, cleanup, and exit codes that never lie about success. (159 chars)
  • Schema: HowTo · noindex: false
  • Related: ps-001, ps-002, ps-003, ps-004, ps-005, ps-006, ps-008, ps-009, ps-010
  • Keywords: powershell error handling, powershell try catch finally, erroraction stop, powershell retry logic, exponential backoff powershell, powershell exit codes, idempotency powershell, non-terminating errors, powershell cleanup finally, unattended script error handling
Copied