Private/Invoke-PbiApiRequest.ps1
Function Invoke-PbiApiRequest { <# .SYNOPSIS Sends an HTTP request to a Fabric API endpoint and retrieves the response. Takes care of: authentication, 429 throttling, Long-Running-Operation (LRO) response #> [CmdletBinding()] param( [Parameter(Mandatory = $false)] [System.Security.SecureString] $authToken, [Parameter(Mandatory = $true)] [string] $uri, [Parameter(Mandatory = $false)] [ValidateSet('Get', 'Post', 'Delete', 'Put', 'Patch','GET','POST','DELETE','PUT','PATH')] [string] $method = "Get", [Parameter(Mandatory = $false)] $body, [Parameter(Mandatory = $false)] [string] $contentType = "application/json; charset=utf-8", [Parameter(Mandatory = $false)] [int] $timeoutSec = 240, [Parameter(Mandatory = $false)] [int] $retryCount = 0 ) #$script:fabricToken = $null if ($null -eq $authToken) { $authToken = Get-FabricApiAuthToken } try { $requestUrl = "$($PbiApiUrl)/$uri" Write-Log "Calling $requestUrl" # If need to use -OutFile beware of the following breaking change: https://github.com/PowerShell/PowerShell/issues/20744 # TODO: use -SkipHttpErrorCheck to read the entire error response, need to find a solution to handle 429 errors: https://stackoverflow.com/questions/75629606/powershell-webrequest-handle-response-code-and-exit $ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($authToken) try { $plaintext = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr) $response = Invoke-WebRequest -Headers @{ 'Content-Type' = $contentType; 'Authorization' = "Bearer {0}" -f $plaintext} -Method $method -Uri $requestUrl -Body $body -TimeoutSec $timeoutSec # Perform operations with the contents of $plaintext in this section. } finally { # The following line ensures that sensitive data is not left in memory. [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ssPtr) if ($plaintext) { $plaintext = $null } } $requestId = [string]$response.Headers.requestId Write-Log "RAID: $requestId" $lroFailOrNoResultFlag = $false if ($response.StatusCode -eq 202) { do { $asyncUrl = [string]$response.Headers.Location Write-Log "LRO - Waiting for request to complete in service." Start-Sleep -Seconds 5 $ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($authToken) try { $plaintext = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr) $response = Invoke-WebRequest -Headers @{ 'Content-Type' = $contentType; 'Authorization' = "Bearer {0}" -f $plaintext} -Method Get -Uri $asyncUrl # Perform operations with the contents of $plaintext in this section. } finally { # The following line ensures that sensitive data is not left in memory. [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ssPtr) if ($plaintext) { $plaintext = $null } } $lroStatusContent = $response.Content | ConvertFrom-Json } while ($lroStatusContent.status -ine "succeeded" -and $lroStatusContent.status -ine "failed") if ($lroStatusContent.status -ieq "succeeded") { # Only calls /result if there is a location header, otherwise 'OperationHasNoResult' error is thrown $resultUrl = [string]$response.Headers.Location if ($resultUrl) { $ssPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($authToken) try { $plaintext = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ssPtr) $response = Invoke-WebRequest -Headers @{ 'Content-Type' = $contentType; 'Authorization' = "Bearer {0}" -f $plaintext} -Method Get -Uri $resultUrl # Perform operations with the contents of $plaintext in this section. } finally { # The following line ensures that sensitive data is not left in memory. [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ssPtr) if ($plaintext) { $plaintext = $null } } } else { $lroFailOrNoResultFlag = $true } } else { $lroFailOrNoResultFlag = $true if ($lroStatusContent.error) { throw "LRO API Error: '$($lroStatusContent.error.errorCode)' - $($lroStatusContent.error.message)" } } } Write-Log "Request completed." #if ($response.StatusCode -in @(200,201) -and $response.Content) if (!$lroFailOrNoResultFlag -and $response.Content) { $contentBytes = $response.RawContentStream.ToArray() # Test for BOM if ($contentBytes[0] -eq 0xef -and $contentBytes[1] -eq 0xbb -and $contentBytes[2] -eq 0xbf) { $contentText = [System.Text.Encoding]::UTF8.GetString($contentBytes[0..$contentBytes.Length]) } else { $contentText = $response.Content } $jsonResult = $contentText | ConvertFrom-Json if ($jsonResult.value) { $jsonResult = $jsonResult.value } Write-Output $jsonResult -NoEnumerate } } catch { $ex = $_.Exception $message = $null if ($ex.Response -ne $null) { $responseStatusCode = [int]$ex.Response.StatusCode if ($responseStatusCode -in @(429)) { if ($ex.Response.Headers.RetryAfter) { $retryAfterSeconds = $ex.Response.Headers.RetryAfter.Delta.TotalSeconds + 5 } if (!$retryAfterSeconds) { $retryAfterSeconds = 60 } Write-Log "Exceeded the amount of calls (TooManyRequests - 429), sleeping for $retryAfterSeconds seconds." Start-Sleep -Seconds $retryAfterSeconds $maxRetries = 3 if ($retryCount -le $maxRetries) { Invoke-FabricApiRequest -authToken $authToken -uri $uri -method $method -body $body -contentType $contentType -timeoutSec $timeoutSec -retryCount ($retryCount + 1) } else { throw "Exceeded the amount of retries ($maxRetries) after 429 error." } } else { $apiErrorObj = $ex.Response.Headers | ? { $_.key -ieq "x-ms-public-api-error-code" } | Select -First 1 if ($apiErrorObj) { $apiError = $apiErrorObj.Value[0] if ($apiError -ieq "ItemHasProtectedLabel") { Write-Warning "Item has a protected label." } else { $message = "$($ex.Message); API error code: '$apiError'" throw $message } } } } else { $message = "$($ex.Message)" } if ($message) { throw $message } } } |