PowerShell/03core11 min

Error handling (try/catch, $ErrorActionPreference)

A ZEUS probe runs on a remote client machine — when something goes wrong, the backend must get a readable error, not a hung process or empty JSON. The key to this is understanding terminating errors and correct try/catch.

Two kinds of errors

PowerShell distinguishes non-terminating errors (the script keeps going) and terminating errors (they halt execution). try/catch catches only terminating ones — and that's the trap.

# a non-terminating error — try/catch will NOT catch it
try {
    Get-ADUser -Identity "doesnotexist"   # an ordinary error, keeps going
} catch {
    Write-Host "This won't be reached"
}

$ErrorActionPreference and -ErrorAction

For try/catch to work, the error must be terminating. We force this with -ErrorAction Stop at the command level.

try {
    Get-ADUser -Identity "doesnotexist" -ErrorAction Stop
} catch {
    Write-Verbose "User not found: $($_.Exception.Message)"
}

We set this globally at the start of the script:

$ErrorActionPreference = "Stop"

ProfessNet standard: probe scripts start with $ErrorActionPreference = "Stop". Then every error is terminating and falls into try/catch — no problem slips through silently.

The probe pattern: catch returns the error as JSON

In the ZEUS probes the catch doesn't just log — it builds a structured error object and writes it to stdout as JSON so the backend can parse it.

[CmdletBinding()]
param([Parameter(Mandatory)][string]$Forest, [switch]$AsJson)

$ErrorActionPreference = "Stop"

try {
    $forest = Get-ADForest -Identity $Forest
    $result = [pscustomobject]@{
        Ok      = $true
        Forest  = $forest.Name
        Domains = $forest.Domains
    }
}
catch {
    $result = [pscustomobject]@{
        Ok    = $false
        Error = $_.Exception.Message
        Type  = $_.Exception.GetType().Name
    }
}

if ($AsJson) {
    $result | ConvertTo-Json -Depth 5 -Compress
}

ProfessNet standard: a probe always returns a structured result — success or an error as JSON on stdout. An uncaught exception = a broken contract with the backend.

Error information

The error object ($_ in catch, $Error[0] globally) carries full context:

catch {
    $_.Exception.Message                       # the error text
    $_.Exception.GetType().FullName            # the exception type
    $_.InvocationInfo.ScriptLineNumber         # where it occurred
    $_.ScriptStackTrace                        # the full stack
}

finally — cleanup

finally always runs, regardless of an error — for releasing sessions and closing connections.

$session = New-PSSession -ComputerName $target
try {
    Invoke-Command -Session $session -ScriptBlock { Get-Service }
}
finally {
    Remove-PSSession $session    # the session is closed even on error
}
ElementRole
$ErrorActionPreference = "Stop"all errors terminating
-ErrorAction Stopforces termination for one command
try/catchcaptures a terminating error
finallycleanup always (sessions, connections)

Tip: don't use throw without a message. Throw a meaningful message: throw "Forest $Forest unavailable: $($_.Exception.Message)".


$ErrorActionPreference = "Stop", try/catch with the error returned as JSON and finally for cleanup — that's the foundation of a reliable probe. The backend always gets a readable result, never silence.