Private/Invoke-MgGraphRequestWithRetry.ps1
|
function Invoke-MgGraphRequestWithRetry { <# .SYNOPSIS Wraps Invoke-MgGraphRequest with automatic retry on HTTP 429 (throttling). .DESCRIPTION Manual pagination loops over large tenants can be throttled by Graph. Without a retry, the catch block typically logs a warning and continues, silently returning incomplete results. This helper retries on 429, honoring the Retry-After header when present and otherwise backing off exponentially (capped at 60s). Any non-429 error is rethrown unchanged. .PARAMETER Uri The Graph request URI. .PARAMETER Method HTTP method (default GET). .PARAMETER Body Optional request body. .PARAMETER Headers Optional request headers, for the calls that need one (ConsistencyLevel on advanced queries). .PARAMETER ContentType Optional request content type, for the calls that send a body. .PARAMETER OutputType Optional Invoke-MgGraphRequest output type. Left unset, the SDK default applies, so existing callers are unaffected. Passing Text is what a $count endpoint needs, since it answers with a bare number and not with JSON. .PARAMETER MaxRetries Maximum number of retry attempts on 429 (default 5). #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Uri, [Parameter()] [string]$Method = 'GET', [Parameter()] $Body, [Parameter()] [hashtable]$Headers, [Parameter()] [string]$ContentType, [Parameter()] [string]$OutputType, [Parameter()] [int]$MaxRetries = 5 ) $attempt = 0 while ($true) { try { $params = @{ Method = $Method; Uri = $Uri; ErrorAction = 'Stop' } foreach ($passthrough in @('Body', 'Headers', 'ContentType', 'OutputType')) { if ($PSBoundParameters.ContainsKey($passthrough)) { $params[$passthrough] = $PSBoundParameters[$passthrough] } } return Invoke-MgGraphRequest @params } catch { # Best-effort status-code extraction across SDK/exception shapes. $statusCode = $null foreach ($candidate in @( { [int]$_.Exception.Response.StatusCode }, { [int]$_.Exception.ResponseStatusCode }, { [int]$_.Exception.StatusCode } )) { try { $value = & $candidate if ($value) { $statusCode = $value; break } } catch { } } if ($statusCode -eq 429 -and $attempt -lt $MaxRetries) { $attempt++ $retryAfter = 0 try { $retryAfter = [int]$_.Exception.Response.Headers.RetryAfter.Delta.TotalSeconds } catch { } if (-not $retryAfter -or $retryAfter -lt 1) { $retryAfter = [int][math]::Min([math]::Pow(2, $attempt), 60) } Write-Warning "Microsoft Graph throttled the request (429). Retry $attempt/$MaxRetries after $retryAfter second(s)." Start-Sleep -Seconds $retryAfter continue } throw } } } |