private/SynchronousTransport.ps1

# Take Sentry's SerializableHttpContent, convert it to a string, and send via PowerShell's Invoke-WebRequest,
# then translate the response back to a .NET HttpResponseMessage.
# There are limited options to perform synchronous operations in Windows PowerShell 5.1 on .NET 4.6, so this is a workaround.
class SynchronousTransport : Sentry.Http.HttpTransportBase, Sentry.Extensibility.ITransport {
    [Sentry.Extensibility.IDiagnosticLogger] $logger
    # PowerShell 7.5.2+ changed how property assignment works in constructors when inheriting from .NET classes.
    # Using a hashtable instead of individual [System.Reflection.MethodInfo] properties works around this issue.
    # See: https://github.com/PowerShell/PowerShell/releases/tag/v7.5.2
    [hashtable] $reflectionMethods = @{}

    SynchronousTransport([Sentry.SentryOptions] $options) : base($options) {
        $this.logger = $options.DiagnosticLogger
        if ($null -eq $this.logger) {
            $this.logger = Get-Variable -Scope script -Name SentryPowerShellDiagnosticLogger -ValueOnly -ErrorAction SilentlyContinue
        }

        # These are internal methods, so we need to use reflection to access them.
        $instanceMethod = [System.Reflection.BindingFlags]::Instance + [System.Reflection.BindingFlags]::NonPublic + [System.Reflection.BindingFlags]::Public;
        $this.reflectionMethods['ProcessEnvelope'] = [Sentry.Http.HttpTransportBase].GetMethod('ProcessEnvelope', $instanceMethod)
        if ($null -eq $this.reflectionMethods['ProcessEnvelope']) {
            throw "Failed to find ProcessEnvelope method on Sentry.Http.HttpTransportBase"
        }

        $this.reflectionMethods['CreateRequest'] = [Sentry.Http.HttpTransportBase].GetMethod('CreateRequest', $instanceMethod)
        if ($null -eq $this.reflectionMethods['CreateRequest']) {
            throw "Failed to find CreateRequest method on Sentry.Http.HttpTransportBase"
        }

        $EnvelopeHttpContentType = [Sentry.SentrySdk].Assembly.GetType('Sentry.Internal.Http.EnvelopeHttpContent')
        if ($null -eq $EnvelopeHttpContentType) {
            throw "Failed to find Sentry.Internal.Http.EnvelopeHttpContent type"
        }

        $this.reflectionMethods['SerializeToStream'] = $EnvelopeHttpContentType.GetMethod('SerializeToStream', $instanceMethod)
        if ($null -eq $this.reflectionMethods['SerializeToStream']) {
            throw "Failed to find SerializeToStream method on EnvelopeHttpContent"
        }
    }

    [System.Threading.Tasks.Task] SendEnvelopeAsync([Sentry.Protocol.Envelopes.Envelope] $envelope, [System.Threading.CancellationToken]$cancellationToken = [System.Threading.CancellationToken]::None) {
        $processedEnvelope = $this.reflectionMethods['ProcessEnvelope'].Invoke($this, @($envelope))
        if ($processedEnvelope.Items.count -gt 0) {
            $request = $this.reflectionMethods['CreateRequest'].Invoke($this, @($processedEnvelope))

            $headers = @{}
            foreach ($header in $request.Headers) {
                $Key = $header.Key
                $Value = $header.Value.Trim() -join ', '
                $headers[$Key] = $Value
            }

            $memoryStream = [System.IO.MemoryStream]::new()
            $this.reflectionMethods['SerializeToStream'].Invoke($request.Content, @($memoryStream, $null, $cancellationToken))
            $memoryStream.Position = 0

            if ($null -ne $this.logger) {
                $this.logger.Log([Sentry.SentryLevel]::Debug, 'Sending content synchronously, Content-Length: {0}', $null, $memoryStream.Length)
            }

            $ProgressPreference = 'SilentlyContinue'
            $psResponse = Invoke-WebRequest -Uri $request.RequestUri -Method $request.Method.Method -Headers $headers -Body $memoryStream -UseBasicParsing

            $response = [System.Net.Http.HttpResponseMessage]::new($psResponse.StatusCode)
            $contentType = $psResponse.Headers['Content-Type']
            if ($null -eq $contentType) {
                $contentType = 'application/json'
            }
            $response.Content = [System.Net.Http.StringContent]::new($psResponse.Content, [System.Text.Encoding]::UTF8, $contentType)

            foreach ($header in $psResponse.Headers.GetEnumerator()) {
                $response.Headers.TryAddWithoutValidation($header.Key, $header.Value)
            }

            $this.HandleResponse($response, $processedEnvelope)
        }

        return [System.Threading.Tasks.Task]::CompletedTask
    }
}