Private/Get-NextLink.ps1

function Get-NextLink {
    <#
.SYNOPSIS
Follows & returns values from nextLink URLs

.DESCRIPTION
CyberArk API sometimes provides an object containing a `nextLink` property.
Function follows the URL provided as the value for `nextLink`.
Provides back all results obtained when following `nextLink`.

Some CyberArk APIs instead only provide a `totalCount`/`Total` value, with no
nextLink/nextCursor property. For these, pass the URI used for the initial
request via the RequestUri parameter; subsequent pages are requested by
incrementing an `offset` query parameter against that URI until the reported
total number of results have been collected.

.PARAMETER InitialResult
The value of the initial result containing the `nextLink` property

.PARAMETER BaseURI
The BaseURI to use for the relative NextLink query.
If not specified defaults to $psPASSession.BaseURI

.PARAMETER SavedFilter
A value matching a configured Saved Filters

.PARAMETER RequestUri
The URI used for the initial request.
Required to page through results using an incrementing `offset` query parameter
when the API response only provides a `totalCount` value and no nextLink/nextCursor
property.

.PARAMETER TimeoutSec
See Invoke-WebRequest
Specify a timeout value in seconds

.EXAMPLE
$Result | Get-NextLink

Where result has a `nextLink` property, processes each nextlink and returns results.

.EXAMPLE
$Result | Get-NextLink -RequestUri $URI

Where result has a `totalCount` property but no nextLink/nextCursor, requests
successive pages from $URI via an incrementing offset query parameter.
#>

    [CmdletBinding()]
    param(
        [parameter(
            Mandatory = $false,
            ValueFromPipeline = $true
        )]
        $InitialResult,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string]$SavedFilter,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true,
            ParameterSetName = 'BaseURI'
        )]
        [string]$BaseURI,

        [parameter(
            Mandatory = $false,
            ValueFromPipelinebyPropertyName = $true
        )]
        [string]$RequestUri,

        [parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $false
        )]
        [int]$TimeoutSec
    )

    begin {
        switch ($PSCmdlet.ParameterSetName) {
            'BaseURI' {
                #SH & PCloud BaseURI is sometimes different
                #*Allow PCloud to be specified via parameter
            }
            default {
                #*but default here to the standard
                $BaseURI = $psPASSession.BaseURI
            }
        }
    }

    process {

        $LinkProperty = $null
        $ResultProperty = $null
        $TotalCountMode = $false

        switch ($InitialResult) {
            #SH & PCloud result and nextLink property names differ
            #*Figure out what properties we are dealing with here
            { $null -ne $PSItem.value } {
                $ResultProperty = 'value'
            }

            { $null -ne $PSItem.nextLink } {
                $LinkProperty = 'NextLink'
            }

            { $null -ne $PSItem.items } {
                $ResultProperty = 'items'
            }

            { $null -ne $PSItem.nextCursor } {
                $LinkProperty = 'nextCursor'
            }
        }

        if (($null -eq $LinkProperty) -and ($null -eq $ResultProperty)) {

            #API may only provide a totalCount/Total value, with no nextLink/nextCursor.
            #Figure out which (if either) of these count property names is present.
            $CountProperty = 'totalCount', 'Total' | Where-Object { $null -ne $InitialResult.$PSItem } | Select-Object -First 1

            if ($null -ne $CountProperty) {

                #page via an incrementing offset query parameter instead of a nextLink/nextCursor.
                #Find the property holding the returned items.
                $ResultProperty = $InitialResult.PSObject.Properties |
                    Where-Object { ($PSItem.Name -ne $CountProperty) -and ($PSItem.Value -is [System.Collections.IEnumerable]) -and ($PSItem.Value -isnot [string]) } |
                    Select-Object -First 1 -ExpandProperty Name

                $TotalCount = $InitialResult.$CountProperty
                $TotalCountMode = $true

            }

        }

        #Proceed if there are results
        if (($null -ne $InitialResult.${ResultProperty}) -and ($InitialResult.${ResultProperty}.Count -gt 0)) {

            #store list of initial query results
            $Result = [Collections.Generic.List[Object]]::New(($InitialResult.${ResultProperty}))

            #!SavedFilter is not inclduded in NextLink value.
            #* Create a query parameter for SavedFilter to include in URL
            $queryString = $PSBoundParameters | Get-PASParameter -ParametersToKeep SavedFilter | ConvertTo-QueryString

            if ($TotalCountMode) {

                #page via an incrementing offset query parameter against the original request URI
                while ($Result.Count -lt $TotalCount) {

                    $Separator = if ($RequestUri -match '\?') { '&' } else { '?' }
                    $URI = "$RequestUri${Separator}offset=$($Result.Count)"

                    if ($null -ne $queryString) {

                        $URI = "$URI&$queryString"

                    }

                    $NextResult = Invoke-PASRestMethod -Uri $URI -Method GET -TimeoutSec $TimeoutSec
                    $PageResults = $NextResult.$ResultProperty

                    if (($null -eq $PageResults) -or ($PageResults.Count -eq 0)) {

                        break

                    }

                    $null = $Result.AddRange($PageResults)

                }

            } else {

                #iterate any nextLinks
                $NextLink = $InitialResult.$LinkProperty

                while ( $null -ne $NextLink ) {

                    $URI = "$BaseURI/$NextLink"

                    #*If there is a SavedFilter querystring, append it to the URL
                    if ($null -ne $queryString) {

                        #Build URL from base URL/NextLink
                        $URI = "$URI`&$queryString"

                    }

                    $NextResult = Invoke-PASRestMethod -Uri $URI -Method GET -TimeoutSec $TimeoutSec
                    $NextLink = $NextResult.$LinkProperty
                    $null = $Result.AddRange(($NextResult.$ResultProperty))

                }

            }

            #return list
            $Result

        }

    }

    end {}

}