EventSources/@HttpResponse.ps1

<#
.Synopsis
    Sends events on HTTP Responses.
.Description
    Sends HTTP requests and signals on Responses


    Event MessageData will contain the response, with two additional properties:
    * .ResponseBytes
    * .ResponseContent
#>

[ComponentModel.InitializationEvent({
    # Because of the potential for a "narrow window" timing issue,
    # this event source needs to send the request the moment after the event has been subscribed to.
    $httpAsyncInfo = [PSCustomObject]@{
        InputObject  = $this
        IAsyncResult = $this.BeginGetResponse($null, $null)
        InvokeID     = $this.RequestID
    }
    if ($null -eq $Global:HttpResponsesAsync) {
        $Global:HttpResponsesAsync = [Collections.ArrayList]::new()
    }
    $null = $Global:HttpResponsesAsync.Add($httpAsyncInfo)
})]
param(
# The Uniform Resource Identifier.
[Parameter(Mandatory,Position=0,ValueFromPipelineByPropertyName)]
[Alias('Url')]
[Uri]
$Uri,

# The HTTP Method
[Parameter(Position=1,ValueFromPipelineByPropertyName)]
[ValidateSet('Get','Head','Post','Put','Delete','Trace','Options','Merge','Patch')]
[string]
$Method = 'GET',

# A collection of headers to send with the request.
[Parameter(Position=2,ValueFromPipelineByPropertyName)]
[Alias('Headers')]
[Collections.IDictionary]
$Header,

# The request body.
[Parameter(Position=3,ValueFromPipelineByPropertyName)]
[PSObject]
$Body,

# The polling interval.
# This is the minimum amount of time until you will be notified of the success or failure of a http request
[Parameter(Position=3,ValueFromPipelineByPropertyName)]
[Timespan]
$PollingInterval = "00:00:00.331",

[Text.Encoding]
$TransferEncoding = $([Text.Encoding]::UTF8)
)

process {
    # Clear the event subscriber if one exists.
    $eventSubscriber = Get-EventSubscriber -SourceIdentifier "@HttpResponse_Check" -ErrorAction SilentlyContinue
    if ($eventSubscriber) {$eventSubscriber | Unregister-Event}

    # Create a new subscriber for the request.
    $httpResponseCheckTimer = New-Object Timers.Timer -Property @{
        Interval  = $PollingInterval.TotalMilliseconds # Every pollinginterval,
        AutoReset = $true
    }
    $HttpResponseChecker = 
        Register-ObjectEvent -InputObject $httpResponseCheckTimer -EventName Elapsed -Action {
            $toCallEnd = # check to see if any requests have completed.
                @(foreach ($httpAsyncInfo in $Global:HttpResponsesAsync) { 
                    if ($httpAsyncInfo.IAsyncResult.IsCompleted) {
                        $httpAsyncInfo
                    }
                })

            $null = # Foreach completed request
                foreach ($httpAsyncInfo in $toCallEnd) {
                    $webResponse = 
                        try {   # try to get the response
                            $httpAsyncInfo.InputObject.EndGetResponse($httpAsyncInfo.IAsyncResult)
                        } catch {                                
                            $_ # and catch any error.
                        }

                    
                    if ($webResponse -is [Management.Automation.ErrorRecord] -or 
                        $webResponse -is [Exception]) # If we got an error
                    {
                        # Signal the error
                        New-Event -SourceIdentifier "HttpRequest.Failed.$($httpAsyncInfo.InvokeID)" -MessageData $webResponse
                        $Global:HttpResponsesAsync.Remove($httpAsyncInfo)
                        continue
                    }


                    # Otherwise, get the response stream
                    $ms = [IO.MemoryStream]::new()
                    $null = $webResponse.GetResponseStream().CopyTo($ms)
                    $responseBytes = $ms.ToArray() # as a [byte[]].
                    $ms.Close()
                    $ms.Dispose()
                    

                    $encoding = # See if the response had an encoding
                        if ($webResponse.ContentEncoding) { $webResponse.ContentEncoding }
                        elseif ($webResponse.CharacterSet) {
                            # or a character set.
                            [Text.Encoding]::GetEncodings() | Where-Object Name -EQ $webResponse.CharacterSet
                        }

                    $webResponseContent = 
                        if ($encoding) { # If it did, decode the response content.
                            [IO.StreamReader]::new([IO.MemoryStream]::new($responseBytes), $encoding).ReadToEnd()
                        } else {
                            $null
                        }

                    # Add the properties to the web response.
                    $webResponse | 
                        Add-Member NoteProperty ResponseBytes $webResponseContent -Force -PassThru |
                        Add-Member NoteProperty ResponseContent $webResponseContent -Force
                    $webResponse.Close()

                    # And send the response with the additional information.
                    New-Event -SourceIdentifier "HttpRequest.Completed.$($httpAsyncInfo.InvokeID)" -MessageData $webResponse
                    $Global:HttpResponsesAsync.Remove($httpAsyncInfo)
                }

            if ($Global:HttpResponsesAsync.Count -eq 0) {
                Get-EventSubscriber -SourceIdentifier "@HttpResponse_Check" | Unregister-Event
            }
        }

    $httpResponseCheckTimer.Start() # Start it's timer.


    $httpRequest = [Net.HttpWebRequest]::CreateHttp($Uri)
    $httpRequest.Method = $Method
    if ($Header -and $Header.Count) {
        foreach ($kv in $Header.GetEnumerator()) {
            $httpRequest.Headers[$kv.Key] = $kv.Value
        }
    }
    if ($Body) {
        $requestStream = $httpRequest.GetRequestStream()
        
        if (-not $requestStream) { return }
        if ($body -is [byte[]] -or $Body -as [byte[]]) {
            [IO.MemoryStream]::new([byte[]]$Body).CopyTo($requestStream)
        }
        elseif ($Body -is [string]) {
            [IO.StreamWriter]::new($requestStream, $TransferEncoding).Write($Body)
        }
        else {
            [IO.StreamWriter]::new($requestStream, $TransferEncoding).Write((ConvertTo-Json -InputObject $body -Depth 100))
        }
    }
    $requestId   = [Guid]::NewGuid().ToString() 
    $httpRequest |
        Add-Member NoteProperty SourceIdentifier "HttpRequest.Completed.$requestId","HttpRequest.Failed.$requestId" -Force -PassThru |
        Add-Member NoteProperty RequestID $requestId -Force -PassThru
}