Private/Invoke/Invoke-CWMWebRequest.ps1

function Invoke-CWMWebRequest {
    [CmdletBinding()]
    param(
        $Arguments,
        [int]$MaxRetry = 5
    )

    # Check that we have cached connection info
    if (!$script:CWMServerConnection) {
        $ErrorMessage = @()
        $ErrorMessage += 'Not connected to a Manage server.'
        $ErrorMessage += '--> $CWMServerConnection variable not found.'
        $ErrorMessage += "----> Run 'Connect-CWM' to initialize the connection before issuing other CWM cmdlets."
        return Write-Error ($ErrorMessage | Out-String)
    }

    # Add default set of arguments
    foreach ($Key in $script:CWMServerConnection.Headers.Keys) {
        if ($Arguments.Headers.Keys -notcontains $Key) {
            # Set version
            if ($Key -eq 'Accept' -and $Arguments.Version -and $Arguments.Version -ne $script:CWMServerConnection.Version) {
                $Arguments.Headers.Accept = "application/vnd.connectwise.com+json; version=$($Arguments.Version)"
                Write-Verbose "Version Passed: $($Arguments.Version)"
            }
            else {
                $Arguments.Headers += @{$Key = $script:CWMServerConnection.Headers.$Key }
            }
        }
    }
    $Arguments.Remove('Version')

    if (!$Arguments.SessionVariable) { $Arguments.WebSession = $script:CWMServerConnection.Session }

    # Check URI format
    if ($Arguments.URI -notlike '*`?*' -and $Arguments.URI -like '*`&*') {
        $Arguments.URI = $Arguments.URI -replace '(.*?)&(.*)', '$1?$2'
    }

    # Issue request
    try {
        Write-Debug "Arguments: $($Arguments | ConvertTo-Json)"
        $prevProgressPreference = $global:ProgressPreference
        $global:ProgressPreference = 'SilentlyContinue'

        $Result = Invoke-WebRequest @Arguments -UseBasicParsing

        $global:ProgressPreference = $prevProgressPreference
    }
    catch {
        $global:ProgressPreference = $prevProgressPreference

        # Start error message
        $ErrorMessage = @()

        if ($_.Exception.Response) {
            try {
                # Read exception response
                #this can fail with some type of exceptions
                $ErrorStream = $_.Exception.Response.GetResponseStream()
                $Reader = New-Object System.IO.StreamReader($ErrorStream)
                $script:ErrBody = $Reader.ReadToEnd() | ConvertFrom-Json
            }
            catch {
                $script:ErrBody = $_.Exception.Response.Content
            }
            $ErrBody = $script:ErrBody
            if ($ErrBody.code) {
                $ErrorMessage += 'An exception code has been thrown.'
                $ErrorMessage += "--> $($ErrBody.code)"
                if ($ErrBody.code -eq 'Unauthorized') {
                    $ErrorMessage += "-----> $($ErrBody.message)"
                    $ErrorMessage += "-----> Use 'Disconnect-CWM' or 'Connect-CWM -Force' to set new authentication."
                }
                elseif ($ErrBody.code -eq 'ConnectWiseApi') {
                    switch ($ErrBody.message) {
                        'UserNotAuthenticated' {
                            $ErrorMessage += "-----> $($ErrBody.message)"
                            $ErrorMessage += '-----> Check your connection parameters and/or user permissions.'
                        }
                        Default {
                            $ErrorMessage += "-----> $($ErrBody.message)"
                            $ErrorMessage += '-----> ^ Error has not been documented please report. ^'
                        }
                    }
                }
                else {
                    $ErrorMessage += "-----> $($ErrBody.message)"
                    $ErrorMessage += '-----> ^ Error has not been documented please report. ^'
                }
            }
            elseif ($_.Exception.message) {
                $ErrorMessage += 'An exception has been thrown.'
                $ErrorMessage += "--> $($_.Exception.message)"
            }
        }

        if ($_.ErrorDetails) {
            $ErrorMessage += 'An error has been thrown.'
            $script:ErrDetails = $_.ErrorDetails
            $ErrorMessage += "--> $($ErrDetails.code)"
            $ErrorMessage += "--> $($ErrDetails.message)"
            if ($ErrDetails.errors.message) {
                $ErrorMessage += "-----> $($ErrDetails.errors.message)"
            }
        }

        if ($ErrorMessage.Length -lt 1) { $ErrorMessage = $_ }
        else { $ErrorMessage += $_.ScriptStackTrace }

        return Write-Error ($ErrorMessage | Out-String)
    }

    # Not sure this will be hit with current iwr error handling
    # May need to move to catch block need to find test
    # TODO Find test for retry
    # Retry the request
    $Retry = 0
    while ($Retry -lt $MaxRetry -and $Result.StatusCode -ge 500) {
        $Retry++
        # ConnectWise Manage recommended wait time
        $Wait = $([math]::pow( 2, $Retry))
        Write-Warning "Issue with request, status: $($Result.StatusCode) $($Result.StatusDescription)"
        Write-Warning "$($Retry)/$($MaxRetry) retries, waiting $($Wait)ms."
        Start-Sleep -Milliseconds $Wait
        $Result = Invoke-WebRequest @Arguments -UseBasicParsing
    }
    if ($Retry -ge $MaxRetry) {
        return Write-Error "Max retries hit. Status: $($Result.StatusCode) $($Result.StatusDescription)"
    }

    # Save current version
    if (!$script:CWMServerConnection.'api-current-version') {
        $script:CWMServerConnection.'api-current-version' = $Result.Headers.'api-current-version'
    }

    $Result
}