Last active
July 22, 2019 20:15
-
-
Save JustinGrote/52cf75ea75f2888dbaf4b0c86519d0a6 to your computer and use it in GitHub Desktop.
Powershell trigger breakpoint when a terminating exception occurs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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