Private/Invoke-CloudPCGraphRequestWithRetry.ps1
|
function Invoke-CloudPCGraphRequestWithRetry { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('GET','POST','PATCH','DELETE')] [string]$Method, [Parameter(Mandatory)] [string]$Uri, [hashtable]$Headers, [string]$Body, [string]$ContentType, [string]$OutputFilePath, [ValidateRange(0, 10)] [int]$MaxRetryCount = 6, [ValidateRange(1, 3600)] [int]$InitialRetryDelaySeconds = 3, [ValidateRange(1, 3600)] [int]$MaxRetryDelaySeconds = 120 ) begin { } process { $attempt = 0 while ($true) { $params = @{ Method = $Method Uri = $Uri ErrorAction = 'Stop' } if ($Headers) { $params['Headers'] = $Headers } if ($Body) { $params['Body'] = $Body } if ($ContentType) { $params['ContentType'] = $ContentType } if ($OutputFilePath) { $params['OutputFilePath'] = $OutputFilePath } try { Invoke-MgGraphRequest @params return } catch { $statusCode = Get-CloudPCGraphStatusCode -ErrorRecord $_ if ($statusCode -notin @(429, 503, 504) -or $attempt -ge $MaxRetryCount) { throw } $retryAfter = Get-CloudPCGraphRetryAfterDelay -ErrorRecord $_ if ($null -eq $retryAfter) { $retryAfter = [math]::Min( $MaxRetryDelaySeconds, $InitialRetryDelaySeconds * [math]::Pow(2, $attempt) ) } $retryAfter = [int][math]::Max(1, [math]::Min($MaxRetryDelaySeconds, [math]::Ceiling($retryAfter))) Write-Verbose "Graph request was throttled or temporarily unavailable (HTTP $statusCode). Retrying in $retryAfter seconds. Attempt $($attempt + 1) of $MaxRetryCount." Start-Sleep -Seconds $retryAfter $attempt++ } } } end { } } function Get-CloudPCGraphStatusCode { [CmdletBinding()] param( [Parameter(Mandatory)] [System.Management.Automation.ErrorRecord]$ErrorRecord ) $response = $ErrorRecord.Exception.Response if ($response -and $response.PSObject.Properties.Name -contains 'StatusCode') { return [int]$response.StatusCode } if ($ErrorRecord.Exception.PSObject.Properties.Name -contains 'StatusCode') { return [int]$ErrorRecord.Exception.StatusCode } if ($ErrorRecord.Exception.Message -match '\b(429|503|504)\b') { return [int]$Matches[1] } if ($ErrorRecord.Exception.Message -match 'Too\s+Many\s+Requests') { return 429 } $null } function Get-CloudPCGraphRetryAfterDelay { [CmdletBinding()] param( [Parameter(Mandatory)] [System.Management.Automation.ErrorRecord]$ErrorRecord ) $response = $ErrorRecord.Exception.Response $headers = if ($response -and $response.PSObject.Properties.Name -contains 'Headers') { $response.Headers } else { $null } if ($headers) { $retryAfter = $headers.RetryAfter if ($retryAfter) { if ($retryAfter.Delta) { return $retryAfter.Delta.TotalSeconds } if ($retryAfter.Date) { return ([datetimeoffset]$retryAfter.Date).UtcDateTime.Subtract([datetime]::UtcNow).TotalSeconds } } try { $headerValue = $headers['Retry-After'] if ($headerValue) { $seconds = 0 if ([int]::TryParse([string]$headerValue, [ref]$seconds)) { return $seconds } $date = [datetimeoffset]::MinValue if ([datetimeoffset]::TryParse([string]$headerValue, [ref]$date)) { return $date.UtcDateTime.Subtract([datetime]::UtcNow).TotalSeconds } } } catch { Write-Verbose "Could not parse Retry-After header: $($_.Exception.Message)" } } if ($ErrorRecord.Exception.Message -match 'Retry-After:\s*(\d+)') { return [int]$Matches[1] } $null } |