Public/Invoke-DattoApiRequest.ps1

function Invoke-DattoApiRequest {
<#
.SYNOPSIS
Invokes an authenticated request against a relative Datto REST API v1 path.
.DESCRIPTION
Provides the common request engine used by the endpoint-specific commands. It supports GET and PUT, safe URI construction, JSON request bodies, response parsing, automatic pagination, bounded retries, and access to raw response metadata. Only relative /v1/ paths are accepted so credentials cannot be redirected to another host.
.PARAMETER Method
GET or PUT.
.PARAMETER Path
A relative Datto API path beginning with /v1/. Do not include a query string.
.PARAMETER Query
A hashtable of query parameters. Enumerable values are encoded as comma-separated values.
.PARAMETER Body
An object to serialize as JSON for PUT requests.
.PARAMETER All
Retrieves all pages for a GET operation when the response contains pagination metadata. It is rejected for PUT operations.
.PARAMETER ResultProperty
The envelope property containing records, such as items or clients. Auto-detected when omitted.
.PARAMETER RawResponse
Returns the API response body rather than extracting records. With -All, returns an aggregated envelope with _dattoAggregation metadata.
.PARAMETER IncludeResponseMetadata
Returns status, headers, content type, and body for a single request. It cannot be combined with -All.
.PARAMETER AsByteArray
Returns response bytes. It cannot be combined with -All.
.PARAMETER RetryNonIdempotent
Allows automatic retries for PUT requests. Use only when the operation is known to be safe to repeat.
.PARAMETER MaxPages
Safety ceiling for automatic pagination.
.PARAMETER Connection
A connection object, name, or ID.
.EXAMPLE
Invoke-DattoApiRequest -Path '/v1/bcdr/device' -Query @{ _page = 1; _perPage = 100 }
.EXAMPLE
Invoke-DattoApiRequest -Path '/v1/bcdr/agent' -All -ResultProperty clients
.EXAMPLE
Invoke-DattoApiRequest -Method PUT -Path '/v1/dtc/agent/AGENT_UUID/bandwidth' -Body @{ pauseWhileMetered = $false }
#>

    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateSet('GET', 'PUT')]
        [string]$Method = 'GET',

        [Parameter(Mandatory)]
        [ValidatePattern('^/v1/')]
        [string]$Path,

        [Parameter()]
        [AllowNull()]
        [System.Collections.IDictionary]$Query,

        [Parameter()]
        [AllowNull()]
        [object]$Body,

        [Parameter()]
        [switch]$All,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$ResultProperty,

        [Parameter()]
        [switch]$RawResponse,

        [Parameter()]
        [switch]$IncludeResponseMetadata,

        [Parameter()]
        [switch]$AsByteArray,

        [Parameter()]
        [switch]$RetryNonIdempotent,

        [Parameter()]
        [ValidateRange(1, 10000)]
        [int]$MaxPages = 1000,

        [Parameter()]
        [AllowNull()]
        [object]$Connection
    )

    if ($Method -eq 'GET' -and $PSBoundParameters.ContainsKey('Body')) {
        throw [System.ArgumentException]::new('A request body is not supported for GET operations in the supplied Datto API contract.')
    }
    if ($All -and $Method -ne 'GET') {
        throw [System.ArgumentException]::new('-All is supported only for GET operations and will never repeat a state-changing PUT request.')
    }
    if ($All -and ($AsByteArray -or $IncludeResponseMetadata)) {
        throw [System.ArgumentException]::new('-All cannot be combined with -AsByteArray or -IncludeResponseMetadata.')
    }

    $queryCopy = Copy-DattoApiQuery -Query $Query
    $response = Invoke-DattoApiHttpRequest -Method $Method -Path $Path -Query $queryCopy -Body $Body -Connection $Connection -AsByteArray:$AsByteArray -RetryNonIdempotent:$RetryNonIdempotent

    if ($IncludeResponseMetadata) {
        return $response
    }
    if ($AsByteArray) {
        Write-Output -NoEnumerate $response.Bytes
        return
    }
    if (-not $All) {
        if ($RawResponse) {
            Write-Output -NoEnumerate $response.Body
            return
        }
        $resolved = Resolve-DattoApiResultCollection -Body $response.Body -ResultProperty $ResultProperty
        foreach ($value in $resolved.Values) { $value }
        return
    }

    $firstBody = $response.Body
    $resolvedFirst = Resolve-DattoApiResultCollection -Body $firstBody -ResultProperty $ResultProperty
    $pagination = Get-DattoApiPaginationObject -Body $firstBody
    if ($null -eq $pagination -or -not $resolvedFirst.IsEnvelope) {
        if ($RawResponse) { Write-Output -NoEnumerate $firstBody }
        else { foreach ($value in $resolvedFirst.Values) { $value } }
        return
    }

    $currentPage = 1
    $totalPages = 1
    if ($pagination.PSObject.Properties.Name -contains 'page') { $currentPage = [int]$pagination.page }
    if ($pagination.PSObject.Properties.Name -contains 'totalPages') { $totalPages = [int]$pagination.totalPages }
    if ($totalPages -lt $currentPage) { $totalPages = $currentPage }
    $pagesNeeded = ($totalPages - $currentPage) + 1
    if ($pagesNeeded -gt $MaxPages) {
        throw [System.InvalidOperationException]::new("The response requires $pagesNeeded pages, which exceeds -MaxPages $MaxPages.")
    }

    $allValues = [System.Collections.Generic.List[object]]::new()
    foreach ($value in $resolvedFirst.Values) { $allValues.Add($value) }
    $firstPage = $currentPage
    $pagesRequested = 1

    for ($page = $currentPage + 1; $page -le $totalPages; $page++) {
        $queryCopy['_page'] = $page
        $pageResponse = Invoke-DattoApiHttpRequest -Method $Method -Path $Path -Query $queryCopy -Body $Body -Connection $Connection -RetryNonIdempotent:$RetryNonIdempotent
        $pageResolved = Resolve-DattoApiResultCollection -Body $pageResponse.Body -ResultProperty $resolvedFirst.PropertyName
        foreach ($value in $pageResolved.Values) { $allValues.Add($value) }
        $pagesRequested++
    }

    $values = [object[]]$allValues.ToArray()
    if ($RawResponse) {
        $envelope = New-DattoApiAggregatedEnvelope -FirstBody $firstBody -ResultProperty $resolvedFirst.PropertyName -Values $values -FirstPage $firstPage -LastPage $totalPages -PagesRequested $pagesRequested
        Write-Output -NoEnumerate $envelope
    }
    else {
        foreach ($value in $values) { $value }
    }
}