From c55fe55c684d76bb9b0ed9bf861be59f272155f2 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 28 May 2026 11:58:43 +1200 Subject: [PATCH 1/8] Add Add-SentryAttachment cmdlet with extension-based content-type defaults Wraps Scope.AddAttachment and, when -ContentType is not supplied, infers a content type from the file extension. PowerShell-specific extensions (.ps1, .psm1, .psd1, .ps1xml, .pssc, .psrc) and common text/structured formats (.txt, .log, .json, .xml, .csv, .yaml, ...) get a useful default so the Sentry UI can preview them instead of falling back to application/octet-stream. Closes #32 Co-Authored-By: Claude Opus 4.7 --- CHANGELOG.md | 1 + modules/Sentry/Sentry.psd1 | 1 + modules/Sentry/Sentry.psm1 | 1 + .../private/Get-AttachmentContentType.ps1 | 47 +++++++++++++++++ .../Sentry/public/Add-SentryAttachment.ps1 | 52 +++++++++++++++++++ tests/scope.tests.ps1 | 46 ++++++++++++++++ 6 files changed, 148 insertions(+) create mode 100644 modules/Sentry/private/Get-AttachmentContentType.ps1 create mode 100644 modules/Sentry/public/Add-SentryAttachment.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index d051a0c..fffe207 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Features +- Add `Add-SentryAttachment` cmdlet that infers a content type from the file extension (including PowerShell-specific extensions like `.ps1`, `.psm1`, `.psd1`) so the Sentry UI can preview attachments as text/JSON instead of falling back to `application/octet-stream` ([#132](https://github.com/getsentry/sentry-powershell/pull/132)) - 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)) diff --git a/modules/Sentry/Sentry.psd1 b/modules/Sentry/Sentry.psd1 index 2963a13..6f49492 100644 --- a/modules/Sentry/Sentry.psd1 +++ b/modules/Sentry/Sentry.psd1 @@ -32,6 +32,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-SentryAttachment', 'Add-SentryBreadcrumb', 'Add-SentryEventProcessor', 'Edit-SentryScope', diff --git a/modules/Sentry/Sentry.psm1 b/modules/Sentry/Sentry.psm1 index ab848b6..26d1857 100644 --- a/modules/Sentry/Sentry.psm1 +++ b/modules/Sentry/Sentry.psm1 @@ -23,6 +23,7 @@ function Add-SentryInlineType([string] $sourceFile, [string[]] $extraReferences) Add-SentryInlineType "$privateDir/SentryEventProcessor.cs" @() Add-SentryInlineType "$privateDir/ScriptBlockEventProcessor.cs" @($automationDllPath) . "$privateDir/SentryEventProcessor.ps1" +. "$privateDir/Get-AttachmentContentType.ps1" Get-ChildItem $publicDir -Filter '*.ps1' | ForEach-Object { . $_.FullName diff --git a/modules/Sentry/private/Get-AttachmentContentType.ps1 b/modules/Sentry/private/Get-AttachmentContentType.ps1 new file mode 100644 index 0000000..28f951f --- /dev/null +++ b/modules/Sentry/private/Get-AttachmentContentType.ps1 @@ -0,0 +1,47 @@ +# Maps common file extensions (especially PowerShell-flavored ones that aren't +# in most OS MIME databases) to a content type suitable for the Sentry server's +# attachment preview. Returns $null when no match is known, in which case the +# caller should leave content-type unset and let the server default it. +$script:SentryAttachmentContentTypes = @{ + # PowerShell + '.ps1' = 'text/plain' + '.psm1' = 'text/plain' + '.psd1' = 'text/plain' + '.ps1xml' = 'application/xml' + '.pssc' = 'text/plain' + '.psrc' = 'text/plain' + + # Plain text / logs / config + '.txt' = 'text/plain' + '.log' = 'text/plain' + '.md' = 'text/plain' + '.ini' = 'text/plain' + '.cfg' = 'text/plain' + '.conf' = 'text/plain' + '.yaml' = 'text/plain' + '.yml' = 'text/plain' + '.toml' = 'text/plain' + + # Structured + '.json' = 'application/json' + '.xml' = 'application/xml' + '.csv' = 'text/csv' + '.html' = 'text/html' + '.htm' = 'text/html' + '.css' = 'text/css' + '.js' = 'text/javascript' + '.sql' = 'text/plain' +} + +function Get-AttachmentContentType { + param([string] $FileName) + + if ([string]::IsNullOrEmpty($FileName)) { + return $null + } + $ext = [System.IO.Path]::GetExtension($FileName) + if ([string]::IsNullOrEmpty($ext)) { + return $null + } + return $script:SentryAttachmentContentTypes[$ext.ToLowerInvariant()] +} diff --git a/modules/Sentry/public/Add-SentryAttachment.ps1 b/modules/Sentry/public/Add-SentryAttachment.ps1 new file mode 100644 index 0000000..d551cb2 --- /dev/null +++ b/modules/Sentry/public/Add-SentryAttachment.ps1 @@ -0,0 +1,52 @@ +function Add-SentryAttachment { + <# + .SYNOPSIS + Adds a file or byte attachment to the current Sentry scope. + .DESCRIPTION + Wraps Scope.AddAttachment and, when -ContentType is not specified, + infers a sensible content type from the file extension so that the + Sentry UI can preview common text formats (including PowerShell + scripts) instead of falling back to application/octet-stream. + .PARAMETER Path + Path to a file to attach. + .PARAMETER Bytes + Raw bytes to attach. Must be combined with -FileName. + .PARAMETER FileName + File name to associate with byte data. + .PARAMETER ContentType + Optional MIME type. If omitted, a default is chosen based on the + file extension; if no default is known, content-type is left unset. + .PARAMETER Type + Sentry attachment type. Defaults to Default. + .EXAMPLE + PS> Add-SentryAttachment -Path $PSCommandPath + .EXAMPLE + PS> Add-SentryAttachment -Bytes $bytes -FileName 'data.json' + #> + [CmdletBinding(DefaultParameterSetName = 'Path')] + param( + [Parameter(Mandatory, Position = 0, ParameterSetName = 'Path', ValueFromPipeline = $true)] + [string] $Path, + + [Parameter(Mandatory, ParameterSetName = 'Bytes')] + [byte[]] $Bytes, + + [Parameter(Mandatory, ParameterSetName = 'Bytes')] + [string] $FileName, + + [string] $ContentType, + + [Sentry.AttachmentType] $Type = [Sentry.AttachmentType]::Default + ) + + process { + if ($PSCmdlet.ParameterSetName -eq 'Path') { + $resolvedFileName = [System.IO.Path]::GetFileName($Path) + $resolvedContentType = if ($PSBoundParameters.ContainsKey('ContentType')) { $ContentType } else { Get-AttachmentContentType $resolvedFileName } + Edit-SentryScope { $_.AddAttachment($Path, $Type, $resolvedContentType) }.GetNewClosure() + } else { + $resolvedContentType = if ($PSBoundParameters.ContainsKey('ContentType')) { $ContentType } else { Get-AttachmentContentType $FileName } + Edit-SentryScope { $_.AddAttachment($Bytes, $FileName, $Type, $resolvedContentType) }.GetNewClosure() + } + } +} diff --git a/tests/scope.tests.ps1 b/tests/scope.tests.ps1 index c7ba07c..7a7e8f3 100644 --- a/tests/scope.tests.ps1 +++ b/tests/scope.tests.ps1 @@ -41,3 +41,49 @@ Describe 'Edit-SentryScope' { $envelope.Items[1].Header.filename | Should -Be 'filename.bin' } } + +Describe 'Add-SentryAttachment' { + BeforeEach { + $events = [System.Collections.Generic.List[Sentry.SentryEvent]]::new(); + $transport = [RecordingTransport]::new() + StartSentryForEventTests ([ref] $events) ([ref] $transport) + } + + AfterEach { + Stop-Sentry + } + + It 'infers text/plain for a .ps1 file' { + Add-SentryAttachment -Path $PSCommandPath + 'message' | Out-Sentry + $envelope = [Sentry.Protocol.Envelopes.Envelope]$transport.Envelopes.ToArray()[0] + $envelope.Items[1].Header.filename | Should -Be 'scope.tests.ps1' + $envelope.Items[1].Header.content_type | Should -Be 'text/plain' + } + + It 'infers application/json for a .json byte attachment' { + [byte[]] $data = [System.Text.Encoding]::UTF8.GetBytes('{"hello":"world"}') + Add-SentryAttachment -Bytes $data -FileName 'payload.json' + 'message' | Out-Sentry + $envelope = [Sentry.Protocol.Envelopes.Envelope]$transport.Envelopes.ToArray()[0] + $envelope.Items[1].Header.filename | Should -Be 'payload.json' + $envelope.Items[1].Header.content_type | Should -Be 'application/json' + } + + It 'honors an explicit -ContentType' { + Add-SentryAttachment -Path $PSCommandPath -ContentType 'text/x-powershell' + 'message' | Out-Sentry + $envelope = [Sentry.Protocol.Envelopes.Envelope]$transport.Envelopes.ToArray()[0] + $envelope.Items[1].Header.content_type | Should -Be 'text/x-powershell' + } + + It 'leaves content-type unset for unknown extensions' { + [byte[]] $data = 1, 2, 3 + Add-SentryAttachment -Bytes $data -FileName 'thing.unknownext' + 'message' | Out-Sentry + $envelope = [Sentry.Protocol.Envelopes.Envelope]$transport.Envelopes.ToArray()[0] + $envelope.Items[1].Header.filename | Should -Be 'thing.unknownext' + # When no extension match and no explicit override, we don't set a content type. + [string]::IsNullOrEmpty($envelope.Items[1].Header.content_type) | Should -Be $true + } +} From b43559317d2e29b14e6bf62b173577ab06d49311 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 28 May 2026 11:59:10 +1200 Subject: [PATCH 2/8] Fix PR number in changelog Co-Authored-By: Claude Opus 4.7 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fffe207..223f2f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ ### Features -- Add `Add-SentryAttachment` cmdlet that infers a content type from the file extension (including PowerShell-specific extensions like `.ps1`, `.psm1`, `.psd1`) so the Sentry UI can preview attachments as text/JSON instead of falling back to `application/octet-stream` ([#132](https://github.com/getsentry/sentry-powershell/pull/132)) +- Add `Add-SentryAttachment` cmdlet that infers a content type from the file extension (including PowerShell-specific extensions like `.ps1`, `.psm1`, `.psd1`) so the Sentry UI can preview attachments as text/JSON instead of falling back to `application/octet-stream` ([#134](https://github.com/getsentry/sentry-powershell/pull/134)) - 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)) From 0b56cc6a2b2ad505e06f176d0292e9d95d5f171a Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 28 May 2026 13:33:57 +1200 Subject: [PATCH 3/8] Tweaked comments --- modules/Sentry/private/Get-AttachmentContentType.ps1 | 6 ++---- modules/Sentry/public/Add-SentryAttachment.ps1 | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/modules/Sentry/private/Get-AttachmentContentType.ps1 b/modules/Sentry/private/Get-AttachmentContentType.ps1 index 28f951f..21e0de1 100644 --- a/modules/Sentry/private/Get-AttachmentContentType.ps1 +++ b/modules/Sentry/private/Get-AttachmentContentType.ps1 @@ -1,7 +1,5 @@ -# Maps common file extensions (especially PowerShell-flavored ones that aren't -# in most OS MIME databases) to a content type suitable for the Sentry server's -# attachment preview. Returns $null when no match is known, in which case the -# caller should leave content-type unset and let the server default it. +# Maps common file extensions that aren't guaranteed to be in OS MIME databases +# to a content type that Sentry will recognize for attachment preview to work. $script:SentryAttachmentContentTypes = @{ # PowerShell '.ps1' = 'text/plain' diff --git a/modules/Sentry/public/Add-SentryAttachment.ps1 b/modules/Sentry/public/Add-SentryAttachment.ps1 index d551cb2..78c985e 100644 --- a/modules/Sentry/public/Add-SentryAttachment.ps1 +++ b/modules/Sentry/public/Add-SentryAttachment.ps1 @@ -6,7 +6,7 @@ function Add-SentryAttachment { Wraps Scope.AddAttachment and, when -ContentType is not specified, infers a sensible content type from the file extension so that the Sentry UI can preview common text formats (including PowerShell - scripts) instead of falling back to application/octet-stream. + scripts). .PARAMETER Path Path to a file to attach. .PARAMETER Bytes From 9dba662fb0827ee1484e464d0fc189ef93dfdd4e Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 28 May 2026 13:36:20 +1200 Subject: [PATCH 4/8] Add send-attachment.ps1 sample Demonstrates the new Add-SentryAttachment cmdlet covering all three common paths: file by path, raw bytes with a filename hint, and an explicit content-type override. Co-Authored-By: Claude Opus 4.7 --- samples/README.md | 5 +++++ samples/send-attachment.ps1 | 44 +++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 samples/send-attachment.ps1 diff --git a/samples/README.md b/samples/README.md index 1ac0e9f..e018887 100644 --- a/samples/README.md +++ b/samples/README.md @@ -14,4 +14,9 @@ pwsh ./samples/locate-city.ps1 Toronto Or send structured logs to Sentry (see the [Sentry Logs docs](https://docs.sentry.io/platforms/dotnet/logs/)): ```sh pwsh ./samples/send-logs.ps1 +``` + +Or send an event with file/byte attachments (see the [Attachments docs](https://docs.sentry.io/platforms/powershell/enriching-events/attachments/)): +```sh +pwsh ./samples/send-attachment.ps1 ``` \ No newline at end of file diff --git a/samples/send-attachment.ps1 b/samples/send-attachment.ps1 new file mode 100644 index 0000000..e9b7d69 --- /dev/null +++ b/samples/send-attachment.ps1 @@ -0,0 +1,44 @@ +<# +.SYNOPSIS + Demonstrates attaching files and byte data to a Sentry event. +.DESCRIPTION + Shows how to use Add-SentryAttachment to upload supporting files alongside + an event. When -ContentType is not specified, the cmdlet infers one from + the file extension so the Sentry UI can preview common text formats + (including PowerShell scripts) instead of falling back to + application/octet-stream. +.EXAMPLE + PS> ./send-attachment.ps1 +.LINK + https://docs.sentry.io/platforms/powershell/enriching-events/attachments/ +#> + +# Import the Sentry module. In your code, you would just use `Import-Module Sentry`. +Import-Module $PSScriptRoot/../modules/Sentry/Sentry.psd1 + +Start-Sentry { + $_.Dsn = 'https://997874440feaba4ecc65c1e25df7912b@o447951.ingest.us.sentry.io/4508073336176640' + $_.Debug = $true +} + +try { + # 1) Attach a file by path. The extension (.ps1) is recognized as text, + # so Sentry will render it inline in the event's Attachments tab. + Add-SentryAttachment -Path $PSCommandPath + + # 2) Attach raw bytes with a filename hint. .json maps to application/json + # so the UI shows a JSON preview. + $payload = @{ host = [System.Net.Dns]::GetHostName(); psVersion = $PSVersionTable.PSVersion.ToString() } | + ConvertTo-Json + $bytes = [System.Text.Encoding]::UTF8.GetBytes($payload) + Add-SentryAttachment -Bytes $bytes -FileName 'context.json' + + # 3) Explicit -ContentType always wins over the inferred default. + Add-SentryAttachment -Path $PSCommandPath -ContentType 'text/x-powershell' + + # Capture an event so the attachments have something to ride along with. + # All three attachments above are attached to this event via the current scope. + 'Sample event with attachments' | Out-Sentry +} finally { + Stop-Sentry +} From 3668568b26654511aabc54794ca17d6ab1f6acac Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 28 May 2026 14:03:42 +1200 Subject: [PATCH 5/8] Resolve relative attachment paths against PowerShell $PWD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PowerShell's $PWD and [Environment]::CurrentDirectory can diverge after Set-Location. The Sentry SDK reads attachment files lazily at send time via .NET I/O, which resolves relative paths against [Environment]::CurrentDirectory — so a relative path that looks correct from the caller's perspective could fail or attach the wrong file. Resolve eagerly with GetUnresolvedProviderPathFromPSPath before passing the path on. Caught by Cursor Bugbot on #134. Co-Authored-By: Claude Opus 4.7 --- .../Sentry/public/Add-SentryAttachment.ps1 | 4 +++ tests/scope.tests.ps1 | 31 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/modules/Sentry/public/Add-SentryAttachment.ps1 b/modules/Sentry/public/Add-SentryAttachment.ps1 index 78c985e..fb5325b 100644 --- a/modules/Sentry/public/Add-SentryAttachment.ps1 +++ b/modules/Sentry/public/Add-SentryAttachment.ps1 @@ -41,6 +41,10 @@ function Add-SentryAttachment { process { if ($PSCmdlet.ParameterSetName -eq 'Path') { + # Resolve relative paths against PowerShell's $PWD. The Sentry SDK reads + # the file lazily via .NET I/O, which resolves against [Environment]::CurrentDirectory + # — that can diverge from $PWD after Set-Location, so resolve eagerly here. + $Path = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($Path) $resolvedFileName = [System.IO.Path]::GetFileName($Path) $resolvedContentType = if ($PSBoundParameters.ContainsKey('ContentType')) { $ContentType } else { Get-AttachmentContentType $resolvedFileName } Edit-SentryScope { $_.AddAttachment($Path, $Type, $resolvedContentType) }.GetNewClosure() diff --git a/tests/scope.tests.ps1 b/tests/scope.tests.ps1 index 7a7e8f3..80d37cb 100644 --- a/tests/scope.tests.ps1 +++ b/tests/scope.tests.ps1 @@ -77,6 +77,37 @@ Describe 'Add-SentryAttachment' { $envelope.Items[1].Header.content_type | Should -Be 'text/x-powershell' } + It 'resolves relative paths against PowerShell $PWD, not [Environment]::CurrentDirectory' { + # Simulate the common case where PowerShell's location diverges from the + # process working directory (which is what .NET I/O uses for relative paths). + $originalLocation = Get-Location + $originalEnvCwd = [Environment]::CurrentDirectory + try { + $tempDir = New-Item -ItemType Directory -Path (Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString())) + $relativeName = 'attachment-relative.txt' + $fileContents = 'hello from a relative path' + Set-Content -Path (Join-Path $tempDir $relativeName) -Value $fileContents -NoNewline + Set-Location $tempDir + # Force divergence: leave [Environment]::CurrentDirectory pointed elsewhere. + [Environment]::CurrentDirectory = $originalEnvCwd + + Add-SentryAttachment -Path $relativeName + 'message' | Out-Sentry + + $envelope = [Sentry.Protocol.Envelopes.Envelope]$transport.Envelopes.ToArray()[0] + $envelope.Items[1].Header.filename | Should -Be $relativeName + # The envelope serializer reads the file lazily — if the path didn't resolve, + # we'd see an empty/zero-length payload instead of the real bytes. + $envelope.Items[1].Header.length | Should -Be $fileContents.Length + } finally { + Set-Location $originalLocation + [Environment]::CurrentDirectory = $originalEnvCwd + if ($tempDir -and (Test-Path $tempDir)) { + Remove-Item $tempDir -Recurse -Force + } + } + } + It 'leaves content-type unset for unknown extensions' { [byte[]] $data = 1, 2, 3 Add-SentryAttachment -Bytes $data -FileName 'thing.unknownext' From 3dfebcce49df5955facbf2083fc814c0ed37238c Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Thu, 28 May 2026 14:10:01 +1200 Subject: [PATCH 6/8] Tolerate Windows file-lock race in relative-path test cleanup The Sentry SDK may keep an attachment file handle open until Stop-Sentry runs in AfterEach. On Windows this races with the in-test `finally` cleanup of the temp dir, which doesn't allow concurrent access. The temp dir is harmless to leave behind, so swallow the cleanup error rather than fail the test. Co-Authored-By: Claude Opus 4.7 --- tests/scope.tests.ps1 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/scope.tests.ps1 b/tests/scope.tests.ps1 index 80d37cb..bb03d38 100644 --- a/tests/scope.tests.ps1 +++ b/tests/scope.tests.ps1 @@ -102,8 +102,11 @@ Describe 'Add-SentryAttachment' { } finally { Set-Location $originalLocation [Environment]::CurrentDirectory = $originalEnvCwd + # On Windows the SDK may still have a handle on the attachment file + # until Stop-Sentry runs in AfterEach, so cleanup can race. The temp + # dir is harmless to leave behind, so don't fail the test over it. if ($tempDir -and (Test-Path $tempDir)) { - Remove-Item $tempDir -Recurse -Force + Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue } } } From 879466e9fdf207407b1b851e7cf12d538451444f Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Fri, 29 May 2026 11:02:07 +1200 Subject: [PATCH 7/8] Document and test piping a file path into Add-SentryAttachment The Path parameter already accepts pipeline input by value; add an example (cmdlet help + sample) and a regression test for `$path | Add-SentryAttachment`. Byte input stays a named parameter since piping a byte[] unrolls it into individual bytes. Co-Authored-By: Claude Opus 4.7 --- modules/Sentry/public/Add-SentryAttachment.ps1 | 2 ++ samples/send-attachment.ps1 | 2 ++ tests/scope.tests.ps1 | 8 ++++++++ 3 files changed, 12 insertions(+) diff --git a/modules/Sentry/public/Add-SentryAttachment.ps1 b/modules/Sentry/public/Add-SentryAttachment.ps1 index fb5325b..d423496 100644 --- a/modules/Sentry/public/Add-SentryAttachment.ps1 +++ b/modules/Sentry/public/Add-SentryAttachment.ps1 @@ -20,6 +20,8 @@ function Add-SentryAttachment { Sentry attachment type. Defaults to Default. .EXAMPLE PS> Add-SentryAttachment -Path $PSCommandPath + .EXAMPLE + PS> $PSCommandPath | Add-SentryAttachment .EXAMPLE PS> Add-SentryAttachment -Bytes $bytes -FileName 'data.json' #> diff --git a/samples/send-attachment.ps1 b/samples/send-attachment.ps1 index e9b7d69..d5d4c42 100644 --- a/samples/send-attachment.ps1 +++ b/samples/send-attachment.ps1 @@ -24,6 +24,8 @@ Start-Sentry { try { # 1) Attach a file by path. The extension (.ps1) is recognized as text, # so Sentry will render it inline in the event's Attachments tab. + # A path can also be supplied from the pipeline: + # $PSCommandPath | Add-SentryAttachment Add-SentryAttachment -Path $PSCommandPath # 2) Attach raw bytes with a filename hint. .json maps to application/json diff --git a/tests/scope.tests.ps1 b/tests/scope.tests.ps1 index bb03d38..2467ac6 100644 --- a/tests/scope.tests.ps1 +++ b/tests/scope.tests.ps1 @@ -61,6 +61,14 @@ Describe 'Add-SentryAttachment' { $envelope.Items[1].Header.content_type | Should -Be 'text/plain' } + It 'accepts a file path from the pipeline' { + $PSCommandPath | Add-SentryAttachment + 'message' | Out-Sentry + $envelope = [Sentry.Protocol.Envelopes.Envelope]$transport.Envelopes.ToArray()[0] + $envelope.Items[1].Header.filename | Should -Be 'scope.tests.ps1' + $envelope.Items[1].Header.content_type | Should -Be 'text/plain' + } + It 'infers application/json for a .json byte attachment' { [byte[]] $data = [System.Text.Encoding]::UTF8.GetBytes('{"hello":"world"}') Add-SentryAttachment -Bytes $data -FileName 'payload.json' From 2721fd1a6cd5e47b8f8748abfdc3c7a6f31ddeba Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Fri, 29 May 2026 11:12:52 +1200 Subject: [PATCH 8/8] Support piping byte[] into Add-SentryAttachment via comma operator Add ValueFromPipeline to -Bytes so advanced callers can pipe a byte array (wrapped with the unary comma operator to avoid the pipeline unrolling it). Not demonstrated in the sample since it requires understanding the comma-operator idiom; documented in the parameter help and covered by a test. Co-Authored-By: Claude Opus 4.7 --- modules/Sentry/public/Add-SentryAttachment.ps1 | 7 +++++-- tests/scope.tests.ps1 | 13 +++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/modules/Sentry/public/Add-SentryAttachment.ps1 b/modules/Sentry/public/Add-SentryAttachment.ps1 index d423496..cf9636a 100644 --- a/modules/Sentry/public/Add-SentryAttachment.ps1 +++ b/modules/Sentry/public/Add-SentryAttachment.ps1 @@ -10,7 +10,10 @@ function Add-SentryAttachment { .PARAMETER Path Path to a file to attach. .PARAMETER Bytes - Raw bytes to attach. Must be combined with -FileName. + Raw bytes to attach. Must be combined with -FileName. Accepts pipeline + input, but note the pipeline unrolls arrays: to pipe a byte[] as a single + attachment, wrap it with the unary comma operator, e.g. + `,$bytes | Add-SentryAttachment -FileName 'data.json'`. .PARAMETER FileName File name to associate with byte data. .PARAMETER ContentType @@ -30,7 +33,7 @@ function Add-SentryAttachment { [Parameter(Mandatory, Position = 0, ParameterSetName = 'Path', ValueFromPipeline = $true)] [string] $Path, - [Parameter(Mandatory, ParameterSetName = 'Bytes')] + [Parameter(Mandatory, ParameterSetName = 'Bytes', ValueFromPipeline = $true)] [byte[]] $Bytes, [Parameter(Mandatory, ParameterSetName = 'Bytes')] diff --git a/tests/scope.tests.ps1 b/tests/scope.tests.ps1 index 2467ac6..c3a221d 100644 --- a/tests/scope.tests.ps1 +++ b/tests/scope.tests.ps1 @@ -69,6 +69,19 @@ Describe 'Add-SentryAttachment' { $envelope.Items[1].Header.content_type | Should -Be 'text/plain' } + It 'accepts a byte[] from the pipeline via the unary comma operator' { + [byte[]] $data = [System.Text.Encoding]::UTF8.GetBytes('{"hello":"world"}') + # The comma operator wraps $data so the pipeline delivers it as one item + # rather than unrolling it into individual bytes. + , $data | Add-SentryAttachment -FileName 'piped.json' + 'message' | Out-Sentry + $envelope = [Sentry.Protocol.Envelopes.Envelope]$transport.Envelopes.ToArray()[0] + $envelope.Items.Count | Should -Be 2 + $envelope.Items[1].Header.filename | Should -Be 'piped.json' + $envelope.Items[1].Header.length | Should -Be $data.Length + $envelope.Items[1].Header.content_type | Should -Be 'application/json' + } + It 'infers application/json for a .json byte attachment' { [byte[]] $data = [System.Text.Encoding]::UTF8.GetBytes('{"hello":"world"}') Add-SentryAttachment -Bytes $data -FileName 'payload.json'