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 & Ken@Dobrunz.tech LASTEDIT: 07.06.2024 - Version: 2.4.1 #> [cmdletbinding()] Param( [Parameter(Mandatory = $true)]$uri, [Parameter()][Alias('filter')]$QueryFilter, [Parameter()]$QueryLimit = 1000, [Parameter()]$APIkey, [Parameter()][Alias('Header_OkktaResponse')]$Header_OktaResponse, #? Alias = Legacy compatibility [Parameter()]$RateLimitMinimumRemaining = 5, [Parameter()]$MaxPaginationCount = 1000, [Parameter()]$TimeoutSec = 60, [Parameter()][switch]$ReturnPaginationObjectsOnly, [Parameter()][switch]$ReturnContext, [Parameter()][switch]$ReturnContextOnly, [Parameter()][switch]$DontFixQuotationMarks ) Begin { $ProgressPreference = 'SilentlyContinue' # Check APIKey if($APIkey -like "SSWS*"){ Write-Error "Invalid APIKey syntax - Only provide the key!" } # Build Header $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($QueryFilter.Length -gt 0){ if(!$DontFixQuotationMarks){ $QueryFilter = $QueryFilter.Replace("'",'"') } if ($uri -match "\?") { $uri = $uri + "&filter=" + $QueryFilter } else { $uri = $uri + "?filter=" + $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 } # Ensure no NULL pagination if($MaxPaginationCount -eq $null){ $MaxPaginationCount = 1000 } if($TimeoutSec -eq $null){ $TimeoutSec = 60 } $IsErrorRun = $false $RunCount = 1 Write-Verbose "URI: [$uri] @ MaxPagination: [$MaxPaginationCount] with QueryLimit [$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 Write-Verbose "Page: [$RunCount] - MaxPaginationCount: [$MaxPaginationCount]";$RunCount++ $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 { #todo: Error value (.StatusCode) not available (NUll) in LINUX function $TryError = $_.Exception Write-Error "ERROR: [$($TryError.StatusCode.value__)] $($TryError.StatusCode) @ [$($uri)]" Write-Debug "$($_.Exception.Message)" $nexturi = $uri; $uri = $null $IsErrorRun = $true } } # Stop pagination $PaginationCount++ if ($PaginationCount -ge $MaxPaginationCount) { if ($uri -ne $null) { Write-Warning "Paginationlimit of [$MaxPaginationCount] reached - Ignoring additional responses" } $nexturi = $uri; $uri = $null $ReachedMaxPagination = $true } } #* Return as selected if ($IsErrorRun -eq $false) { if (($ReachedMaxPagination -or $ReturnContext -or $ReturnContextOnly)) { #* Build Return if ($ReturnPaginationObjectsOnly -eq $true -and ($ReturnContextOnly -or $ReturnContext)) { #* catch selection error Write-Error "Invalid selection (Can not use PaginationObjectOnly with Context)" return "Invalid selection (Can not use PaginationObjectOnly with Context)" } elseif ($ReturnPaginationObjectsOnly -eq $true) { #* Only Objects return $functionlist } elseif ($ReturnContextOnly) { #* Ignore Pagination and objects $ReturnBody = [ordered]@{ RateLimitTotal = [int]($response.Headers.'x-rate-limit-limit')[0] RateLimitRemaining = [int]($response.Headers.'x-rate-limit-remaining')[0] RateLimitReset = [int]($response.Headers.'x-rate-limit-reset')[0] NextUri = $nexturi } if ($nexturi.Length -gt 0) { $ReturnBody.HasNextLink = $true } else { $ReturnBody.HasNextLink = $false } return $ReturnBody } else { #* Build context using all relevant details # Create Context $ReturnBody = [ordered]@{ } # Add PaginationLimit if ($ReachedMaxPagination) { if ($nexturi.Length -gt 0) { $ReturnBody.Message = "Paginationlimit of [$MaxPaginationCount] reached - Ignoring additional responses" } else { $ReturnBody.Message = "Paginationlimit of [$MaxPaginationCount] reached - All values received" } } # Add Context if ($ReturnContext) { $ReturnBody.RateLimitTotal = [int]($response.Headers.'x-rate-limit-limit')[0] $ReturnBody.RateLimitRemaining = [int]($response.Headers.'x-rate-limit-remaining')[0] $ReturnBody.RateLimitReset = [int]($response.Headers.'x-rate-limit-reset')[0] $ReturnBody.NextUri = $nexturi } if ($nexturi.Length -gt 0) { $ReturnBody.HasNextLink = $true } else { $ReturnBody.HasNextLink = $false } # Add Objects $ReturnBody.ObjectCount = (($functionlist.count) + 0) $ReturnBody.ObjectList = $functionlist return $ReturnBody } } else { # DEFAULT: Return objects return $functionlist } } } } #v2.4.1 Export-ModuleMember -Function * Write-Verbose "Import-END| [$($FunctionScriptName)]" |