Private/Runtime/Send-OpenApiHttpRequest.ps1

function Send-OpenApiHttpRequest {
    <#
    .SYNOPSIS
        Sends one HTTP request with HttpClient and returns a response object whose body is still unread (Message).
    .DESCRIPTION
        The request message and its content are created for this attempt and disposed after the response headers
        arrive. Transport failures are rethrown as the underlying exception (HttpRequestException, TimeoutException).
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory)]
        [System.Net.Http.HttpClient]$Client,

        [Parameter(Mandatory)]
        [string]$Method,

        [Parameter(Mandatory)]
        [string]$Uri,

        [Parameter()]
        [AllowNull()]
        [System.Collections.IDictionary]$Header,

        [Parameter()]
        [AllowNull()]
        [scriptblock]$ContentFactory,

        [Parameter()]
        [AllowNull()]
        [AllowEmptyCollection()]
        [string[]]$SensitiveName
    )

    $request = New-Object System.Net.Http.HttpRequestMessage -ArgumentList (New-Object System.Net.Http.HttpMethod -ArgumentList $Method.ToUpperInvariant()), ([uri]$Uri)
    try {
        if ($null -ne $ContentFactory) {
            $request.Content = & $ContentFactory
        }
        if ($null -ne $Header) {
            foreach ($key in $Header.Keys) {
                $name = [string]$key
                $value = [string]$Header[$key]
                if ($name -eq 'Content-Type') {
                    if ($null -ne $request.Content) {
                        $request.Content.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::Parse($value)
                    }
                    continue
                }
                if (-not $request.Headers.TryAddWithoutValidation($name, $value)) {
                    if ($null -ne $request.Content) {
                        [void]$request.Content.Headers.Remove($name)
                        [void]$request.Content.Headers.TryAddWithoutValidation($name, $value)
                    }
                }
            }
        }

        $displayUri = Get-OpenApiRedactedUri -Uri $Uri -SensitiveName $SensitiveName
        Write-Verbose -Message ('{0} {1}' -f $request.Method.Method, $displayUri)
        if ($DebugPreference -ne 'SilentlyContinue') {
            $headerLog = [ordered]@{}
            foreach ($pair in $request.Headers) {
                $headerLog[$pair.Key] = @($pair.Value)
            }
            if ($null -ne $request.Content) {
                foreach ($pair in $request.Content.Headers) {
                    $headerLog[$pair.Key] = @($pair.Value)
                }
            }
            Write-Debug -Message ("Request headers:" + [Environment]::NewLine + (Format-OpenApiHeaderLog -Header $headerLog -SensitiveName $SensitiveName))
            if ($null -ne $request.Content) {
                Write-Debug -Message ('Request body: ' + (Get-OpenApiBodyLogText -Content $request.Content))
            }
        }

        $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
        try {
            $message = $Client.SendAsync($request, [System.Net.Http.HttpCompletionOption]::ResponseHeadersRead).GetAwaiter().GetResult()
        }
        catch {
            $failure = $_.Exception
            while (($failure -is [System.Management.Automation.MethodInvocationException] -or $failure -is [System.AggregateException]) -and $null -ne $failure.InnerException) {
                $failure = $failure.InnerException
            }
            if ($failure -is [System.Threading.Tasks.TaskCanceledException] -or $failure -is [System.OperationCanceledException]) {
                $failure = New-Object System.TimeoutException -ArgumentList "The request $($request.Method.Method) $displayUri timed out after $([int]$Client.Timeout.TotalSeconds) seconds.", $failure
            }
            Write-Verbose -Message "$($request.Method.Method) $displayUri failed: $($failure.Message)"
            throw $failure
        }
        $stopwatch.Stop()

        $headers = ConvertFrom-OpenApiHttpHeader -Response $message
        $contentType = $null
        if ($null -ne $message.Content -and $null -ne $message.Content.Headers.ContentType) {
            $contentType = $message.Content.Headers.ContentType.ToString()
        }
        Write-Verbose -Message ('{0} {1} -> {2} {3} ({4} ms)' -f $request.Method.Method, $displayUri, [int]$message.StatusCode, $message.ReasonPhrase, $stopwatch.ElapsedMilliseconds)
        if ($DebugPreference -ne 'SilentlyContinue') {
            Write-Debug -Message ("Response headers:" + [Environment]::NewLine + (Format-OpenApiHeaderLog -Header $headers -SensitiveName $SensitiveName))
        }
        return [pscustomobject]@{
            PSTypeName   = 'Tcs.OpenApi.HttpResponse'
            Method       = $request.Method.Method
            Uri          = $Uri
            StatusCode   = [int]$message.StatusCode
            ReasonPhrase = $message.ReasonPhrase
            Headers      = $headers
            ContentType  = $contentType
            Message      = $message
            Body         = $null
        }
    }
    finally {
        if ($null -ne $request.Content -and $null -ne $request.Content.PSObject.Properties['TcsOpenApiKeepOpen']) {
            # The content wraps the caller's stream: do not let the request dispose it
            $request.Content = $null
        }
        $request.Dispose()
    }
}