Get-OktaURIRecursive.psm1

$FunctionScriptName = "Get-OktaURIRecursive"
Write-Verbose "Import-Start| [$($FunctionScriptName)]"

#* Dependencies
# N/A

function Get-OktaURIRecursive {
    <#
        .SYNOPSIS
            Get Okta API data - recursive
        .Description
            Get recursive Okta API data
            Uses nextLink property
        .NOTES
            AUTHOR: Ken Dobrunz // Ken.Dobrunz@skaylink.com | Direkt Gruppe
             
            LASTEDIT: 06.05.2024 - Version: 2.3
        #>

    [cmdletbinding()]
    Param(
        [Parameter(Mandatory = $true)]$uri,
        [Parameter()][Alias('filter')]$QueryFilter,
        [Parameter()]$QueryLimit = 1000,
        [Parameter()]$APIkey,
        [Parameter()][Alias('Header_OkktaResponse')]$Header_OktaResponse,
        [Parameter()]$RateLimitMinimumRemaining = 5,
        [Parameter()]$MaxPaginationCount = 1000,
        [Parameter()]$TimeoutSec = 60,
        [Parameter()][switch]$ReturnPaginationObjectsOnly
    )
    Begin {
        $ProgressPreference = 'SilentlyContinue'
        $OktaHeader = @{
            Accept          = "application/json"
            "Content-Type"  = "application/json"
            "Authorization" = "SSWS " + $APIkey
        }
        # Set OktaResponse
        if ($Header_OktaResponse) {
            $OktaHeader.'okta-response' = $Header_OktaResponse
        }

        # Add Query filter
        if($uri -match "\?" -and $QueryFilter){
            $uri = $uri + "&" + $QueryFilter
        }elseif($QueryFilter){
            $uri = $uri + "?" + $QueryFilter
        }

        # Add limit #? Only add if not in uri
        if($uri -match "\?" -and $QueryLimit -and $uri -notlike "*limit=*"){
            $uri = $uri + "&limit=" + $QueryLimit
        }elseif($QueryLimit -and $uri -notlike "*limit=*"){
            $uri = $uri + "?limit=" + $QueryLimit
        }
    }
    process {
        $functionlist = @(); $PaginationCount = 0
        while ($null -ne $uri) {
            # Get response from API
            Try {
                # Get response from OKTA API
                $response = $null
                $response = (Invoke-WebRequest -Method GET -Uri $uri -Headers $OktaHeader -TimeoutSec $TimeoutSec)
                
                # Rate Limit / Pagination
                Write-Debug "Rate Limit: [Remaining: $($response.Headers.'x-rate-limit-remaining') / Limit: $($response.Headers.'x-rate-limit-limit')] - QRY Limit: $($RateLimitMinimumRemaining)"
                if ([int]($response.Headers.'x-rate-limit-remaining')[0] -le $RateLimitMinimumRemaining) {
                    # Wait until "x-rate-limit-reset"
                    $RateLimitWaitSeconds = ($response.Headers.'x-rate-limit-reset')[0] - (([DateTimeOffset](($response.Headers.'date')[0])).ToUnixTimeSeconds())
                    Write-Warning "Waiting for Rate Limit [Remaining: $($response.Headers.'x-rate-limit-remaining') / Limit: $($response.Headers.'x-rate-limit-limit')] - Waiting [$($RateLimitWaitSeconds)] seconds"
                    Start-Sleep -Seconds $RateLimitWaitSeconds
                } else {
                    # Pagination - Skipping current turn to ensure clean data
                    $functionlist = $functionlist + ($response.content | ConvertFrom-Json)
                    if ($null -ne ($response.RelationLink.next)) {
                        $uri = $response.RelationLink.next
                    } else {
                        $uri = $null
                    }
                }
            
            } catch {
                # Check for error & Rate limit
                if ($_.Exception.StatusCode -eq "TooManyRequests") {
                    # Wait until "x-rate-limit-reset"
                    $ErrorHeaderResetTime = ($_.Exception.Response.Headers).GetValues('x-rate-limit-reset')
                    $ErrorHeaderDateTime = ($_.Exception.Response.Headers).GetValues('date')                   
                    $RateLimitWaitSeconds = ($ErrorHeaderResetTime)[0] - (([DateTimeOffset](($ErrorHeaderDateTime)[0])).ToUnixTimeSeconds())
                    Write-Warning "Too Many requests - Waiting [$($RateLimitWaitSeconds)] seconds"
                    Start-Sleep -Seconds $RateLimitWaitSeconds
                } else {
                    $TryError = $_.Exception
                    Write-Error "ERROR: [$($TryError.StatusCode.value__)] $($TryError.StatusCode) @ [$($uri)]"
                    Write-Debug "$($_.Exception.Message)"
                    $uri = $null
                }
            }
            
            # Stop pagination
            $PaginationCount++
            if ($PaginationCount -ge $MaxPaginationCount) {
                $uri = $null
                
                Write-Warning "Paginationlimit of [$MaxPaginationCount] reached - Ignoring additional responses"
                if ($ReturnPaginationObjectsOnly -eq $true) {
                    return $functionlist
                } else {
                    return [ordered]@{
                        Message            = "Paginationlimit of [$MaxPaginationCount] reached - Ignoring additional responses"
                        MaxPaginationCount = $MaxPaginationCount
                        ObjectCount = (($functionlist.count) + 0)
                        Objects            = $functionlist
                    }
                }
            }
        }
        
        return $functionlist
    }
} #v2.3

Export-ModuleMember -Function *
Write-Verbose "Import-END| [$($FunctionScriptName)]"