Skip to content

Output and error handling#

Output streams#

Practice: Write-Output for return values. Never Write-Host. Use Write-Verbose, Write-Warning, Write-Error, Write-Information for their respective purposes.

Why: Each stream has one job (Single Responsibility). Write-Host bypasses the pipeline, breaks testing, and behaves differently in CI than locally (Build for all developers).

How:

# Good
Write-Verbose "Processing $($Data.Count) items"
Write-Output $result
# Bad
Write-Host 'Processing data...'          # Bypasses pipeline
Write-Host $result                       # Untestable

Suppressing output#

Practice: $null = for cmdlets. [void] for .NET method calls. Never | Out-Null.

Why: | Out-Null adds a pipeline stage — measurably slower in loops. $null = is resolved at parse time with no runtime cost (Build for the modern engineer).

How:

# Good
$null = New-Item -Path $path -ItemType Directory
[void]$list.Add($item)
# Bad
New-Item -Path $path -ItemType Directory | Out-Null
$list.Add($item) | Out-Null

Error handling#

Practice: Use try/catch/finally. Catch specific exception types. throw for unrecoverable errors. Write-Error for non-terminating. Use try/catch only when adding value (translation, enrichment, cleanup).

Why: Silently swallowing exceptions hides bugs — the opposite of Shift Left. Specific catch blocks separate recoverable from fatal errors. Meaningful messages serve the next person diagnosing the failure (Write it down).

How:

# Good
try {
    Get-Content -Path $Path -ErrorAction Stop
} catch [System.IO.IOException] {
    Write-Error "IO error: $_"
    throw
} catch {
    Write-Error "Unexpected error: $_"
    throw
}
# Bad
try {
    Get-Content -Path $Path
} catch {
    # Silently swallowed
}

Never pass -Verbose explicitly#

Practice: Never pass -Verbose to commands inside scripts or module code. The only permitted exception is -Verbose:$false to explicitly silence a call.

Why: Passing -Verbose overrides the caller's preference, breaking the expected propagation of common parameters. This violates the principle of least surprise and makes verbose output uncontrollable (Product mindset).

How:

# Good — verbose propagates from caller preference
Invoke-RestMethod -Uri $uri -Method Get

# Good — explicitly silencing a noisy call
Import-Module -Name Az.Accounts -Verbose:$false
# Bad
Invoke-RestMethod -Uri $uri -Method Get -Verbose   # Overrides caller preference