Debug Faster with PowerShell Get-Error: Turn Vague Failures into Actionable Fixes
PowerShell 7 introduced Get-Error, a purpose-built cmdlet that turns cryptic red text into a rich, structured breakdown of what failed, where it failed, and why. Instead of scanning walls of output, you can surface the inner exception, call stack, command info, and even HResult codes in seconds. In this post, you'll learn practical patterns to use Get-Error for rapid root cause analysis, clearer logs, and reliable automation.
- Use Get-Error to expand inner exceptions and call stacks.
- Pipe the latest error (or a specific ErrorRecord) for precise details.
- Capture key fields for quick, structured logs.
Whether you're debugging locally, inside a container, or in CI/CD, these techniques will help you troubleshoot with confidence.
Understand Error Records and Get-Error
ErrorRecord basics
PowerShell errors are objects (ErrorRecord) that carry rich context: the exception, command invocation info, script and line number, category info, and more. You can access recent errors from the automatic $Error variable, e.g. $Error[0] for the most recent one.
Get-Error takes those records and expands them into a developer-friendly, detailed view:
# Show the most recent error with full detail
Get-Error -Newest 1 -Detailed
# Pipe a specific ErrorRecord
$Error[0] | Get-Error -Detailed
# Control how deep to expand inner exceptions
Get-Error -Newest 1 -Detailed -Depth 5
# When you only want messages (no stacks)
Get-Error -Newest 3 -MessageGet-Error is available in PowerShell 7+. If you're on Windows PowerShell 5.1, consider upgrading or running PowerShell 7 side-by-side.
Expanding inner exceptions and call stacks
Many failures hide the true cause inside InnerException. With -Detailed (and optionally -Depth), Get-Error walks the inner exception chain and emits a ScriptStackTrace showing where the failure bubbled up. That makes it easy to pinpoint the first frame that matters and skip noisy infrastructure calls.
Fast workflows you can copy-paste
Turn the last error into an actionable summary
This pattern captures the message, HResult, last command, and the top stack frame so you can see the failure at a glance. Paste it into any catch block:
try {
Invoke-RestMethod -Uri 'https://bad.example/api' -ErrorAction Stop
} catch {
$e = Get-Error -ErrorRecord $_ -Detailed
[pscustomobject]@{
Message = $e.ErrorRecord.Exception.Message
HResult = ('0x{0:X}' -f $e.ErrorRecord.Exception.HResult)
LastCmd = $e.InvocationInfo.MyCommand
StackTop = ($e.ScriptStackTrace -split '\r?\n' | Select-Object -First 1)
} | Format-List
}Use this anytime you want a concise, human-readable snapshot during dev or when tailing logs in a container shell.
Structured JSON logs for CI/CD and containers
In automation, structured logs beat free-form text. Emit JSON so your pipeline, log shipper, or SIEM can search and alert on the exact fields you care about.
try {
# Your script logic here
throw [System.InvalidOperationException]::new('Bad config value: sizeLimit')
} catch {
$e = Get-Error -ErrorRecord $_ -Detailed -Depth 5
$payload = [pscustomobject]@{
Timestamp = (Get-Date).ToString('o')
Message = $e.ErrorRecord.Exception.Message
Exception = $e.ErrorRecord.Exception.GetType().FullName
HResult = ('0x{0:X}' -f $e.ErrorRecord.Exception.HResult)
FQID = $e.FullyQualifiedErrorId
Command = $e.InvocationInfo.MyCommand
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
Stack = $e.ScriptStackTrace
}
$json = $payload | ConvertTo-Json -Depth 6
# Append to a file or send to your collector
$json | Out-File -FilePath './error.log' -Append -Encoding utf8
throw # rethrow if you want the job to fail
}Tip: include a stable FQID (FullyQualifiedErrorId) to group similar failures across runs.
HTTP and API failures: pull status code and body
REST calls often require peeking into the response to understand the root cause. Depending on the exception type, you may have Response, StatusCode, or other properties. This pattern extracts them when available:
try {
Invoke-RestMethod -Uri 'https://api.example.com/thing' -Method Post -Body @{ name = 'x' } -ErrorAction Stop
} catch {
$e = Get-Error -ErrorRecord $_ -Detailed
$status = $null; $body = $null
if ($e.ErrorRecord.Exception -is [Microsoft.PowerShell.Commands.HttpResponseException]) {
$status = [int]$e.ErrorRecord.Exception.Response.StatusCode
$body = $e.ErrorRecord.Exception.Response.Content
} elseif ($e.ErrorRecord.Exception -is [System.Net.WebException]) {
$resp = [System.Net.HttpWebResponse]$e.ErrorRecord.Exception.Response
$status = [int]$resp.StatusCode
$sr = New-Object System.IO.StreamReader($resp.GetResponseStream())
$body = $sr.ReadToEnd(); $sr.Dispose()
}
[pscustomobject]@{
StatusCode = $status
Body = $body
Message = $e.ErrorRecord.Exception.Message
StackTop = ($e.ScriptStackTrace -split '\r?\n' | Select-Object -First 1)
} | Format-List
}Now you can log the HTTP status and response body alongside the message and top stack frame for quick triage.
A reusable helper: Get-LastErrorSummary
Drop this function in your profile or shared module to quickly summarize recent failures from the interactive console or a failing job.
function Get-LastErrorSummary {
param(
[int]$Newest = 1,
[int]$Depth = 3
)
$e = Get-Error -Newest $Newest -Detailed -Depth $Depth
[pscustomobject]@{
Message = $e.ErrorRecord.Exception.Message
Type = $e.ErrorRecord.Exception.GetType().FullName
HResult = ('0x{0:X}' -f $e.ErrorRecord.Exception.HResult)
Command = $e.InvocationInfo.MyCommand
Script = $e.InvocationInfo.ScriptName
Line = $e.InvocationInfo.ScriptLineNumber
StackTop = ($e.ScriptStackTrace -split '\r?\n' | Select-Object -First 1)
}
}
# Usage
Get-LastErrorSummary | Format-ListMake it production-ready
Best practices that surface the real root cause
- Promote to terminating errors when appropriate: use
-ErrorAction Stopon cmdlets so exceptions land incatch. - Prefer
try { } catch { }totrapfor clarity and scope control. - Capture a dedicated error variable:
-ErrorVariable evlets you inspect$evseparately from the global$Errorbuffer. - Fail fast in automation: after logging with Get-Error,
throwto signal job failure if required.
$ev = @()
Invoke-Something -ErrorAction Continue -ErrorVariable ev
if ($ev.Count -gt 0) {
$ev[0] | Get-Error -Detailed | Out-String | Write-Host
}Integrate with CI/CD and containers
- Containers: Write structured logs to stdout or a mounted volume so your orchestrator (Docker, Kubernetes) can collect them. Example:
$payload | ConvertTo-Json -Depth 6 | Write-Output. - GitHub Actions/Azure DevOps: Emit concise summaries to the console and attach full JSON as an artifact. Keep stack traces searchable.
- Retry loops: On transient failures, log the first and last occurrence with Get-Error and include a correlation ID for traceability.
Security and signal quality
- Redact secrets: Before logging response bodies or headers, mask tokens and passwords. Keep the structure, remove the secrets.
- Avoid PII sprawl: Only capture fields you need: message, exception type, status code, top stack frame, and command are usually enough.
- Control noise: Periodically
$Error.Clear()in long-running sessions soGet-Error -Newestreferences current failures.
function Mask-Secret {
param([string]$text)
if ([string]::IsNullOrEmpty($text)) { return $text }
# Basic masking for common keys (tune to your data)
return ($text -replace '(?i)(token|apikey|password)\s*=\s*[^;,\s]+', '$1=***')
}Performance tips
- Use
-Messagewhen you don't need stacks; it's lighter and faster. - Limit recursion with
-Depthto avoid expanding huge inner exception trees. - Log once: capture a summarized object for your pipeline and keep verbose detail only on demand.
A quick troubleshooting checklist
- Reproduce and run
Get-Error -Newest 1 -Detailed. - Skim the top stack frame to find the first relevant code path.
- Identify the exception type and inner exception message.
- If it's an HTTP call, extract StatusCode and Body.
- Log a structured summary (message, type, HResult, command, stack top).
- Redact sensitive fields before persisting logs.
- Act on the actionable fields (e.g., bad config key, missing file, 401/403, timeouts).
Conclusion
Get-Error turns vague errors into actionable fixes. Expand inner exceptions, grab the exact command and line, and emit structured logs you can search and alert on. The result: faster debugging, clearer root causes, and fewer blind spots in production.
Want more PowerShell patterns like this? Check out the PowerShell Advanced Cookbook for proven recipes and deeper dives: PowerShell Advanced Cookbook.