From 3dda6003440827d5bb98721860bac6067f56dd15 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 26 May 2026 14:09:31 +1200 Subject: [PATCH 1/3] Add Add-SentryEventProcessor cmdlet Public cmdlet that registers a global event processor backed by a PowerShell script block. The block receives the event via the automatic $_ variable (matching Edit-SentryScope), returns the event to send it, or $null to drop it. Add-SentryEventProcessor { $_.SetTag('host', $env:COMPUTERNAME); $_ } Add-SentryEventProcessor { if ($_.Message -match 'secret') { return $null } $_ } Internally the block is wrapped by a new C# ScriptBlockEventProcessor that implements ISentryEventProcessor directly. Doing the interface implementation in C# (rather than asking users to author a PowerShell class deriving from an internal base) keeps the public API a single scriptblock and avoids the `Process` keyword conflict in Windows PowerShell that would otherwise require a Process_ / DoProcess workaround. Sentry.psm1 factors the existing Add-Type call into a small helper so the second Add-Type for ScriptBlockEventProcessor.cs picks up the same /nowarn CompilerOptions treatment and can pass an extra System.Management.Automation reference. Co-Authored-By: Claude Opus 4.7 --- modules/Sentry/Sentry.psd1 | 1 + modules/Sentry/Sentry.psm1 | 25 ++++++--- .../private/ScriptBlockEventProcessor.cs | 55 +++++++++++++++++++ .../public/Add-SentryEventProcessor.ps1 | 37 +++++++++++++ tests/add-sentry-event-processor.tests.ps1 | 54 ++++++++++++++++++ 5 files changed, 163 insertions(+), 9 deletions(-) create mode 100644 modules/Sentry/private/ScriptBlockEventProcessor.cs create mode 100644 modules/Sentry/public/Add-SentryEventProcessor.ps1 create mode 100644 tests/add-sentry-event-processor.tests.ps1 diff --git a/modules/Sentry/Sentry.psd1 b/modules/Sentry/Sentry.psd1 index 66b0a46..2963a13 100644 --- a/modules/Sentry/Sentry.psd1 +++ b/modules/Sentry/Sentry.psd1 @@ -33,6 +33,7 @@ # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. FunctionsToExport = @( 'Add-SentryBreadcrumb', + 'Add-SentryEventProcessor', 'Edit-SentryScope', 'Invoke-WithSentry', 'Out-Sentry', diff --git a/modules/Sentry/Sentry.psm1 b/modules/Sentry/Sentry.psm1 index 11366ef..ab848b6 100644 --- a/modules/Sentry/Sentry.psm1 +++ b/modules/Sentry/Sentry.psm1 @@ -4,17 +4,24 @@ $moduleInfo = Import-PowerShellDataFile (Join-Path (Split-Path -Parent $MyInvoca . "$privateDir/Get-SentryAssembliesDirectory.ps1" $sentryDllPath = (Join-Path (Get-SentryAssembliesDirectory) 'Sentry.dll') +# ScriptBlockEventProcessor.cs uses System.Management.Automation.ScriptBlock. +$automationDllPath = [System.Management.Automation.PSObject].Assembly.Location -$addTypeParams = @{ - TypeDefinition = (Get-Content "$privateDir/SentryEventProcessor.cs" -Raw) - ReferencedAssemblies = $sentryDllPath - Debug = $false +function Add-SentryInlineType([string] $sourceFile, [string[]] $extraReferences) { + $addTypeParams = @{ + TypeDefinition = (Get-Content $sourceFile -Raw) + ReferencedAssemblies = @($sentryDllPath) + $extraReferences + Debug = $false + } + # -CompilerOptions is PS Core only; suppress CS1701/CS1702 (harmless binding-redirect noise) when available. + if ($PSEdition -eq 'Core') { + $addTypeParams['CompilerOptions'] = '/nowarn:CS1701;CS1702' + } + Add-Type @addTypeParams } -# -CompilerOptions is PS Core only; suppress CS1701/CS1702 (harmless binding-redirect noise) when available. -if ($PSEdition -eq 'Core') { - $addTypeParams['CompilerOptions'] = '/nowarn:CS1701;CS1702' -} -Add-Type @addTypeParams + +Add-SentryInlineType "$privateDir/SentryEventProcessor.cs" @() +Add-SentryInlineType "$privateDir/ScriptBlockEventProcessor.cs" @($automationDllPath) . "$privateDir/SentryEventProcessor.ps1" Get-ChildItem $publicDir -Filter '*.ps1' | ForEach-Object { diff --git a/modules/Sentry/private/ScriptBlockEventProcessor.cs b/modules/Sentry/private/ScriptBlockEventProcessor.cs new file mode 100644 index 0000000..fcd73d5 --- /dev/null +++ b/modules/Sentry/private/ScriptBlockEventProcessor.cs @@ -0,0 +1,55 @@ +using System; +using System.Management.Automation; +using Sentry; +using Sentry.Extensibility; + +// Wraps a PowerShell ScriptBlock as an ISentryEventProcessor so the public +// Add-SentryEventProcessor cmdlet can register user-supplied script blocks with +// the Sentry pipeline. Implementing the interface in C# (rather than asking users +// to author a PowerShell class deriving from an internal base) keeps the public +// API a single scriptblock and avoids the `Process` keyword conflict in Windows +// PowerShell that would otherwise require a Process_ / DoProcess workaround. +public sealed class ScriptBlockEventProcessor : ISentryEventProcessor +{ + private readonly ScriptBlock _scriptBlock; + private readonly IDiagnosticLogger _logger; + + public ScriptBlockEventProcessor(ScriptBlock scriptBlock, IDiagnosticLogger logger) + { + if (scriptBlock == null) throw new ArgumentNullException("scriptBlock"); + _scriptBlock = scriptBlock; + _logger = logger; + } + + public SentryEvent Process(SentryEvent @event) + { + try + { + var results = _scriptBlock.Invoke(@event); + if (results == null || results.Count == 0) + { + return @event; + } + + var last = results[results.Count - 1]; + if (last == null) + { + return null; + } + + return (last.BaseObject as SentryEvent) ?? @event; + } + catch (Exception ex) + { + if (_logger != null) + { + _logger.Log( + SentryLevel.Warning, + "Event processor scriptblock failed for event {0}: {1}", + ex, + new object[] { @event.EventId, ex.Message }); + } + return @event; + } + } +} diff --git a/modules/Sentry/public/Add-SentryEventProcessor.ps1 b/modules/Sentry/public/Add-SentryEventProcessor.ps1 new file mode 100644 index 0000000..8d4697c --- /dev/null +++ b/modules/Sentry/public/Add-SentryEventProcessor.ps1 @@ -0,0 +1,37 @@ +. "$privateDir/Get-CurrentOptions.ps1" + +<# +.SYNOPSIS + Registers a global event processor that runs on every event before it is sent to Sentry. +.DESCRIPTION + The script block receives the Sentry event via the automatic variable $_ (matching the + convention used by Edit-SentryScope). Return the event to send it, or $null to drop it. +.EXAMPLE + PS> Add-SentryEventProcessor { $_.SetTag('host', $env:COMPUTERNAME); $_ } +.EXAMPLE + PS> Add-SentryEventProcessor { + if ($_.Message -match 'secret') { return $null } + $_ + } +#> +function Add-SentryEventProcessor { + param( + [Parameter(Mandatory, Position = 0)] + [scriptblock] $ScriptBlock + ) + + $options = Get-CurrentOptions + if ($null -eq $options) { + throw 'Sentry is not initialized. Call Start-Sentry before adding an event processor.' + } + + # Wrap the user's script block in a pipeline so that $_ is bound to the event, + # matching Edit-SentryScope's convention. + $wrapped = { + param([Sentry.SentryEvent] $event_) + $event_ | ForEach-Object $ScriptBlock + }.GetNewClosure() + + $options.AddEventProcessor( + [ScriptBlockEventProcessor]::new($wrapped, $options.DiagnosticLogger)) +} diff --git a/tests/add-sentry-event-processor.tests.ps1 b/tests/add-sentry-event-processor.tests.ps1 new file mode 100644 index 0000000..681eb3b --- /dev/null +++ b/tests/add-sentry-event-processor.tests.ps1 @@ -0,0 +1,54 @@ +BeforeAll { + . "$PSScriptRoot/utils.ps1" + $global:SentryPowershellRethrowErrors = $true +} + +AfterAll { + $global:SentryPowershellRethrowErrors = $false +} + +Describe 'Add-SentryEventProcessor' { + BeforeEach { + $events = [System.Collections.Generic.List[Sentry.SentryEvent]]::new(); + $transport = [RecordingTransport]::new() + StartSentryForEventTests ([ref] $events) ([ref] $transport) + } + + AfterEach { + $events.Clear() + Stop-Sentry + } + + It 'Mutates events via $_' { + Add-SentryEventProcessor { $_.SetTag('custom', 'value'); $_ } + 'msg' | Out-Sentry + + $events[0].Tags['custom'] | Should -Be 'value' + } + + It 'Drops events when the script block returns $null' { + Add-SentryEventProcessor { + if ($_.Message.Message -match 'drop-me') { return $null } + $_ + } + 'drop-me please' | Out-Sentry + 'keep this one' | Out-Sentry + + $events.Count | Should -Be 1 + $events[0].Message.Message | Should -Be 'keep this one' + } + + It 'Chains multiple processors in registration order' { + Add-SentryEventProcessor { $_.SetTag('first', '1'); $_ } + Add-SentryEventProcessor { $_.SetTag('second', '2'); $_ } + 'msg' | Out-Sentry + + $events[0].Tags['first'] | Should -Be '1' + $events[0].Tags['second'] | Should -Be '2' + } + + It 'Throws when Sentry is not initialized' { + Stop-Sentry + { Add-SentryEventProcessor { $_ } } | Should -Throw '*Sentry is not initialized*' + } +} From f439577449b96077f16c8e77d0b89005b1181113 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 26 May 2026 14:15:16 +1200 Subject: [PATCH 2/3] Add changelog entry for Add-SentryEventProcessor Co-Authored-By: Claude Opus 4.7 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90aaeff..d051a0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Features - Add `Write-SentryLog` cmdlet, a native PowerShell API for sending structured logs (Sentry Logs) ([#131](https://github.com/getsentry/sentry-powershell/pull/131)) +- Add `Add-SentryEventProcessor` cmdlet for registering a global event processor from a PowerShell script block ([#130](https://github.com/getsentry/sentry-powershell/pull/130)) ## 0.4.0 From 257bf69b230161f593a1ab95f85df29917de7d8e Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 26 May 2026 14:21:51 +1200 Subject: [PATCH 3/3] Drop SentryPowershellRethrowErrors from Add-SentryEventProcessor tests The flag exists so internal try/catch paths (currently only in StackTraceProcessor's source-file context-line reads and Start-Sentry's transport/worker init) rethrow during tests instead of swallowing errors. These tests register simple script blocks and capture events via 'msg' | Out-Sentry, so they don't exercise any of those paths and don't need the override. Silences the PSScriptAnalyzer global-variable warning on the new test file. Co-Authored-By: Claude Opus 4.7 --- tests/add-sentry-event-processor.tests.ps1 | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/add-sentry-event-processor.tests.ps1 b/tests/add-sentry-event-processor.tests.ps1 index 681eb3b..9d975f8 100644 --- a/tests/add-sentry-event-processor.tests.ps1 +++ b/tests/add-sentry-event-processor.tests.ps1 @@ -1,10 +1,5 @@ BeforeAll { . "$PSScriptRoot/utils.ps1" - $global:SentryPowershellRethrowErrors = $true -} - -AfterAll { - $global:SentryPowershellRethrowErrors = $false } Describe 'Add-SentryEventProcessor' {