Skip to content

Capture ScriptBlock source code from the topmost frame #60

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Fixes

- StackTrace parsing on Windows Powershell 5.1 ([#50](https://github.com/getsentry/sentry-powershell/pull/50))
- Context lines captured for PowerShell executed directly as a ScriptBlock ([#60](https://github.com/getsentry/sentry-powershell/pull/60))

### Dependencies

Expand Down
36 changes: 30 additions & 6 deletions modules/Sentry/private/StackTraceProcessor.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class StackTraceProcessor : SentryEventProcessor
[System.Management.Automation.InvocationInfo]$InvocationInfo
[System.Management.Automation.CallStackFrame[]]$StackTraceFrames
[string[]]$StackTraceString
[string[]]$ScriptBlockSource
hidden [string[]] $modulePaths
hidden [hashtable] $pwshModules = @{}

Expand Down Expand Up @@ -178,7 +179,7 @@ class StackTraceProcessor : SentryEventProcessor
# Update module info
$this.SetModule($sentryFrame)
$sentryFrame.InApp = [string]::IsNullOrEmpty($sentryFrame.Module)
$this.SetContextLines($sentryFrame)
$this.SetContextLines($sentryFrame, $this.ScriptBlockSource)
}

$sentryFrames.Reverse()
Expand All @@ -190,7 +191,14 @@ class StackTraceProcessor : SentryEventProcessor
hidden [Sentry.SentryStackFrame] CreateFrame([System.Management.Automation.InvocationInfo] $info)
{
$sentryFrame = [Sentry.SentryStackFrame]::new()
$sentryFrame.AbsolutePath = $info.ScriptName
if ("" -eq $info.ScriptName)
{
$sentryFrame.AbsolutePath = "<No file>"
}
else
{
$sentryFrame.AbsolutePath = $info.ScriptName
}
$sentryFrame.LineNumber = $info.ScriptLineNumber
$sentryFrame.ColumnNumber = $info.OffsetInLine
$sentryFrame.ContextLine = $info.Line.TrimEnd()
Expand Down Expand Up @@ -275,18 +283,34 @@ class StackTraceProcessor : SentryEventProcessor
}
}

hidden SetContextLines([Sentry.SentryStackFrame] $sentryFrame)
hidden SetContextLines([Sentry.SentryStackFrame] $sentryFrame, [string[]] $scriptBlockSource)
{
if ([string]::IsNullOrEmpty($sentryFrame.AbsolutePath) -or $sentryFrame.LineNumber -lt 1)
if ($sentryFrame.LineNumber -lt 1)
{
return
}

if ((Test-Path $sentryFrame.AbsolutePath -IsValid) -and (Test-Path $sentryFrame.AbsolutePath -PathType Leaf))
$lines = $null

if (-not [string]::IsNullOrEmpty($sentryFrame.AbsolutePath) -and (Test-Path $sentryFrame.AbsolutePath -IsValid) -and (Test-Path $sentryFrame.AbsolutePath -PathType Leaf))
{
try
{
$lines = Get-Content $sentryFrame.AbsolutePath -TotalCount ($sentryFrame.LineNumber + 5)
}
catch
{
Write-Warning "Failed to read context lines for $($sentryFrame.AbsolutePath): $_"
}
} elseif ($null -ne $scriptBlockSource) {
Write-Debug "Using fallback ScriptBlockSource for context lines."
$lines = $scriptBlockSource | Select-Object -First ($sentryFrame.LineNumber + 5)
}

if ($null -ne $lines)
{
try
{
if ($null -eq $sentryFrame.ContextLine)
{
$sentryFrame.ContextLine = $lines[$sentryFrame.LineNumber - 1]
Expand All @@ -303,7 +327,7 @@ class StackTraceProcessor : SentryEventProcessor
}
catch
{
Write-Warning "Failed to read context lines for $($sentryFrame.AbsolutePath): $_"
Write-Warning "Failed to process context lines for $($sentryFrame.AbsolutePath): $_"
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions modules/Sentry/public/Out-Sentry.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ function Out-Sentry
return
}

# Use the PSCallStack to capture the source code of the main script that is being executed.
# This is used as a fallback in case the code is executed directly as a ScriptBlock from a hosted .NET environment with no .ps1 file.
# If the top frame is Script (i.e. the code is executed as a script file), we don't need to capture the source code.
$TopFrame = Get-PSCallStack | Select-Object -Last 1
if ("Script" -ne $TopFrame.InvocationInfo.MyCommand.CommandType)
{
$processor.ScriptBlockSource = $TopFrame.InvocationInfo.MyCommand.ScriptBlock.ToString() -split "`r`n"
}

if ($options.AttachStackTrace -and $null -eq $processor.StackTraceFrames -and $null -eq $processor.StackTraceString)
{
$processor.StackTraceFrames = Get-PSCallStack | Select-Object -Skip 1
Expand Down
Loading