Private/Invoke-PSAOAIApiRequest.ps1
|
# This function makes an API request and stores the response function Invoke-PSAOAIApiRequest { <# .SYNOPSIS Sends a POST request to the specified API and stores the response. .DESCRIPTION The Invoke-ApiRequest function sends a POST request to the API specified by the url parameter. It uses the provided headers and bodyJSON for the request. If the request is successful, it returns the response. If an error occurs during the request, it writes bounded diagnostics (when a logfile is provided) and rethrows the error. .PARAMETER url Specifies the URL for the API request. This parameter is mandatory. .PARAMETER headers Specifies the headers for the API request. This parameter is mandatory. .PARAMETER bodyJSON Specifies the body for the API request. This parameter is mandatory. .EXAMPLE Invoke-ApiRequest -url $url -headers $headers -bodyJSON $bodyJSON .OUTPUTS If successful, it outputs the response from the API request. If an error occurs, it throws. #> param( [Parameter(Mandatory = $true)] [string]$url, # The URL for the API request [Parameter(Mandatory = $true)] [hashtable]$headers, # The headers for the API request [Parameter(Mandatory = $true)] [string]$bodyJSON, # The body for the API request [Parameter(Mandatory = $false)] $timeout = 60, [Parameter(Mandatory = $false)] [string]$logfile, [Parameter(Mandatory = $false)] [hashtable]$DiagnosticMetadata ) $job = $null # Try to send the API request and handle any errors try { $job = Start-Job -ScriptBlock { param($url, $headers, $bodyJSON, $timeout) try { $response = Invoke-WebRequest -Uri $url -Method POST -Headers $headers -Body $bodyJSON -TimeoutSec $timeout -ErrorAction Stop -ContentType 'application/json; charset=utf-8' $utf8 = [System.Text.Encoding]::UTF8 $reader = New-Object System.IO.StreamReader($response.RawContentStream, $utf8) $jsonString = $reader.ReadToEnd() $reader.Close() $responseJson = $jsonString | ConvertFrom-Json return $responseJson } catch { $message = $_.Exception.Message $errorBody = $null $statusCode = $null $reasonPhrase = $null $exceptionType = $_.Exception.GetType().FullName if ($_.ErrorDetails -and $_.ErrorDetails.Message) { $errorBody = $_.ErrorDetails.Message } if ($_.Exception.Response) { try { if ($_.Exception.Response.StatusCode) { $statusCode = [int]$_.Exception.Response.StatusCode } } catch { } try { if ($_.Exception.Response.ReasonPhrase) { $reasonPhrase = [string]$_.Exception.Response.ReasonPhrase } } catch { } try { if ((-not $errorBody) -and $_.Exception.Response.Content) { $errorBody = $_.Exception.Response.Content.ReadAsStringAsync().GetAwaiter().GetResult() } } catch { } try { if ((-not $errorBody) -and $_.Exception.Response.GetResponseStream) { $stream = $_.Exception.Response.GetResponseStream() if ($stream) { $reader = New-Object System.IO.StreamReader($stream) $errorBody = $reader.ReadToEnd() $reader.Close() } } } catch { } } return [pscustomobject]@{ __PSAOAIRequestFailed = $true Message = $message ErrorBody = $errorBody StatusCode = $statusCode ReasonPhrase = $reasonPhrase ExceptionType = $exceptionType } } } -ArgumentList $url, $headers, $bodyJSON, $timeout # Write verbose output for the job Write-Verbose ("Job: $($job | ConvertTo-Json)") # Wait for the job to finish while (($job.JobStateInfo.State -eq 'Running') -or ($job.JobStateInfo.State -eq 'NotStarted')) { Write-Host "." -NoNewline -ForegroundColor Blue Start-Sleep -Milliseconds 1000 } Write-Host "" # If the job failed unexpectedly, write the error message and throw if ($job.JobStateInfo.State -eq 'Failed') { $jobFailureMessage = $job.ChildJobs[0].JobStateInfo.Reason.Message if ($logfile) { Write-LogMessage -Message "HTTP request job failed before returning diagnostics: $jobFailureMessage" -Level "ERROR" -LogFile $logfile } throw $jobFailureMessage } # Receive the job result $response = Receive-Job -Id $job.Id -Wait -ErrorAction Stop if ($response -and $response.PSObject.Properties.Name -contains '__PSAOAIRequestFailed' -and $response.__PSAOAIRequestFailed) { $requestId = $null if ($DiagnosticMetadata -and $DiagnosticMetadata.ContainsKey('requestId')) { $requestId = [string]$DiagnosticMetadata['requestId'] } $errorSummary = "HTTP request failed: $($response.Message)" if ($response.StatusCode) { $errorSummary += " (status=$($response.StatusCode)" if ($response.ReasonPhrase) { $errorSummary += ", reason=$($response.ReasonPhrase)" } $errorSummary += ")" } if ($logfile) { $logDirectory = Split-Path -Path $logfile -Parent $logBaseName = [System.IO.Path]::GetFileNameWithoutExtension($logfile) $requestSuffix = if ($requestId) { ".request-$requestId" } else { "" } $errorBodyPath = Join-Path $logDirectory "$logBaseName$requestSuffix.http-error.txt" $errorMetaPath = Join-Path $logDirectory "$logBaseName$requestSuffix.http-error-meta.json" $errorMeta = [ordered]@{ timestamp = (Get-Date).ToString('o') requestId = $requestId url = $url statusCode = $response.StatusCode reasonPhrase = $response.ReasonPhrase exceptionType = $response.ExceptionType message = $response.Message logfile = $logfile errorBodyPath = $errorBodyPath metadata = $DiagnosticMetadata } if ($response.ErrorBody) { Set-Content -Path $errorBodyPath -Value $response.ErrorBody -Encoding UTF8 Write-LogMessage -Message "Diagnostic raw HTTP error body saved to: $errorBodyPath" -LogFile $logfile } else { Write-LogMessage -Message "Diagnostic raw HTTP error body was empty or unavailable." -Level "WARNING" -LogFile $logfile } $errorMeta | ConvertTo-Json -Depth 8 | Set-Content -Path $errorMetaPath -Encoding UTF8 Write-LogMessage -Message "Diagnostic HTTP error metadata saved to: $errorMetaPath" -LogFile $logfile Write-LogMessage -Message $errorSummary -Level "ERROR" -LogFile $logfile } if ($response.ErrorBody) { throw "$errorSummary`n$response.ErrorBody" } throw $errorSummary } if ($logfile) { $requestId = $null if ($DiagnosticMetadata -and $DiagnosticMetadata.ContainsKey('requestId')) { $requestId = [string]$DiagnosticMetadata['requestId'] } $logDirectory = Split-Path -Path $logfile -Parent $logBaseName = [System.IO.Path]::GetFileNameWithoutExtension($logfile) $requestSuffix = if ($requestId) { ".request-$requestId" } else { "" } $successResponsePath = Join-Path $logDirectory "$logBaseName$requestSuffix.http-success-response.json" $successMetaPath = Join-Path $logDirectory "$logBaseName$requestSuffix.http-success-meta.json" $successMeta = [ordered]@{ timestamp = (Get-Date).ToString('o') requestId = $requestId url = $url responseType = if ($null -ne $response) { $response.GetType().FullName } else { $null } topLevelProperties = if ($null -ne $response) { @($response.PSObject.Properties.Name) } else { @() } logfile = $logfile responseBodyPath = $successResponsePath metadata = $DiagnosticMetadata } $response | ConvertTo-Json -Depth 100 | Set-Content -Path $successResponsePath -Encoding UTF8 $successMeta | ConvertTo-Json -Depth 10 | Set-Content -Path $successMetaPath -Encoding UTF8 Write-LogMessage -Message "Diagnostic raw HTTP success response saved to: $successResponsePath" -LogFile $logfile Write-LogMessage -Message "Diagnostic HTTP success metadata saved to: $successMetaPath" -LogFile $logfile } # Write verbose output for the response Write-Verbose ($response | Out-String) # Return the response return $response } # Catch any errors and rethrow after logging catch { if ($logfile) { Write-LogMessage -Message ($_.Exception.Message) -Level "ERROR" -LogFile $logfile } throw } finally { if ($job) { Remove-Job -Id $job.Id -Force -ErrorAction SilentlyContinue } } } |