Private/Get-KB4NextCursor.ps1
|
function Get-KB4NextCursor { [CmdletBinding()] param( [Parameter(Mandatory)] [pscustomobject] $Response ) $headerCursor = Get-KB4HeaderValue -Headers $Response.Headers -Name @( 'X-Next-Cursor', 'Next-Cursor', 'X-Cursor-Id', 'X-Cursor', 'next-cursor' ) # Prefer cursor hints from headers if KnowBe4 or an intermediary supplies them. if (-not [string]::IsNullOrWhiteSpace([string] $headerCursor) -and [string] $headerCursor -ne 'true') { return [string] $headerCursor } # Some APIs expose the next cursor in a Link header instead of the JSON body. $link = Get-KB4HeaderValue -Headers $Response.Headers -Name @('Link', 'link') if ($link -and [string] $link -match '<([^>]+)>;\s*rel="?next"?') { $nextUri = [uri] $Matches[1] foreach ($pair in $nextUri.Query.TrimStart('?').Split('&', [System.StringSplitOptions]::RemoveEmptyEntries)) { $parts = $pair.Split('=', 2) $name = [System.Uri]::UnescapeDataString($parts[0]) if ($name -eq 'cursor' -and $parts.Count -eq 2) { $cursor = [System.Uri]::UnescapeDataString($parts[1]) if (-not [string]::IsNullOrWhiteSpace($cursor)) { return $cursor } } } } $body = $Response.Body # Keep body parsing flexible because pagination metadata can vary by endpoint. foreach ($propertyPath in @( @('next_cursor'), @('nextCursor'), @('pagination', 'next_cursor'), @('pagination', 'nextCursor'), @('meta', 'next_cursor'), @('meta', 'nextCursor') )) { $value = $body foreach ($propertyName in $propertyPath) { if ($null -eq $value) { break } $property = $value.PSObject.Properties[$propertyName] if (-not $property) { $value = $null break } $value = $property.Value } if (-not [string]::IsNullOrWhiteSpace([string] $value)) { return [string] $value } } $null } |