Public/Helpers/Invoke-VeeamSPCRequest.ps1
|
function Invoke-VeeamSPCRequest { <# .SYNOPSIS Sends a request to the Veeam Service Provider Console REST API. .DESCRIPTION Wraps Invoke-RestMethod / Invoke-WebRequest with auth headers, idempotence token (X-Request-id), automatic offset-based pagination for collection responses, and HTTP 202 / Location handling for asynchronous operations. Supported QueryParams keys (per spec 3.6.2): expand, filter, sort, select, limit, offset. .EXAMPLE Invoke-VeeamSPCRequest -URI 'organizations/companies' -Method Get -QueryParams @{ filter = "name eq 'Acme'"; limit = 50 } #> [CmdletBinding(SupportsShouldProcess)] param ( $URI, $Method, $Body, $QueryParams ) if (!$script:VeeamSPCConnection) { throw 'Use Connect-VeeamSPC first.' } $Headers = @{} + $script:VeeamSPCConnection.Headers $Headers['X-Request-id'] = (New-Guid).Guid $Splat = @{ Method = $Method ContentType = 'application/json' Headers = $Headers } if ($Body) { $Splat.Body = $Body Write-Verbose $Body } $isCore = $PSVersionTable.PSEdition -eq 'Core' if ($script:VeeamSPCConnection.SkipCertificateCheck) { if ($isCore) { $Splat.SkipCertificateCheck = $true } else { [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true } Write-Verbose 'Windows PowerShell: SkipCertificateCheck applied via ServicePointManager callback (process-wide).' } } $URL = [System.UriBuilder]$script:VeeamSPCConnection.Server $URL.Scheme = 'https' $URL.Port = $script:VeeamSPCConnection.Port $URL.Path = Join-URL '/api/v3' $URI $useWebRequest = $Method -ne 'Get' # Honour -WhatIf / -Confirm propagated from caller for mutating verbs only. if ($Method -in 'Post', 'Patch', 'Put', 'Delete') { if (-not $PSCmdlet.ShouldProcess($URL.Uri.OriginalString, $Method)) { return } } $Offset = 0 $MaxIterations = 1000 $Iteration = 0 do { $Iteration++ if ($Iteration -gt $MaxIterations) { Write-Warning "Pagination iteration cap ($MaxIterations) hit on $URI - aborting paginated read." break } $Query = [System.Web.HttpUtility]::ParseQueryString([String]::Empty) # Always send a fixed limit so the server has a predictable page size to honor. $Query.add('limit', 500) if ($Offset -gt 0) { $Query.add('offset', $Offset) } if ($QueryParams) { $QueryParams.GetEnumerator() | ForEach-Object { $Query.add($_.Name, $_.Value) } } $URL.Query = $Query.ToString() $Result = $null try { $prevProgressPreference = $global:ProgressPreference $global:ProgressPreference = 'SilentlyContinue' if ($useWebRequest) { $response = Invoke-WebRequest @Splat -Uri $URL.Uri.OriginalString -UseBasicParsing if ($response.StatusCode -eq 202) { $location = $response.Headers['Location'] if ($location -is [array]) { $location = $location[0] } $asyncUid = $null if ($location -match '/asyncActions/([0-9a-fA-F-]+)') { $asyncUid = $Matches[1] } [pscustomobject]@{ AsyncActionUid = $asyncUid Location = $location StatusCode = 202 } $global:ProgressPreference = $prevProgressPreference return } if ($response.Content) { $Result = $response.Content | ConvertFrom-Json } } else { $Result = Invoke-RestMethod @Splat -Uri $URL.Uri.OriginalString } $global:ProgressPreference = $prevProgressPreference } catch { $global:ProgressPreference = $prevProgressPreference if ($_.Exception.Message -eq 'The remote server returned an error: (429) Too Many Requests.') { $PSCmdlet.ThrowTerminatingError( [System.Management.Automation.ErrorRecord]::new( 'Throttled: Look at changing these settings: https://helpcenter.veeam.com/docs/vac/rest/throttling_config.html', $_.Exception.Message, [System.Management.Automation.ErrorCategory]::ProtocolError, $null ) ) } else { $PSCmdlet.ThrowTerminatingError($_) } } if ($Result.meta.pagingInfo) { Write-Verbose $Result.meta.pagingInfo } # Detect non-advancing pagination: if the server echoes back an offset that doesn't match # what we sent, or the next offset wouldn't advance, break instead of spinning forever # (some filter/expand combos cause the server to ignore offset and re-serve the first page). $ServerOffset = $Result.meta.pagingInfo.offset if ($null -ne $ServerOffset -and $Offset -gt 0 -and $ServerOffset -ne $Offset) { Write-Warning "VSPC pagination ignored offset on $URI (sent $Offset, server returned $ServerOffset). Returning current page only to avoid duplicate results." $Result.data break } $NextOffset = $Result.meta.pagingInfo.count + $Result.meta.pagingInfo.offset if ($NextOffset -le $Offset) { $Result.data break } $Offset = $NextOffset $Result.data } while ($Result.meta -and $Result.meta.pagingInfo.count + $Result.meta.pagingInfo.offset -lt $Result.meta.pagingInfo.total) } |