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.
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.
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.
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.
A try/catch without -ErrorAction Stop is a trap: non-terminating cmdlets sail past the catch block, and the handler you wrote never runs.
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.
Cleanup only on the success path is cleanup that never runs when you need it most; guaranteed paths like finally are the whole point.
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.
Map and Classify Every Failure
- 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
Retrofit Error Handling with AI
- 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
Validate and Operationalize
- 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.
Prerequisites Checklist
- 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:
PowerShell Error-Handling Prompt Pack
- 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.
Exit-Code Model
- Standardize exit codes across the organization
- Distinguish partial, validation, auth, and dependency failures
- Ensure scheduled tasks never return 0 after a critical failure
| Exit Code | Meaning |
|---|---|
| 0 | Full success |
| 1 | Unhandled or general failure |
| 2 | Invalid input or configuration |
| 3 | Dependency unavailable |
| 4 | Authentication failure |
| 5 | Authorization failure |
| 6 | Partial completion |
| 7 | Rollback failure |
| 8 | Timeout |
| 9 | Cleanup failure |
| 10 | Success with warnings |
Final Status Model
- Assign an accurate final status to every run
- Distinguish partial success from full success and failure
- Avoid reporting Succeeded when mandatory operations failed
| Final Status |
|---|
| Succeeded |
| SucceededWithWarnings |
| PartiallySucceeded |
| Failed |
| RolledBack |
| RollbackFailed |
| Cancelled |
Validation Checklist
- 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:
Error-Handling Test Plan
- 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 BlueprintFull 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 BlueprintError-Handling Objectivesprotected
A production script should make it possible to answer:
- What failed?
- Why did it fail?
- Which operation was in progress?
- Which object or resource was affected?
- Did anything change before the failure?
- Can the script continue safely?
- Should the operation be retried?
- Is the failure transient or permanent?
- Is rollback required?
- Did cleanup complete?
- Is rerunning the script safe?
- What final status and exit code should be returned?
Failure Response Optionsprotected
Continue
- Optional reporting endpoint is unavailable.
Skip
- One user record is malformed in a batch of 5,000 records.
Retry
- HTTP 429 rate limit
- Temporary network timeout
- Service unavailable response
- Transient database connection failure
Roll Back
- A user account was created, but mandatory group assignment failed.
Terminate
- 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
- Missing mandatory value
- Invalid file path
- Unsupported format
- Value outside an approved range
Permission Error
- Access denied
- Unauthorized API response
- Insufficient role assignment
- File permission failure
Authentication Error
- Expired token
- Invalid client secret
- MFA requirement
- Certificate failure
Dependency Error
Transient Error
- Timeout
- Rate limit
- Temporary service outage
- Connection reset
Data Error
Logic Error
Partial-Completion Error
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:
- ServerName is empty or invalid.
- ServiceName is empty or invalid.
- DNS resolution fails.
- The server is offline.
- The service does not exist.
- Remote service management is blocked.
- The executing identity lacks permission.
- The service cannot stop.
- The service cannot start.
- The service enters a pending state indefinitely.
- The command emits a non-terminating error.
- 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 Behavior | Revised Behavior |
|---|---|
| Parameters accepted empty values | Mandatory non-empty validation |
| Non-terminating errors may continue | -ErrorAction Stop enables catch handling |
| No final-state validation | Confirms the service reaches Running |
| Success message always displayed | Success object returned only after validation |
| All failures appear the same | Known timeout and service errors are distinguished |
| No duration information | Runtime is measured |
| Error context may be lost | Original exception is preserved |
| Failure may appear successful | Error is rethrown |
Retry Strategyprotected
Reasonable Retry Candidates
- 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
- 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.
Related Blueprints
⚠ 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
