1+ <#
2+ . Synopsis
3+ Sends events on HTTP Responses.
4+ . Description
5+ Sends HTTP requests and signals on Responses
6+
7+
8+ Event MessageData will contain the response, with two additional properties:
9+ * .ResponseBytes
10+ * .ResponseContent
11+ #>
12+ [ComponentModel.InitializationEvent ({
13+ # Because of the potential for a "narrow window" timing issue,
14+ # this event source needs to send the request the moment after the event has been subscribed to.
15+ $httpAsyncInfo = [PSCustomObject ]@ {
16+ InputObject = $this
17+ IAsyncResult = $this.BeginGetResponse ($null , $null )
18+ InvokeID = $this.RequestID
19+ }
20+ if ($null -eq $Global :HttpResponsesAsync ) {
21+ $Global :HttpResponsesAsync = [Collections.ArrayList ]::new()
22+ }
23+ $null = $Global :HttpResponsesAsync.Add ($httpAsyncInfo )
24+ })]
25+ param (
26+ # The Uniform Resource Identifier.
27+ [Parameter (Mandatory , Position = 0 , ValueFromPipelineByPropertyName )]
28+ [Alias (' Url' )]
29+ [Uri ]
30+ $Uri ,
31+
32+ # The HTTP Method
33+ [Parameter (Position = 1 , ValueFromPipelineByPropertyName )]
34+ [ValidateSet (' Get' , ' Head' , ' Post' , ' Put' , ' Delete' , ' Trace' , ' Options' , ' Merge' , ' Patch' )]
35+ [string ]
36+ $Method = ' GET' ,
37+
38+ # A collection of headers to send with the request.
39+ [Parameter (Position = 2 , ValueFromPipelineByPropertyName )]
40+ [Alias (' Headers' )]
41+ [Collections.IDictionary ]
42+ $Header ,
43+
44+ # The request body.
45+ [Parameter (Position = 3 , ValueFromPipelineByPropertyName )]
46+ [PSObject ]
47+ $Body ,
48+
49+ # The polling interval.
50+ # This is the minimum amount of time until you will be notified of the success or failure of a http request
51+ [Parameter (Position = 3 , ValueFromPipelineByPropertyName )]
52+ [Timespan ]
53+ $PollingInterval = " 00:00:00.331" ,
54+
55+ [Text.Encoding ]
56+ $TransferEncoding = $ ([Text.Encoding ]::UTF8)
57+ )
58+
59+ process {
60+ # Clear the event subscriber if one exists.
61+ $eventSubscriber = Get-EventSubscriber - SourceIdentifier " @HttpResponse_Check" - ErrorAction SilentlyContinue
62+ if ($eventSubscriber ) {$eventSubscriber | Unregister-Event }
63+
64+ # Create a new subscriber for the request.
65+ $httpResponseCheckTimer = New-Object Timers.Timer - Property @ {
66+ Interval = $PollingInterval.TotalMilliseconds # Every pollinginterval,
67+ AutoReset = $true
68+ }
69+ $HttpResponseChecker =
70+ Register-ObjectEvent - InputObject $httpResponseCheckTimer - EventName Elapsed - Action {
71+ $toCallEnd = # check to see if any requests have completed.
72+ @ (foreach ($httpAsyncInfo in $Global :HttpResponsesAsync ) {
73+ if ($httpAsyncInfo.IAsyncResult.IsCompleted ) {
74+ $httpAsyncInfo
75+ }
76+ })
77+
78+ $null = # Foreach completed request
79+ foreach ($httpAsyncInfo in $toCallEnd ) {
80+ $webResponse =
81+ try { # try to get the response
82+ $httpAsyncInfo.InputObject.EndGetResponse ($httpAsyncInfo.IAsyncResult )
83+ } catch {
84+ $_ # and catch any error.
85+ }
86+
87+
88+ if ($webResponse -is [Management.Automation.ErrorRecord ] -or
89+ $webResponse -is [Exception ]) # If we got an error
90+ {
91+ # Signal the error
92+ New-Event - SourceIdentifier " HttpRequest.Failed.$ ( $httpAsyncInfo.InvokeID ) " - MessageData $webResponse
93+ $Global :HttpResponsesAsync.Remove ($httpAsyncInfo )
94+ continue
95+ }
96+
97+
98+ # Otherwise, get the response stream
99+ $ms = [IO.MemoryStream ]::new()
100+ $null = $webResponse.GetResponseStream ().CopyTo($ms )
101+ $responseBytes = $ms.ToArray () # as a [byte[]].
102+ $ms.Close ()
103+ $ms.Dispose ()
104+
105+
106+ $encoding = # See if the response had an encoding
107+ if ($webResponse.ContentEncoding ) { $webResponse.ContentEncoding }
108+ elseif ($webResponse.CharacterSet ) {
109+ # or a character set.
110+ [Text.Encoding ]::GetEncodings() | Where-Object Name -EQ $webResponse.CharacterSet
111+ }
112+
113+ $webResponseContent =
114+ if ($encoding ) { # If it did, decode the response content.
115+ [IO.StreamReader ]::new([IO.MemoryStream ]::new($responseBytes ), $encoding ).ReadToEnd()
116+ } else {
117+ $null
118+ }
119+
120+ # Add the properties to the web response.
121+ $webResponse |
122+ Add-Member NoteProperty ResponseBytes $webResponseContent - Force - PassThru |
123+ Add-Member NoteProperty ResponseContent $webResponseContent - Force
124+ $webResponse.Close ()
125+
126+ # And send the response with the additional information.
127+ New-Event - SourceIdentifier " HttpRequest.Completed.$ ( $httpAsyncInfo.InvokeID ) " - MessageData $webResponse
128+ $Global :HttpResponsesAsync.Remove ($httpAsyncInfo )
129+ }
130+
131+ if ($Global :HttpResponsesAsync.Count -eq 0 ) {
132+ Get-EventSubscriber - SourceIdentifier " @HttpResponse_Check" | Unregister-Event
133+ }
134+ }
135+
136+ $httpResponseCheckTimer.Start () # Start it's timer.
137+
138+
139+ $httpRequest = [Net.HttpWebRequest ]::CreateHttp($Uri )
140+ $httpRequest.Method = $Method
141+ if ($Header -and $Header.Count ) {
142+ foreach ($kv in $Header.GetEnumerator ()) {
143+ $httpRequest.Headers [$kv.Key ] = $kv.Value
144+ }
145+ }
146+ if ($Body ) {
147+ $requestStream = $httpRequest.GetRequestStream ()
148+
149+ if (-not $requestStream ) { return }
150+ if ($body -is [byte []] -or $Body -as [byte []]) {
151+ [IO.MemoryStream ]::new([byte []]$Body ).CopyTo($requestStream )
152+ }
153+ elseif ($Body -is [string ]) {
154+ [IO.StreamWriter ]::new($requestStream , $TransferEncoding ).Write($Body )
155+ }
156+ else {
157+ [IO.StreamWriter ]::new($requestStream , $TransferEncoding ).Write((ConvertTo-Json - InputObject $body - Depth 100 ))
158+ }
159+ }
160+ $requestId = [Guid ]::NewGuid().ToString()
161+ $httpRequest |
162+ Add-Member NoteProperty SourceIdentifier " HttpRequest.Completed.$requestId " , " HttpRequest.Failed.$requestId " - Force - PassThru |
163+ Add-Member NoteProperty RequestID $requestId - Force - PassThru
164+ }
0 commit comments