RavenPowerShell.psm1
function CurrentUnixTimestamp () { return [int64](([datetime]::UtcNow)-(get-date "1/1/1970")).TotalSeconds } Class RavenClient { [string]$sentryDsn [string]$storeUri [string]$sentryKey [string]$sentrySecret [int]$projectId [string]$sentryAuth [string]$userAgent # https://github.com/PowerShell/vscode-powershell/issues/66 hidden [bool]$_getFrameVariablesIsFixed RavenClient([string]$sentryDsn) { $uri = [System.Uri]::New($sentryDsn) $this.sentryDsn = $sentryDsn $this.sentryKey = $uri.UserInfo.Split(':')[0] $this.sentrySecret = $uri.UserInfo.Split(':')[1] $this.projectId = $uri.Segments[1] $this.storeUri = "$($uri.Scheme)://$($uri.Host):$($uri.Port)/api/$($this.projectId)/store/" $this.userAgent = 'PowerShellRaven/1.0' $this.sentryAuth = "Sentry sentry_version=5,sentry_key=$($this.sentryKey),sentry_secret=$($this.sentrySecret)" $this._getFrameVariablesIsFixed = $false } [hashtable]GetBaseRequestBody([string]$message) { $eventid = (New-Guid).Guid.Replace('-', '') $utcNow = (Get-Date).ToUniversalTime() $iso8601 = Get-Date($utcNow) -UFormat '+%Y-%m-%dT%H:%M:%S' $body = @{} $body['event_id'] = $eventid $body['timestamp'] = [string]$iso8601 $body['logger'] = 'root' $body['platform'] = 'other' $body['sdk'] = @{ 'name' = 'PowerShellRaven' 'version' = '1.0' } $body['server_name'] = [System.Net.Dns]::GetHostName() $body['message'] = $message return $body } [void]StoreEvent([hashtable]$body) { $headers = @{} $headers.Add('X-Sentry-Auth', $this.sentryAuth + ",sentry_timestamp=" + $(CurrentUnixTimestamp)) $headers.Add('User-Agent', $this.userAgent) $jsonBody = ConvertTo-Json $body -Depth 6 Invoke-RestMethod -Uri $this.storeUri -Method Post -Body $jsonBody -ContentType 'application/json' -Headers $headers } [hashtable]ParsePSCallstack([System.Management.Automation.CallStackFrame[]]$callstackFrames, [hashtable[]]$frameVariables) { $context_lines_count = 10 $stacktrace = @{ 'frames' = @() } $frames = @() for ($i=0; $i -lt $callstackFrames.Count; $i++) { $stackframe = $callstackFrames[$i] $frame = @{} $frame['filename'] = $stackframe.ScriptName $frame['abs_path'] = $stackframe.ScriptName $frame['context_line'] = $stackframe.Position.StartScriptPosition.Line $frame['lineno'] = $stackframe.ScriptLineNumber $frame['colno'] = $stackframe.Position.StartColumnNumber $frame['function'] = $stackframe.FunctionName $script_lines_arr = $stackframe.Position.StartScriptPosition.GetFullScript() -split '\r?\n' $script_line_index = $stackframe.ScriptLineNumber - 1 $script_lines_count = $script_lines_arr.Count $script_before_idx = if ($script_line_index -lt $context_lines_count) { 0 } else { $script_line_index - $context_lines_count } $script_after_idx = if ($script_line_index -gt $script_lines_count - $context_lines_count) { $script_lines_count - 1 } else { $script_line_index + $context_lines_count } $pre_context = if ($script_line_index -eq 0) { @() } else { $script_lines_arr[$script_before_idx..($script_line_index - 1)] } $post_context = if ($script_line_index -eq $script_lines_count - 1) { @() } else { $script_lines_arr[($script_line_index + 1)..$script_after_idx] } $frame['pre_context'] = $pre_context $frame['post_context'] = $post_context $frame['vars'] = $frameVariables[$i] $frames += $frame } # [System.Array]::Reverse returns an empty array ??? for ($i = $frames.Count - 1; $i -ge 0; $i--) { $stacktrace['frames'] += $frames[$i] } return $stacktrace } [void]CaptureMessage([string]$messageRaw, [string[]]$messageParams, [string]$messageFormatted) { $body = $this.GetBaseRequestBody('') $body['sentry.interfaces.Message'] = @{ 'message' = $messageRaw 'params' = $messageParams 'formatted' = $messageFormatted } $this.StoreEvent($body) } [void]CaptureException([System.Management.Automation.ErrorRecord]$errorRecord) { # skip ourselves $this.CaptureException($errorRecord, 1) } [void]CaptureException([System.Management.Automation.ErrorRecord]$errorRecord, [int]$skipFrames) { # skip ourselves $callstackSkip = 1 + $skipFrames $frameVariables = @() $callstackFrames = Get-PSCallStack | Select-Object -Skip $callstackSkip if ($this._getFrameVariablesIsFixed) { foreach ($stackframe in $callstackFrames) { $frameVariabless += $stackframe.GetFrameVariables() } } else { for ($i = $callstackSkip; $i -lt ($callstackFrames.Count + $callstackSkip); $i++) { $scopeVariables = @{} $scopeVariablesList = Get-Variable -Scope $i foreach ($scopeVariable in $scopeVariablesList) { $scopeVariables[$scopeVariable.Name] = $scopeVariable.Value } $frameVariables += $scopeVariables } } $this.CaptureException($errorRecord, $callstackFrames, $frameVariables) } [void]CaptureException([System.Management.Automation.ErrorRecord]$errorRecord, [System.Management.Automation.CallStackFrame[]]$callstackFrames=@(), [hashtable[]]$frameVariables) { $exceptionMessage = $errorRecord.Exception.Message if ($errorRecord.ErrorDetails.Message -ne $null) { $exceptionMessage = $errorRecord.ErrorDetails.Message } $exceptionName = $errorRecord.Exception.GetType().Name $exceptionSource = $errorRecord.Exception.Source $body = $this.GetBaseRequestBody($exceptionMessage) $exceptionValue = @{ 'type' = $exceptionName 'value' = $exceptionMessage 'module' = $exceptionSource } $exceptionValues = @() $exceptionValues += $exceptionValue $body['exception'] = @{ 'values' = $exceptionValues } $body['stacktrace'] = $this.ParsePSCallstack($callstackFrames, $frameVariables) $this.StoreEvent($body) } } function New-RavenClient { param( # Sentry DSN [Parameter(Mandatory=$true)] [string] $SentryDsn ) return [RavenClient]::New($SentryDsn) } Export-ModuleMember -Function New-RavenClient |