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 intotry/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
}
| Element | Role |
|---|---|
$ErrorActionPreference = "Stop" | all errors terminating |
-ErrorAction Stop | forces termination for one command |
try/catch | captures a terminating error |
finally | cleanup always (sessions, connections) |
Tip: don't use
throwwithout 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.