Public/Account/Invoke-RMMApiMethod.ps1

<#
    Copyright (c) 2025-2026 Robert Faddes
    SPDX-License-Identifier: MPL-2.0
#>

function Invoke-RMMApiMethod {
    <#
    .SYNOPSIS
        Invokes an arbitrary Datto RMM API endpoint and returns the untyped response as a PSObject.
 
    .DESCRIPTION
        The Invoke-RMMApiMethod function is a generic wrapper for calling any Datto RMM API v2
        endpoint. It handles authentication, token refresh, proxy configuration, retry logic, and
        optional pagination — the same infrastructure used by all other module functions — but
        returns the response as an untyped PSObject without mapping it to a module class.
 
        Because the module uses Invoke-RestMethod internally, the JSON response from the API is
        automatically deserialised into PowerShell objects. The returned data is not raw JSON —
        it is a fully usable PSObject hierarchy. If you need to inspect the raw JSON payload,
        use a dedicated API tool such as Postman (usage of which is outside the scope of this
        module).
 
        This function is useful when:
        - You need to call a new or undocumented API endpoint that is not yet wrapped by
          a dedicated module function.
        - You want to inspect the untyped response structure returned by the API for debugging
          or exploration.
        - You are testing or prototyping against the API before a typed function is available.
 
        The Path parameter is the relative API path appended to the base URL (e.g.
        https://<region>.centrastage.net/api/v2). You do not need to include the base URL
        or the /api/v2 prefix — only the path segment that follows it.
 
    .PARAMETER Path
        The relative API path to call, appended after /api/v2/.
 
        Do not include the base URL or the /api/v2 prefix. Only provide the path segment
        that follows it.
 
        Examples of valid Path values:
        - 'account' → GET /api/v2/account
        - 'account/users' → paginated user list
        - 'device/abc-123-uid' → single device by UID
        - 'site/abc-123-uid/settings' → site settings
 
    .PARAMETER Method
        The HTTP method to use for the request. Defaults to Get.
 
        Accepted values are those supported by System.Net.Http.HttpMethod:
        Get, Post, Put, Patch, Delete, Head, Options, Trace.
 
        Typical usage:
        - Get Retrieve data (default)
        - Post Create or update a resource
        - Put Create or replace a resource
        - Patch Partial update of a resource
        - Delete Remove a resource
 
    .PARAMETER Parameters
        A hashtable of query string parameters to append to the request URL. Each key must
        match a query parameter name defined by the API endpoint being called.
 
        Each key-value pair is converted to a URL query parameter and appended automatically.
        Do not include query parameters in the Path — use this parameter instead.
 
        The exact parameters available depend on the endpoint. Refer to the Datto RMM API
        documentation for the supported query parameters on each path. Common examples:
 
        - filterId Filter devices by a filter ID (long)
        - hostname Filter devices by hostname (partial match)
        - deviceType Filter devices by device type (partial match)
        - operatingSystem Filter devices by OS (partial match)
        - siteName Filter devices/sites by site name (partial match)
        - page Retrieve a specific page of results (int)
 
        Note: All paginated endpoints accept a 'page' parameter. The module's typed public
        functions always retrieve all pages automatically. This function allows you to request
        a specific page via Parameters if needed (e.g. @{page = 2}).
 
    .PARAMETER Body
        An object or hashtable to send as the JSON request body. Applies to Post, Put, and
        Patch requests.
 
        The value is automatically serialised to JSON via ConvertTo-Json before being sent.
        Pass native PowerShell objects or hashtables — do not pre-serialise to a JSON string.
 
    .PARAMETER Paginate
        Enables automatic pagination. When specified, the function follows the
        pageDetails.nextPageUrl link in each response until all pages have been retrieved,
        streaming each page's items to the pipeline as they arrive.
 
        Pagination requires the PageElement parameter so the function knows which property
        of the response object contains the array of items to extract from each page.
 
        If the endpoint does not return paginated data (no pageDetails.nextPageUrl in the
        response), the function returns the single response as normal.
 
        The module's configured page size (Set-RMMConfig -PageSize) is applied automatically
        when Paginate is used. To retrieve a single specific page instead, omit Paginate and
        pass the page number via the Parameters parameter (e.g. @{page = 3}).
 
    .PARAMETER PageElement
        The name of the property in the API response object that contains the array of items
        for paginated endpoints.
 
        Each Datto RMM paginated endpoint wraps its result array under a different property
        name. You must specify the correct property so the function can extract the items
        from each page.
 
        Common PageElement values for known endpoints:
        - 'devices' → device list endpoints
        - 'sites' → site list endpoints
        - 'alerts' → alert list endpoints
        - 'activityLogs' → activity log endpoints
        - 'users' → user list endpoints
        - 'dnetSiteMappings' → Datto Networking mapping endpoints
        - 'components' → component library endpoints
        - 'filters' → filter list endpoints
        - 'jobs' → job list endpoints
 
        If unsure of the correct value, call the endpoint without Paginate first and inspect
        the returned object's property names to identify which property holds the result array.
 
    .EXAMPLE
        Invoke-RMMApiMethod -Path 'account'
 
        Calls GET /api/v2/account and returns the account data as an untyped PSObject.
 
    .EXAMPLE
        Invoke-RMMApiMethod -Path 'account/users' -Paginate -PageElement 'users'
 
        Retrieves all users across all pages, streaming each user object to the pipeline.
 
    .EXAMPLE
        Invoke-RMMApiMethod -Path 'account/devices' -Parameters @{hostname = 'SERVER'} -Paginate -PageElement 'devices'
 
        Retrieves all devices with 'SERVER' in the hostname, with automatic pagination.
 
    .EXAMPLE
        Invoke-RMMApiMethod -Path 'account/devices' -Parameters @{page = 2}
 
        Retrieves a single page (page 2) of the device list without automatic
        pagination. The full response object is returned including pageDetails.
 
    .EXAMPLE
        Invoke-RMMApiMethod -Path 'account/sites' -Paginate -PageElement 'sites'
 
        Retrieves all sites across all pages.
 
    .EXAMPLE
        $Body = @{
            name = 'Test Site'
            description = 'Created via Invoke-RMMApiMethod'
            onDemand = $false
        }
        PS > Invoke-RMMApiMethod -Path 'site' -Method Put -Body $Body
 
        Creates a new site by sending a PUT request with a JSON body.
 
    .EXAMPLE
        Invoke-RMMApiMethod -Path 'device/abc-123-uid'
 
        Retrieves a single device by UID without pagination.
 
    .EXAMPLE
        $Result = Invoke-RMMApiMethod -Path 'account/alerts/open' -Paginate -PageElement 'alerts'
        PS > $Result | Select-Object alertUid, alertMessage
 
        Retrieves all open alerts and selects specific properties from the untyped response.
 
    .INPUTS
        None. You cannot pipe objects to Invoke-RMMApiMethod.
 
    .OUTPUTS
        PSObject. Returns the untyped, deserialised API response as a PowerShell object. The
        shape of the returned object depends entirely on the endpoint called. When Paginate is
        used, the pagination wrapper is removed and individual items from the PageElement array
        are streamed to the pipeline.
 
    .NOTES
        This function requires an active connection to the Datto RMM API.
        Use Connect-DattoRMM to authenticate before calling this function.
 
        This is a pass-through wrapper — it does not validate the API path, map responses
        to typed classes, or interpret error payloads beyond what the underlying
        infrastructure already provides.
 
        For known endpoints with dedicated functions (e.g. Get-RMMDevice, Get-RMMSite),
        prefer using the typed function for a richer, documented experience.
 
    .LINK
        https://github.com/TheShadowTek/DattoRMM.Core/blob/main/docs/commands/Account/Invoke-RMMApiMethod.md
 
    .LINK
        Connect-DattoRMM
 
    .LINK
        Get-RMMDevice
 
    .LINK
        Get-RMMSite
    #>

    [CmdletBinding(DefaultParameterSetName = 'Default')]

    param (

        [Parameter(
            Mandatory = $true
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path,

        [Microsoft.PowerShell.Commands.WebRequestMethod]
        $Method = 'Get',

        [hashtable]
        $Parameters,

        [object]
        $Body,

        [Parameter(
            ParameterSetName = 'Paginate',
            Mandatory = $false
        )]
        [switch]
        $Paginate,

        [Parameter(
            ParameterSetName = 'Paginate',
            Mandatory = $true
        )]
        [string]
        $PageElement

    )

    process {

        Write-Verbose "Invoking API: $Method $Path"

        # Build splatting hashtable for Invoke-ApiMethod
        $InvokeApiMethod = @{
            Path   = $Path
            Method = $Method
        }

        if ($Parameters) {

            $InvokeApiMethod.Parameters = $Parameters

        }

        if ($Body) {

            $InvokeApiMethod.Body = $Body

        }

        if ($Paginate) {

            $InvokeApiMethod.Paginate = $true
            $InvokeApiMethod.PageElement = $PageElement

        }

        Invoke-ApiMethod @InvokeApiMethod

    }
}

# SIG # Begin signature block
# MIIF+wYJKoZIhvcNAQcCoIIF7DCCBegCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCvRPG6XOV7iz8n
# jLpKyvxDCITl7vIKhAj17Au3KFtvwqCCA04wggNKMIICMqADAgECAhB464iXHfI6
# gksEkDDTyrNsMA0GCSqGSIb3DQEBCwUAMD0xFjAUBgNVBAoMDVJvYmVydCBGYWRk
# ZXMxIzAhBgNVBAMMGkRhdHRvUk1NLkNvcmUgQ29kZSBTaWduaW5nMB4XDTI2MDMz
# MTAwMTMzMFoXDTI4MDMzMTAwMjMzMFowPTEWMBQGA1UECgwNUm9iZXJ0IEZhZGRl
# czEjMCEGA1UEAwwaRGF0dG9STU0uQ29yZSBDb2RlIFNpZ25pbmcwggEiMA0GCSqG
# SIb3DQEBAQUAA4IBDwAwggEKAoIBAQChn1EpMYQgl1RgWzQj2+wp2mvdfb3UsaBS
# nxEVGoQ0gj96tJ2MHAF7zsITdUjwaflKS1vE6wAlOg5EI1V79tJCMxzM0bFpOdR1
# L5F2HE/ovIAKNkHxFUF5qWU8vVeAsOViFQ4yhHpzLen0WLF6vhmc9eH23dLQy5fy
# tELZQEc2WbQFa4HMAitP/P9kHAu6CUx5s4woLIOyyR06jkr3l9vk0sxcbCxx7+dF
# RrsSLyPYPH+bUAB8+a0hs+6qCeteBuUfLvGzpMhpzKAsY82WZ3Rd9X38i32dYj+y
# dYx+nx+UEMDLjDJrZgnVa8as4RojqVLcEns5yb/XTjLxDc58VatdAgMBAAGjRjBE
# MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQU
# H+B0vf97dYXqdUX1YMcWhFsY6fcwDQYJKoZIhvcNAQELBQADggEBAJmD4EEGNmcD
# 1JtFoRGxuLJaTHxDwBsjqcRQRE1VPZNGaiwIm8oSQdHVjQg0oIyK7SEb02cs6n6Y
# NZbwf7B7WZJ4aKYbcoLug1k1x9SoqwBmfElECeJTKXf6dkRRNmrAodpGCixR4wMH
# KXqwqP5F+5j7bdnQPiIVXuMesxc4tktz362ysph1bqKjDQSCBpwi0glEIH7bv5Ms
# Ey9Gl3fe+vYC5W06d2LYVebEfm9+7766hsOgpdDVgdtnN+e6uwIJjG/6PTG6TMDP
# y+pr5K6LyUVYJYcWWUTZRBqqwBHiLGekPbxrjEVfxUY32Pq4QfLzUH5hhUCAk4HN
# XpF9pOzFLMUxggIDMIIB/wIBATBRMD0xFjAUBgNVBAoMDVJvYmVydCBGYWRkZXMx
# IzAhBgNVBAMMGkRhdHRvUk1NLkNvcmUgQ29kZSBTaWduaW5nAhB464iXHfI6gksE
# kDDTyrNsMA0GCWCGSAFlAwQCAQUAoIGEMBgGCisGAQQBgjcCAQwxCjAIoAKAAKEC
# gAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwG
# CisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIKnzc15cB5JuvZbuONLEtIywQiL7
# M2d7T3hq842TnncrMA0GCSqGSIb3DQEBAQUABIIBAHm9mvkgTdtu5WUQV2+Ilgyg
# HL25Ihkf/pt8o2KPcZAMg/PSVG10v/CAoki/UGaPGQm0+gai9E4GU9it6/pgZXy/
# pDuOvaq6y+OT92DbJif0fvzIweuzLAPcJxPW3MNYnhXDkWNKq1rIottVAyLcNtWT
# oEGS+tqRblyO1+q7Cp7kBXkQIpvN1EgGEEO4m+cmImMEUjOux0lDqSIReS2WIdKZ
# CUPNQfANjV7VfB2zQeTupZ9SNyRvieC2Pi2x0Kg6rTWy4pgouC0vCdmfhX4jut3I
# Yw2CAcqif9PZUFQD6a+dZ99wCd08iCTFj/f0WXtlIGwGkQIfFFk9H1WXMSXepnk=
# SIG # End signature block