Private/Invoke-TLGraphRequest.ps1
|
function Invoke-TLGraphRequest { <# .SYNOPSIS Wrapper around Invoke-MgGraphRequest with full paging and throttling support. .DESCRIPTION Central Graph request function used by every collector. Follows @odata.nextLink when -All is set, honors Retry-After on HTTP 429 and retries transient 5xx responses with exponential backoff. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Uri, [ValidateSet('GET', 'POST')] [string]$Method = 'GET', [object]$Body, # Follow @odata.nextLink and return the merged item list. [switch]$All, [ValidateRange(0, 10)] [int]$MaxRetries = 5 ) $items = [System.Collections.Generic.List[object]]::new() $nextUri = $Uri while ($nextUri) { $attempt = 0 $response = $null while ($true) { try { $request = @{ Method = $Method Uri = $nextUri OutputType = 'PSObject' ErrorAction = 'Stop' } if ($null -ne $Body) { $request['Body'] = ConvertTo-Json -InputObject $Body -Depth 20 $request['ContentType'] = 'application/json' } $response = Invoke-MgGraphRequest @request break } catch { $exception = $_.Exception $statusCode = $null if ($exception.PSObject.Properties['ResponseStatusCode'] -and $exception.ResponseStatusCode) { $statusCode = [int]$exception.ResponseStatusCode } elseif ($exception.PSObject.Properties['Response'] -and $exception.Response -and $exception.Response.PSObject.Properties['StatusCode']) { $statusCode = [int]$exception.Response.StatusCode } $retryable = ($statusCode -eq 429) -or ($statusCode -ge 500 -and $statusCode -le 599) if (-not $retryable -or $attempt -ge $MaxRetries) { throw } # Exponential backoff, overridden by an explicit Retry-After header. $delaySeconds = [Math]::Min(60, [Math]::Pow(2, $attempt + 1)) $candidates = @($exception) if ($exception.PSObject.Properties['Response'] -and $exception.Response) { $candidates += $exception.Response } foreach ($candidate in $candidates) { if (-not ($candidate.PSObject.Properties['Headers'] -and $candidate.Headers)) { continue } try { $values = @($candidate.Headers.GetValues('Retry-After')) if ($values.Count -gt 0) { $delaySeconds = [double]$values[0]; break } } catch { Write-Debug 'No Retry-After header on this candidate.' } } Write-Verbose ("Graph returned HTTP {0} for '{1}'; retrying in {2}s (attempt {3}/{4})." -f ` $statusCode, $nextUri, $delaySeconds, ($attempt + 1), $MaxRetries) Start-Sleep -Seconds $delaySeconds $attempt++ } } if ($null -ne $response -and $response.PSObject.Properties['value']) { foreach ($item in @($response.value)) { $items.Add($item) } $nextUri = $null if ($All -and $response.PSObject.Properties['@odata.nextLink'] -and $response.'@odata.nextLink') { $nextUri = $response.'@odata.nextLink' } } else { if (-not $All) { return $response } if ($null -ne $response) { $items.Add($response) } $nextUri = $null } } return , $items.ToArray() } |