Skip to content

Instantly share code, notes, and snippets.

@JustinGrote
Last active July 22, 2019 20:15
Show Gist options
  • Save JustinGrote/52cf75ea75f2888dbaf4b0c86519d0a6 to your computer and use it in GitHub Desktop.
Save JustinGrote/52cf75ea75f2888dbaf4b0c86519d0a6 to your computer and use it in GitHub Desktop.
Powershell trigger breakpoint when a terminating exception occurs
using namespace System.Management.Automation
function Debug-OnException ($file) {
#Get-PSBreakPoint -Variable stacktrace | Remove-PSBreakpoint
$null = Set-PSBreakpoint -Variable stacktrace -Mode Write -Action {
$lastCommandCallStack = (get-pscallstack)[1]
$lastCommandExecuted = $lastCommandCallStack.position
$lastCommandLocation = $lastCommandCallStack.location
[bool]$isThrowStatement = if ([Language.Parser]::ParseInput($lastCommandExecuted,[ref]$null,[ref]$null).FindAll(
{$args[0] -is [Language.ThrowStatementAst]},$true
)) {$true} else {$false}
#Skip some non-exception related stacktrace updates
if ($stacktrace -match 'CheckActionPreference|ThrowTerminatingError|ForEachObjectCommand\.ProcessRecord') {
continue
}
if ($isThrowStatement) {
#If this error was from a throw statement, wait for the throw logic to read erroractionpreference, at which point $Error will be updated
#This will not trigger if the thrown exception is caught by trap or try/catch first
#Ref: https://github.com/PowerShell/vscode-powershell/issues/298#issuecomment-343485564
if (-not (Get-PSBreakpoint -Variable ErrorActionPreference)) {
$null = Set-PSBreakpoint -Variable ErrorActionPreference -Mode Read -Action {
$lastCommandCallStack = (get-pscallstack)[1]
$lastCommandExecuted = $lastCommandCallStack.position
$lastCommandLocation = $lastCommandCallStack.location
[bool]$isThrowStatement = if ([Language.Parser]::ParseInput($lastCommandExecuted,[ref]$null,[ref]$null).FindAll(
{$args[0] -is [Language.ThrowStatementAst]},$true
)) {$true} else {$false}
if ($isThrowStatement) {
write-host ([Environment]::NewLine)
write-host -fore red "==========================================================================="
write-host -fore red "Thrown Exception at $($lastCommandLocation) {$lastCommandExecuted}"
write-host -fore red "==========================================================================="
break
} else {
Get-PSBreakpoint -Variable ErrorActionPreference | Remove-PSBreakpoint 2>&1
}
}
}
continue
}
#If it is not a throw statement, continue normally
write-host ([Environment]::NewLine)
write-host -fore red "==========================================================================="
write-host -fore red "Exception at $($lastCommandLocation) {$lastCommandExecuted}"
write-host -fore red "==========================================================================="
#Since we can't watch Error for changes (it's a readonly "constant"), for non-throw errors we have to re-run the last command to capture it within the debug context, since when this breakpoint hits, $Error has not been updated yet.
if (-not $PSDebugNoErrorCapture) {
write-host -fore darkyellow "Re-Executing '$($lastCommandExecuted)' to attempt to capture the error. You can disable this behavior by setting `$PSDebugNoErrorCapture to `$true"
try {
$null = Invoke-Expression $lastCommandExecuted
} catch {
$GLOBAL:PSDebugCapturedError = $PSItem
}
if ($GLOBAL:PSDebugCapturedError) {
#ISSUE: If broken within a function, extraneous scope number errors show up in $Error, hence why we capture to a separate variable.
Write-Host -fore DarkYellow "Error capture succeeded and stored to `$PSDebugCapturedError"
}
}
get-psbreakpoint -Variable stacktrace | remove-psbreakpoint 2>&1
break
}
if ($file) {
. $file
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment