Modules/AzureDevOpsDsc.Common/Api/Functions/Private/Invoke-AzDevOpsApiRestMethod.ps1

<#
    .SYNOPSIS
        This is a light, generic, wrapper proceedure around 'Invoke-RestMethod' to handle
        multiple retries and error/exception handling.
 
        This function makes no assumptions around the versions of the API used, the resource
        being operated/actioned upon, the operation/method being performed, nor the content
        of the HTTP headers and body.
 
    .PARAMETER ApiUri
        The URI of the Azure DevOps API to be connected to. For example:
 
          https://dev.azure.com/someOrganizationName/_apis/
 
    .PARAMETER HttpMethod
        The HTTP method being used in the HTTP/REST request sent to the Azure DevOps API.
 
    .PARAMETER HttpHeaders
        The headers for the HTTP/REST request sent to the Azure DevOps API.
 
    .PARAMETER HttpBody
        The body for the HTTP/REST request sent to the Azure DevOps API. If performing a 'Post',
        'Put' or 'Patch' method/request, this will typically contain the JSON document of the resource.
 
    .PARAMETER RetryAttempts
        The number of times the method/request will attempt to be resent/retried if unsuccessful on the
        initial attempt.
 
        If any attempt is successful, the remaining attempts are ignored.
 
    .PARAMETER RetryIntervalMs
        The interval (in Milliseconds) between retry attempts.
 
    .EXAMPLE
        Invoke-AzDevOpsApiRestMethod -ApiUri 'YourApiUriHere' -HttpMethod 'Get' -HttpHeaders $YouHttpHeadersHashtableHere
 
        Submits a 'Get' request to the Azure DevOps API (relying on the 'ApiUri' value to determine what is being retrieved).
 
    .EXAMPLE
        Invoke-AzDevOpsApiRestMethod -ApiUri 'YourApiUriHere' -HttpMethod 'Patch' -HttpHeaders $YourHttpHeadersHashtableHere `
                                     -HttpBody $YourHttpBodyHere -RetryAttempts 3
 
        Submits a 'Patch' request to the Azure DevOps API with the supplied 'HttpBody' and will attempt to retry 3 times (4 in
        total, including the intitial attempt) if unsuccessful.
#>

function Invoke-AzDevOpsApiRestMethod
{
    [CmdletBinding()]
    [OutputType([System.Management.Automation.PSObject])]
    param
    (
        [Parameter(Mandatory=$true)]
        [Alias('Uri')]
        [System.String]
        $ApiUri,

        [Parameter(Mandatory=$true)]
        [ValidateSet('Get','Post','Patch','Put','Delete')]
        [System.String]
        [Alias('Method')]
        $HttpMethod,

        [Parameter(Mandatory=$true)]
        [ValidateScript( { Test-AzDevOpsApiHttpRequestHeader -HttpRequestHeader $_ -IsValid })]
        [Hashtable]
        [Alias('Headers','HttpRequestHeader')]
        $HttpHeaders,

        [Parameter()]
        [System.String]
        [Alias('Body')]
        $HttpBody,

        [Parameter()]
        [System.String]
        [Alias('ContentType')]
        [ValidateSet('application/json')]
        $HttpContentType = 'application/json',

        [Parameter()]
        [ValidateRange(0,5)]
        [Int32]
        $RetryAttempts = 5,

        [Parameter()]
        [ValidateRange(250,10000)]
        [Int32]
        $RetryIntervalMs = 250
    )

    $invokeRestMethodParameters = @{
        Uri         = $ApiUri
        Method      = $HttpMethod
        Headers     = $HttpHeaders
        Body        = $HttpBody
        ContentType = $HttpContentType
    }

    # Remove the 'Body' and 'ContentType' if not relevant to request
    if ($HttpMethod -in $('Get','Delete'))
    {
        $invokeRestMethodParameters.Remove('Body')
        $invokeRestMethodParameters.Remove('ContentType')
    }

    # Intially set this value to -1, as the first attempt does not want to be classed as a "RetryAttempt"
    $CurrentNoOfRetryAttempts = -1

    while ($CurrentNoOfRetryAttempts -lt $RetryAttempts)
    {
        try
        {
            return Invoke-RestMethod @invokeRestMethodParameters
        }
        catch
        {
            # Increment the number of retries attempted and obtain any exception message
            $CurrentNoOfRetryAttempts++
            $restMethodExceptionMessage = $_.Exception.Message

            # Wait before the next attempt/retry
            Start-Sleep -Milliseconds $RetryIntervalMs
        }
    }


    # If all retry attempts have failed, throw an exception
    $errorMessage = $script:localizedData.AzDevOpsApiRestMethodException -f $MyInvocation.MyCommand, $RetryAttempts, $restMethodExceptionMessage
    New-InvalidOperationException -Message $errorMessage

}