Without understanding the two kinds of errors, try/catch silently fails to catch most cmdlet errors.
PowerShell has two kinds of errors:
- Non-terminating errors, the default for most cmdlets: the error is reported (and added to
$Error) but the command continues with the next item.try/catchdoes not see these. - Terminating errors:
throw, .NET exceptions, and any error raised with-ErrorAction Stop. These stop the current statement and can be caught.
So the standard pattern is try { Some-Cmdlet -ErrorAction Stop } catch { ... }. Inside catch, $_ is the ErrorRecord: $_.Exception.Message, $_.Exception.GetType(), $_.TargetObject, $_.InvocationInfo.ScriptLineNumber. You can catch specific exception types first and a general catch last. finally always runs, for cleanup.
-ErrorAction (common parameter) per call: Stop, Continue (default), SilentlyContinue, Ignore. $ErrorActionPreference = 'Stop' at the top of a script makes all cmdlet errors terminating, which is the PowerShell equivalent of bash's set -e. -ErrorVariable errs collects a call's errors into a variable.
Native programs (git, docker) do not throw; check $LASTEXITCODE after them, or in 7.4+ set $PSNativeCommandUseErrorActionPreference = $true so a non-zero exit becomes an error. $? tells whether the last command succeeded.
Example
Get-Item /nope -ErrorAction SilentlyContinue # non-terminating, hidden
$? # False
try {
Get-Item /nope -ErrorAction Stop # now it is terminating
'not reached'
} catch [System.Management.Automation.ItemNotFoundException] {
"missing: $($_.TargetObject)" # missing: /nope
} catch {
"other error: $($_.Exception.Message)"
} finally {
'cleanup always runs' # cleanup always runs
}
Get-Item /nope, $HOME -ErrorVariable errs -ErrorAction SilentlyContinue | Out-Null
$errs.Count # 1 (the second path worked)
try { throw 'custom failure' } catch { "caught: $($_.Exception.Message)" } # caught: custom failure
$zero = 0
try { 1 / $zero } catch [System.DivideByZeroException] { 'division by zero' } # division by zero
$ErrorActionPreference = 'Stop' # script-wide: every error terminatesRemember
- try/catch only sees terminating errors; add -ErrorAction Stop
- $_ in catch is the ErrorRecord; finally always runs
- Check $LASTEXITCODE after native programs
Watch out for
- Wrapping a cmdlet in try/catch without -ErrorAction Stop and wondering why catch never runs.