Class/Class.ps1
class ApiClient { [System.Net.Http.HttpClientHandler]$Handler [System.Net.Http.HttpClient]$Client [System.Collections.Hashtable]$Collector ApiClient() { $this.Handler = [System.Net.Http.HttpClientHandler]::New() $this.Client = [System.Net.Http.HttpClient]::New($this.Handler) $this.Collector = $null } [string] Path([string]$Path) { $Output = if (![IO.Path]::IsPathRooted($Path)) { $FullPath = Join-Path -Path (Get-Location).Path -ChildPath $Path $FullPath = Join-Path -Path $FullPath -ChildPath '.' [IO.Path]::GetFullPath($FullPath) } else { $Path } return $Output } [System.Object] Invoke([System.Object]$Param) { Write-Verbose "[ApiClient.Invoke] $($Param.Method.ToUpper()) $($Param.Path)" if ($Param.Headers) { [string]$Verbose = $Param.Headers.GetEnumerator().foreach{ "$($_.Key)=$($_.Value)" } -join ', ' if ($Verbose) { Write-Verbose "[ApiClient.Invoke] $Verbose" } } try { $Output = if ($Param.Outfile) { @($Param.Headers.Keys).foreach{ $this.Client.DefaultRequestHeaders.Add($_,$Param.Headers.$_) } $Request = $this.Client.GetByteArrayAsync($Param.Path) if ($Request.Result) { [System.IO.File]::WriteAllBytes($this.Path($Param.Outfile),$Request.Result) Write-Verbose "[ApiClient.Invoke] Output directed to '$($Param.Outfile)'." } @($Param.Headers.Keys).foreach{ if ($this.Client.DefaultRequestHeaders.$_) { $this.Client.DefaultRequestHeaders.Remove($_) } } } else { $Message = [System.Net.Http.HttpRequestMessage]::New($Param.Method.ToUpper(),$Param.Path) ($Param.Headers).GetEnumerator().foreach{ $Message.Headers.Add($_.Key,$_.Value) } if ($Param.Formdata) { $Message.Content = [System.Net.Http.MultipartFormDataContent]::New() ($Param.Formdata).GetEnumerator().foreach{ $Verbose = if ($_.Key -match '^(file|upfile)$') { $FileStream = [System.IO.FileStream]::New($this.Path($_.Value), [System.IO.FileMode]::Open) $Filename = [System.IO.Path]::GetFileName($this.Path($_.Value)) $StreamContent = [System.Net.Http.StreamContent]::New($FileStream) $FileType = $this.StreamType($Filename) if ($FileType) { $StreamContent.Headers.ContentType = $FileType } $Message.Content.Add($StreamContent,$_.Key,$Filename) @($_.Key,'<StreamContent>') -join '=' } else { $Message.Content.Add([System.Net.Http.StringContent]::New($_.Value),$_.Key) @($_.Key,$_.Value) -join '=' } Write-Verbose "[ApiClient.Invoke] $($Verbose -join ', ')" } } elseif ($Param.Body) { $Message.Content = if ($Param.Body -is [string] -and $Param.Headers.ContentType) { [System.Net.Http.StringContent]::New($Param.Body,[System.Text.Encoding]::UTF8, $Param.Headers.ContentType) if ($Param.Path -notmatch '/oauth2/token$') { Write-Verbose "[ApiClient.Invoke] $($Param.Body)" } } else { $Param.Body } } if ($this.Collector.Enable -contains 'requests') { $this.Log($Message) } $this.Client.SendAsync($Message) } if ($Output.Result.StatusCode) { Write-Verbose "[ApiClient.Invoke] $(@($Output.Result.StatusCode.GetHashCode(), $Output.Result.StatusCode) -join ': ')" } if ($Output.Result.Headers) { Write-Verbose "[ApiClient.Invoke] $($Output.Result.Headers.GetEnumerator().foreach{ @($_.Key,(@($_.Value) -join ', ')) -join '=' } -join ', ')" ($Output.Result.Headers.GetEnumerator().Where({ $_.Key -match '^X-Api-Deprecation' })).foreach{ Write-Warning ([string]$_.Key,[string]$_.Value -join ': ') } } if ($Output.Result -and $this.Collector.Enable -contains 'responses') { $this.Log($Output.Result) } } catch { throw $_ } return $Output } [void] Log([System.Object]$Object) { $Item = @{ timestamp = Get-Date -Format o attributes = @{ Headers = @{} } } if ($Object -is [System.Net.Http.HttpRequestMessage]) { @('RequestUri','Method').foreach{ $Item.Attributes[$_] = $Object.$_.ToString() } $Object.Headers.GetEnumerator().Where({ $_.Key -ne 'Authorization' }).foreach{ $Item.Attributes.Headers[$_.Key] = $_.Value } if ($Object.Content -is [System.Net.Http.StringContent]) { $Item.Attributes['StringContent'] = ($Object.Content.ReadAsStringAsync().Result -replace 'client_secret=\w+&?','client_secret=redacted') } } elseif ($Object -is [System.Net.Http.HttpResponseMessage]) { $Object.Headers.GetEnumerator().foreach{ $Item.Attributes.Headers[$_.Key] = $_.Value } if ($Object.Content -and ($Object.Content.Headers.ContentType -eq 'application/json' -or $Object.Content.Headers.ContentType.MediaType -eq 'application/json')) { @(($Object.Content.ReadAsStringAsync().Result | ConvertFrom-Json).PSObject.Properties).Where({ $_.Name -ne 'access_token' }).foreach{ $Item.Attributes[$_.Name] = $_.Value } } elseif ($Object.Content) { $Item.Attributes['StringContent'] = $Object.Content.ReadAsStringAsync().Result } } $Job = @{ Name = 'ApiClient_Log',$Item.timestamp -join '.' ScriptBlock = { $Param = $args[0]; Invoke-RestMethod @Param } ArgumentList = @{ Uri = $this.Collector.Uri Method = 'post' Headers = @{ Authorization = 'Bearer',$this.Collector.Token -join ' ' ContentType = 'application/json' } Body = ConvertTo-Json @( @{ tags = @{ host = [System.Net.Dns]::GetHostName() source = $this.Client.DefaultRequestHeaders.UserAgent.ToString() } events = @(,$Item) } ) -Depth 8 -Compress } } [void](Start-Job @Job) Write-Verbose "[ApiClient.Log] Submitted job '$($Job.Name)'." @(Get-Job | Where-Object { $_.Name -match '^ApiClient_Log' -and $_.State -eq 'Completed' }).foreach{ Write-Verbose "[ApiClient.Log] Removed job '$($_.Name)'." Remove-Job -Id $_.Id } } [string] StreamType([string]$String) { [string]$Extension = [System.IO.Path]::GetExtension($String) -replace '^\.',$null $Output = switch -Regex ($Extension) { '^(bmp|gif|jp(e?)g|png)$' { "image/$_" } '^(pdf|zip)$' { "application/$_" } '^7z$' { 'application/x-7z-compressed' } '^(csv|txt)$' { if ($_ -eq 'txt') { 'text/plain' } else { "text/$_" } } '^doc(x?)$' { if ($_ -match 'x$') { 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' } else { 'application/msword' } } '^ppt(x?)$' { if ($_ -match 'x$') { 'application/vnd.openxmlformats-officedocument.presentationml.presentation' } else { 'application/vnd.ms-powerpoint' } } '^xls(x?)$' { if ($_ -match 'x$') { 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' } else { 'application/vnd.ms-excel' } } } return $Output } } |