Private/Invoke-SPMWithRetry.ps1
|
function Invoke-SPMProgress { <# .SYNOPSIS Writes scan progress to the console AND to an optional caller-supplied callback ($script:SPMProgressAction) so hosts like the cloud worker can surface live status. #> [CmdletBinding()] param( [int]$Id = 2, [int]$ParentId = -1, [string]$Activity = 'Scanning structure', [string]$Status, [int]$PercentComplete = -1, [switch]$Completed ) if ($Completed) { Write-Progress -Id $Id -Activity $Activity -Completed return } $wp = @{ Id = $Id; Activity = $Activity; Status = $Status } if ($ParentId -ge 0) { $wp.ParentId = $ParentId } if ($PercentComplete -ge 0) { $wp.PercentComplete = $PercentComplete } Write-Progress @wp if ($script:SPMProgressAction) { try { & $script:SPMProgressAction $Status } catch { Write-Verbose "Progress callback failed: $($_.Exception.Message)" } } } function Invoke-SPMWithRetry { <# .SYNOPSIS Executes a script block and retries on SharePoint/Graph throttling (HTTP 429/503), honoring Retry-After. #> [CmdletBinding()] param( [Parameter(Mandatory, Position = 0)] [scriptblock]$ScriptBlock, [int]$MaxRetries = 6 ) $attempt = 0 while ($true) { try { return & $ScriptBlock } catch { $attempt++ $status = $null $retryAfter = $null $response = $_.Exception.Response if ($response) { try { $status = [int]$response.StatusCode } catch { $status = $null } try { $values = @($response.Headers.GetValues('Retry-After')) if ($values.Count -gt 0) { $retryAfter = $values[0] } } catch { $retryAfter = $null } } if (-not $status -and $_.Exception.Message -match '(?i)429|too many requests|throttl|503|server too busy') { $status = 429 } if (($status -in 429, 503) -and $attempt -le $MaxRetries) { $delay = [Math]::Min(10 * $attempt, 120) $parsed = 0 if ($retryAfter -and [int]::TryParse([string]$retryAfter, [ref]$parsed) -and $parsed -gt 0) { $delay = $parsed } Write-Warning "Throttled (HTTP $status) - attempt $attempt/$MaxRetries, waiting $delay seconds..." Start-Sleep -Seconds $delay continue } throw } } } |