internal/functions/AzAPICall.ps1

function AzAPICall
{
    <#
    .SYNOPSIS
        Send request against API call and handle auth and errors
     
    .DESCRIPTION
        Send request against API call and handle auth and errors
     
    .PARAMETER uri
        url from API Endpoint
 
    .PARAMETER Method
        Method for api request
     
    .PARAMETER currentTask
        For debuging help
     
    .PARAMETER listenON
        some Endpoint return different Outputs
     
    .PARAMETER body
        Add a body to the API Call
         
    .EXAMPLE
        PS C:\> AzAPICall -uri 'https://graph.microsoft.com/beta/directoryRoles' -Method Get -currentTask "Collecting AADDirectoryRoles"
 
        Get all AzureAD Directory Roles
    #>

    [cmdletbinding()]
    param(
        [Parameter(Mandatory = $true)][string]$uri,

        [Parameter(Mandatory = $true)][ValidateSet("GET","PUT","DELETE","POST","PATCH","HEAD")][string]$method,

        [Parameter(Mandatory = $false)][string]$currentTask = "DefaultTask",

        [Parameter(Mandatory = $false)][ValidateSet("Content","ContentProperties","CSV","StatusCode")][string]$listenOn,

        [Parameter(Mandatory = $false)]$body,

        [Parameter(Mandatory = $false)]$consistencylevel
    )

    $tryCounter = 0
    $tryCounterUnexpectedError = 0
    $retryAuthorizationFailed = 5
    $retryAuthorizationFailedCounter = 0
    $apiCallResultsCollection = [System.Collections.ArrayList]@()
    $initialUri = $uri
    $restartDueToDuplicateNextlinkCounter = 0
    if ($htParameters.DebugAzAPICall -eq $true) {
        if ($caller -like "CustomDataCollection*") {
            $debugForeGroundColors = @('DarkBlue', 'DarkGreen', 'DarkCyan', 'Cyan', 'DarkMagenta', 'DarkYellow', 'Blue', 'Magenta', 'Yellow', 'Green')
            $debugForeGroundColorsCount = $debugForeGroundColors.Count
            $randomNumber = Get-Random -Minimum 0 -Maximum ($debugForeGroundColorsCount - 1)
            $debugForeGroundColor = $debugForeGroundColors[$randomNumber]
        }
        else {
            $debugForeGroundColor = "Cyan"
        }
    }
    Set-AzApiCallEnvironment
    do {
        if ($script:arrayAzureManagementEndPointUrls | Where-Object { $uri -match $_ }) {
            $targetEndpoint = "AzManagementAPI"
            #check if valid Token exist
            checkToken -targetEndpoint $targetEndpoint
            $bearerToUse = $script:htBearerAccessToken.$targetEndpoint.AccessToken
        }
        elseif ($uri -like "*dev.azure*") {
            $targetEndpoint = "AzDevOps"
            #check if valid Token exist
            checkToken -targetEndpoint $targetEndpoint
            $bearerToUse = $script:htBearerAccessToken.$targetEndpoint.AccessToken
        }
        elseif ($uri -like "*api.powerbi*") {
            $targetEndpoint = "MsPowerBi"
            #check if valid Token exist
            checkToken -targetEndpoint $targetEndpoint
            $bearerToUse = $script:htBearerAccessToken.$targetEndpoint.AccessToken
        }
        elseif ($uri -like "*graph.microsoft*"){
            $targetEndpoint = "MsGraphAPI"
            #check if valid Token exist
            checkToken -targetEndpoint $targetEndpoint
            $bearerToUse = $script:htBearerAccessToken.$targetEndpoint.AccessToken
        }
        else {
            Throw "No valid Endpoint! Check URL or provide an issue to https://github.com/JulianHayward/AzAPICall/issues"
        }

        #API Call Tracking
        $tstmp = (Get-Date -format "yyyyMMddHHmmssms")
        $null = $script:arrayAPICallTracking.Add([PSCustomObject]@{
                CurrentTask                          = $currentTask
                TargetEndpoint                       = $targetEndpoint
                Uri                                  = $uri
                Method                               = $method
                TryCounter                           = $tryCounter
                TryCounterUnexpectedError            = $tryCounterUnexpectedError
                RetryAuthorizationFailedCounter      = $retryAuthorizationFailedCounter
                RestartDueToDuplicateNextlinkCounter = $restartDueToDuplicateNextlinkCounter
                TimeStamp                            = $tstmp
            })
        
        if ($caller -eq "CustomDataCollection") {
            $null = $script:arrayAPICallTrackingCustomDataCollection.Add([PSCustomObject]@{
                    CurrentTask                          = $currentTask
                    TargetEndpoint                       = $targetEndpoint
                    Uri                                  = $uri
                    Method                               = $method
                    TryCounter                           = $tryCounter
                    TryCounterUnexpectedError            = $tryCounterUnexpectedError
                    RetryAuthorizationFailedCounter      = $retryAuthorizationFailedCounter
                    RestartDueToDuplicateNextlinkCounter = $restartDueToDuplicateNextlinkCounter
                    TimeStamp                            = $tstmp
                })
        }

        $Header = @{
            "Content-Type" = "application/json";
            "Authorization" = "Bearer $bearerToUse"
        }
        if ($consistencylevel) {
            $Header = @{
                "Content-Type" = "application/json";
                "Authorization" = "Bearer $bearerToUse";
                "consistencylevel" = "$consistencylevel"
            }
        }

        $unexpectedError = $false
        $tryCounter++
        if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUGTASK: attempt#$($tryCounter) processing: $($currenttask)" -ForegroundColor $debugForeGroundColor }
        try {
            if ($body) {
                #write-host "has BODY"
                $azAPIRequest = Invoke-WebRequest -Uri $uri -Method $method -body $body -Headers $Header -UseBasicParsing
            }
            else {
                $azAPIRequest = Invoke-WebRequest -Uri $uri -Method $method -Headers $Header -UseBasicParsing
            }
        }
        catch {
            try {
                if ($Method -like "Head" -and $targetEndpoint -eq "AzManagementAPI") {
                    $catchResult = $_.Exception.Response
                }
                else {
                    $catchResultPlain = $_.ErrorDetails.Message
                    $catchResult = ($catchResultPlain | ConvertFrom-Json -ErrorAction SilentlyContinue)
                }
            }
            catch {
                $catchResult = $catchResultPlain
                $tryCounterUnexpectedError++
                $unexpectedError = $true
            }
        }

        if ($unexpectedError -eq $false) {
            if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: unexpectedError: false" -ForegroundColor $debugForeGroundColor }
            if ($azAPIRequest.StatusCode -eq 203 -and $targetEndPoint -eq "AZDevOps") {
                Write-Host "Debug: get devops token or use Private Access Token -> Unauthorize: HTTP Code 203"
                createBearerToken -targetEndPoint $targetEndpoint
            }
            if ($azAPIRequest.StatusCode -notin 200..204) {
                if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: apiStatusCode: $($azAPIRequest.StatusCode)" -ForegroundColor $debugForeGroundColor }
                if ($catchResult.error.code -like "*GatewayTimeout*" -or
                    $catchResult.error.code -like "*BadGatewayConnection*" -or
                    $catchResult.error.code -like "*InvalidGatewayHost*" -or
                    $catchResult.error.code -like "*ServerTimeout*" -or
                    $catchResult.error.code -like "*ServiceUnavailable*" -or
                    $catchResult.code -like "*ServiceUnavailable*" -or
                    $catchResult.error.code -like "*MultipleErrorsOccurred*" -or
                    $catchResult.code -like "*InternalServerError*" -or
                    $catchResult.error.code -like "*InternalServerError*" -or
                    $catchResult.error.code -like "*RequestTimeout*" -or
                    $catchResult.error.code -like "*AuthorizationFailed*" -or
                    $catchResult.error.code -like "*ExpiredAuthenticationToken*" -or
                    $catchResult.error.code -like "*Authentication_ExpiredToken*" -or
                    $catchResult.error.code -like "*ResponseTooLarge*" -or
                    $catchResult.error.code -like "*InvalidAuthenticationToken*" -or
                    $catchResult.error.message -like "*The offer MS-AZR-0110P is not supported*" -or
                    $catchResult.error.code -like "*UnknownError*" -or
                    $catchResult.error.code -eq "500" -or
                    $catchResult.error.code -like "*throttled*") {
                    if ($catchResult.error.code -like "*ResponseTooLarge*") {
                        Write-Host "###### LIMIT #################################"
                        Write-Host "Hitting LIMIT getting Policy Compliance States!"
                        Write-Host "ErrorCode: $($catchResult.error.code)"
                        Write-Host "ErrorMessage: $($catchResult.error.message)"
                        Write-Host "There is nothing we can do about this right now. Please run AzGovViz with the following parameter: '-NoPolicyComplianceStates'." -ForegroundColor Yellow
                        Write-Host "Impact using parameter '-NoPolicyComplianceStates': only policy compliance states will not be available in the various AzGovViz outputs - all other output remains." -ForegroundColor Yellow
                        break # Break Script
                    }
                    if ($catchResult.error.message -like "*The offer MS-AZR-0110P is not supported*") {
                        Write-Host " $currentTask - try #$tryCounter; returned: <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - seems we´re hitting a malicious endpoint .. try again in $tryCounter second(s)"
                        Start-Sleep -Seconds $tryCounter
                    }
                    if ($catchResult.error.code -like "*GatewayTimeout*" -or $catchResult.error.code -like "*BadGatewayConnection*" -or $catchResult.error.code -like "*InvalidGatewayHost*" -or $catchResult.error.code -like "*ServerTimeout*" -or $catchResult.error.code -like "*ServiceUnavailable*" -or $catchResult.code -like "*ServiceUnavailable*" -or $catchResult.error.code -like "*MultipleErrorsOccurred*" -or $catchResult.code -like "*InternalServerError*" -or $catchResult.error.code -like "*InternalServerError*" -or $catchResult.error.code -like "*RequestTimeout*" -or $catchResult.error.code -like "*UnknownError*" -or $catchResult.error.code -eq "500") {
                        Write-Host " $currentTask - try #$tryCounter; returned: <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - try again in $tryCounter second(s)"
                        Start-Sleep -Seconds $tryCounter
                    }
                    if ($catchResult.error.code -like "*AuthorizationFailed*") {
                        if ($retryAuthorizationFailedCounter -gt $retryAuthorizationFailed) {
                            Write-Host " $currentTask - try #$tryCounter; returned: '$($catchResult.error.code)' | '$($catchResult.error.message)' - $retryAuthorizationFailed retries failed - investigate that error!/exit"
                            Throw "Error - check the last console output for details"
                        }
                        else {
                            if ($retryAuthorizationFailedCounter -gt 2) {
                                Start-Sleep -Seconds 5
                            }
                            if ($retryAuthorizationFailedCounter -gt 3) {
                                Start-Sleep -Seconds 10
                            }
                            Write-Host " $currentTask - try #$tryCounter; returned: '$($catchResult.error.code)' | '$($catchResult.error.message)' - not reasonable, retry #$retryAuthorizationFailedCounter of $retryAuthorizationFailed"
                            $retryAuthorizationFailedCounter ++
                        }
                    }
                    if ($catchResult.error.code -like "*ExpiredAuthenticationToken*" -or $catchResult.error.code -like "*Authentication_ExpiredToken*" -or $catchResult.error.code -like "*InvalidAuthenticationToken*") {
                        Write-Host " $currentTask - try #$tryCounter; returned: '$($catchResult.error.code)' | '$($catchResult.error.message)' - requesting new bearer token ($targetEndpoint)"
                        createBearerToken -targetEndPoint $targetEndpoint
                    }
                    if ($catchResult.error.code -like "*throttled*") {
                        Write-Host " $currentTask - try #$tryCounter; returned: <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - try again"
                        Write-Output "Waiting for Azure API Throttling Limits"
                        Start-Sleep -Seconds 11 #MOST API´s had counters Around 10 Secounds for next API Call without Throttling.
                    }
                }
                elseif ($catchResult.StatusCode.value__ -like "404") {
                    Throw "Information - Ressource didnt exist"
                }
                else {
                    if (-not $catchResult.code -and -not $catchResult.error.code -and -not $catchResult.message -and -not $catchResult.error.message -and -not $catchResult -and $tryCounter -lt 6){
                        $sleepSec = @(3, 7, 12, 20, 30, 45)[$tryCounter]
                        Write-Host " $currentTask - try #$tryCounter; returned: <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) try again in $sleepSec second(s)"
                        Start-Sleep -Seconds $sleepSec

                    }
                    else{
                        Write-Host " $currentTask - try #$tryCounter; returned: <.code: '$($catchResult.code)'> <.error.code: '$($catchResult.error.code)'> | <.message: '$($catchResult.message)'> <.error.message: '$($catchResult.error.message)'> - (plain : $catchResult) investigate that error!/exit"
                        Throw "Error - check the last console output for details"
                    }

                }
            }
            else {
                if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: apiStatusCode: $($azAPIRequest.StatusCode)" -ForegroundColor $debugForeGroundColor }
                $azAPIRequestConvertedFromJson = ($azAPIRequest.Content | ConvertFrom-Json)
                if ($listenOn -eq "StatusCode") {
                    $apiCallResultsCollection.Add("Azure Resource exist!") | Out-Null
                }
                if ($listenOn -eq "CSV") {
                    $azAPIRequestConvertedFromCSV = ($azAPIRequest.Content | ConvertFrom-csv)
                    $apiCallResultsCollection.Add($azAPIRequestConvertedFromCSV.Content)
                }
                if ($listenOn -eq "Content") {
                    if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: listenOn=content ($((($azAPIRequestConvertedFromJson) | Measure-Object).count))" -ForegroundColor $debugForeGroundColor }
                    $null = $apiCallResultsCollection.Add($azAPIRequestConvertedFromJson)
                }
                elseif ($listenOn -eq "ContentProperties") {
                    if (($azAPIRequestConvertedFromJson.properties.rows | Measure-Object).Count -gt 0) {
                        foreach ($consumptionline in $azAPIRequestConvertedFromJson.properties.rows) {
                            $null = $apiCallResultsCollection.Add([PSCustomObject]@{
                                    "$($azAPIRequestConvertedFromJson.properties.columns.name[0])" = $consumptionline[0]
                                    "$($azAPIRequestConvertedFromJson.properties.columns.name[1])" = $consumptionline[1]
                                    SubscriptionMgPath                                             = $htSubscriptionsMgPath.($consumptionline[1]).ParentNameChain
                                    "$($azAPIRequestConvertedFromJson.properties.columns.name[2])" = $consumptionline[2]
                                    "$($azAPIRequestConvertedFromJson.properties.columns.name[3])" = $consumptionline[3]
                                    "$($azAPIRequestConvertedFromJson.properties.columns.name[4])" = $consumptionline[4]
                                    "$($azAPIRequestConvertedFromJson.properties.columns.name[5])" = $consumptionline[5]
                                    "$($azAPIRequestConvertedFromJson.properties.columns.name[6])" = $consumptionline[6]
                                })
                        }
                    }
                }
                else {
                    if (($azAPIRequestConvertedFromJson).value) {
                        if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: listenOn=default(value) value exists ($((($azAPIRequestConvertedFromJson).value | Measure-Object).count))" -ForegroundColor $debugForeGroundColor }
                        $null = $apiCallResultsCollection.AddRange($azAPIRequestConvertedFromJson.value)
                    }
                    else {
                        if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: listenOn=default(value) value not exists; return empty array" -ForegroundColor $debugForeGroundColor }
                    }
                }

                $isMore = $false
                if ($azAPIRequestConvertedFromJson.nextLink) {
                    $isMore = $true
                    if ($uri -eq $azAPIRequestConvertedFromJson.nextLink) {
                        if ($restartDueToDuplicateNextlinkCounter -gt 3) {
                            Write-Host " $currentTask restartDueToDuplicateNextlinkCounter: #$($restartDueToDuplicateNextlinkCounter) - Please report this error/exit"
                            Throw "Error - check the last console output for details"
                        }
                        else {
                            $restartDueToDuplicateNextlinkCounter++
                            Write-Host "nextLinkLog: uri is equal to nextLinkUri"
                            Write-Host "nextLinkLog: uri: $uri"
                            Write-Host "nextLinkLog: nextLinkUri: $($azAPIRequestConvertedFromJson.nextLink)"
                            Write-Host "nextLinkLog: re-starting (#$($restartDueToDuplicateNextlinkCounter)) '$currentTask'"
                            $apiCallResultsCollection = [System.Collections.ArrayList]@()
                            $uri = $initialUri
                            Start-Sleep -Seconds 1
                            createBearerToken -targetEndPoint $targetEndpoint
                            Start-Sleep -Seconds 1
                        }
                    }
                    else {
                        $uri = $azAPIRequestConvertedFromJson.nextLink
                    }
                    if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: nextLink: $Uri" -ForegroundColor $debugForeGroundColor }
                }
                elseIf ($azAPIRequestConvertedFromJson."@oData.nextLink") {
                    $isMore = $true
                    if ($uri -eq $azAPIRequestConvertedFromJson."@odata.nextLink") {
                        if ($restartDueToDuplicateNextlinkCounter -gt 3) {
                            Write-Host " $currentTask restartDueToDuplicate@odataNextlinkCounter: #$($restartDueToDuplicateNextlinkCounter) - Please report this error/exit"
                            Throw "Error - check the last console output for details"
                        }
                        else {
                            $restartDueToDuplicateNextlinkCounter++
                            Write-Host "nextLinkLog: uri is equal to @odata.nextLinkUri"
                            Write-Host "nextLinkLog: uri: $uri"
                            Write-Host "nextLinkLog: @odata.nextLinkUri: $($azAPIRequestConvertedFromJson."@odata.nextLink")"
                            Write-Host "nextLinkLog: re-starting (#$($restartDueToDuplicateNextlinkCounter)) '$currentTask'"
                            $apiCallResultsCollection = [System.Collections.ArrayList]@()
                            $uri = $initialUri
                            Start-Sleep -Seconds 1
                            createBearerToken -targetEndPoint $targetEndpoint
                            Start-Sleep -Seconds 1
                        }
                    }
                    else {
                        $uri = $azAPIRequestConvertedFromJson."@odata.nextLink"
                    }
                    if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: @oData.nextLink: $Uri" -ForegroundColor $debugForeGroundColor }
                }
                elseif ($azAPIRequestConvertedFromJson.properties.nextLink) {
                    $isMore = $true
                    if ($uri -eq $azAPIRequestConvertedFromJson.properties.nextLink) {
                        if ($restartDueToDuplicateNextlinkCounter -gt 3) {
                            Write-Host " $currentTask restartDueToDuplicateNextlinkCounter: #$($restartDueToDuplicateNextlinkCounter) - Please report this error/exit"
                            Throw "Error - check the last console output for details"
                        }
                        else {
                            $restartDueToDuplicateNextlinkCounter++
                            Write-Host "nextLinkLog: uri is equal to nextLinkUri"
                            Write-Host "nextLinkLog: uri: $uri"
                            Write-Host "nextLinkLog: nextLinkUri: $($azAPIRequestConvertedFromJson.properties.nextLink)"
                            Write-Host "nextLinkLog: re-starting (#$($restartDueToDuplicateNextlinkCounter)) '$currentTask'"
                            $apiCallResultsCollection = [System.Collections.ArrayList]@()
                            $uri = $initialUri
                            Start-Sleep -Seconds 1
                            createBearerToken -targetEndPoint $targetEndpoint
                            Start-Sleep -Seconds 1
                        }
                    }
                    else {
                        $uri = $azAPIRequestConvertedFromJson.properties.nextLink
                    }
                    if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: nextLink: $Uri" -ForegroundColor $debugForeGroundColor }
                }
                else {
                    if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: NextLink: none" -ForegroundColor $debugForeGroundColor }
                }
            }
        }
        else {
            if ($htParameters.DebugAzAPICall -eq $true) { Write-Host " DEBUG: unexpectedError: notFalse" -ForegroundColor $debugForeGroundColor }
            if ($tryCounterUnexpectedError -lt 13) {
                $sleepSec = @(1, 2, 3, 5, 7, 10, 13, 17, 20, 30, 40, 50, 60)[$tryCounterUnexpectedError]
                Write-Host " $currentTask #$tryCounterUnexpectedError 'Unexpected Error' occurred (trying 10 times); sleep $sleepSec seconds"
                Write-Host $catchResult
                Start-Sleep -Seconds $sleepSec
            }
            else {
                Write-Host " $currentTask #$tryCounterUnexpectedError 'Unexpected Error' occurred (tried 5 times)/exit"
                Throw "Error - check the last console output for details"
            }
        }
    }
    until(($azAPIRequest.StatusCode -in 200..204 -and -not $isMore ) -or ($Method -eq "HEAD" -and $azAPIRequest.StatusCode -eq 404))
    return $apiCallResultsCollection
}