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
}