AppleBusinessManager.psm1

#Requires -Version 7.0
#Requires -Module jwtPS
function Connect-AppleBusinessManager {
    [CmdletBinding(DefaultParameterSetName = 'EnvironmentVariable')]
    param(
        [string][Parameter(ParameterSetName = 'PrivateKeyAsString', Mandatory)]$ClientId,
        [string][Parameter(ParameterSetName = 'PrivateKeyAsString', Mandatory)]$PrivateKey,
        [string][Parameter(ParameterSetName = 'PrivateKeyAsString', Mandatory)]$PrivateKeyId
    )

    if ($PSBoundParameters.Count -eq 0) {
        # Parameters were not bound, check to see if they're already set before attempting to authenticate
        Write-Verbose "Checking to see if existing credentials were set at a script scope that can be re-used"
        if (-not $Script:ClientId -or -not $Script:PrivateKey -or -not $Script:PrivateKeyId) {
            Write-Verbose "Existing credentials not loaded, checking for environment variables"
            if ($PSCmdlet.ParameterSetName -eq 'EnvironmentVariable') {
                if (-not $Env:AppleBusinessManagerClientId -or -not $Env:AppleBusinessManagerPrivateKeyId -or -not $Env:AppleBusinessManagerPrivateKey) {
                    throw "Client ID, Private Key ID and Private Key environment variables were not set for Apple Business Manager"
                }
                $Script:ClientId = $Env:AppleBusinessManagerClientId
                $Script:PrivateKey = $Env:AppleBusinessManagerPrivateKey
                $Script:PrivateKeyId = $Env:AppleBusinessManagerPrivateKeyId
            }
        }
    }
    
    if ($PSCmdlet.ParameterSetName -eq 'PrivateKeyAsString') {
        # Assign the parameters to script scope so we can reuse them later
        $Script:ClientId = $ClientId
        $Script:PrivateKey = $PrivateKey
        $Script:PrivateKeyId = $PrivateKeyId
    }

    $Header = @{
        'kid' = $Script:PrivateKeyId
    }

    $Payload = @{
        aud = "https://account.apple.com/auth/oauth2/v2/token" 
        iss = $Script:ClientId
        sub = $Script:ClientId
        iat = ([System.DateTimeOffset]::Now).ToUnixTimeSeconds()
        exp = ([System.DateTimeOffset]::Now.AddMinutes(15)).ToUnixTimeSeconds()
        jti = [guid]::NewGuid()
    }

    $Hashing = [jwtTypes+encryption]::SHA256
    $Signature = [jwtTypes+algorithm]::ECDsa
    $Algorithm = [jwtTypes+cryptographyType]::new($Signature, $Hashing)
    $JWT = New-JWT -Payload $Payload -Algorithm $Algorithm -Secret $Script:PrivateKey -Header $Header
    Write-Verbose $JWT

    $Script:Body = @{
        'grant_type'            = 'client_credentials'
        'client_id'             = $Script:ClientId
        'client_assertion'      = $JWT
        'client_assertion_type' = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
        'scope'                 = 'business.api'
    }

    $Script:AuthResponse = Invoke-RestMethod -Method Post -Uri 'https://account.apple.com/auth/oauth2/token' -Body $Script:Body -SkipHttpErrorCheck
    if ($Script:AuthResponse.error) {
        throw $Script:AuthResponse.Error
    }
    $Script:ExpiresAt = (Get-Date).AddSeconds($Script:AuthResponse.expires_in)
}

function Get-AppleBusinessManagerBearerToken {
    if (-not $Script:AuthResponse) {
        try {
            Connect-AppleBusinessManager
        }
        catch {
            throw "Authorization has not been completed, use Connect-AppleBusinessManager first."
        }
    }
    if ((Get-Date).AddMinutes(1) -ge $Script:ExpiresAt) {
        # Access token is approaching expiration, get a new access token
        Connect-AppleBusinessManager
    }

    return ($Script:AuthResponse.access_token | ConvertTo-SecureString -AsPlainText -Force)
}

function Invoke-AppleBusinessManagerPagedApiRequest {
    param (
        [Parameter(Mandatory = $true)][uri] $Uri
    )
    $Results = New-Object System.Collections.ArrayList
    while ($Uri) {
        Write-Verbose "Making request to $Uri"
        $Result = Invoke-RestMethod $Uri -Authentication Bearer -Token (Get-AppleBusinessManagerBearerToken) -ErrorAction Stop
        $Uri = $Result.links.next
        $Results.AddRange(@($Result.data)) | Out-Null
    }
    return $Results
}

function Get-AppleBusinessManagerOrgDevice {
    [Alias('Get-AppleBusinessManagerOrgDevices')]
    param (
        [Parameter(Mandatory, ParameterSetName = 'Read')][string] $OrgDeviceId,
        [Parameter(ParameterSetName = 'Read')]
        [Parameter(ParameterSetName = 'List')]
        [string[]] $Fields,
        [Parameter( ParameterSetName = 'Read')]
        [Parameter(ParameterSetName = 'List')]
        [int] $Limit
    )
    $Uri = switch ($PSCmdlet.ParameterSetName) {
        'Read' { "https://api-business.apple.com/v1/orgDevices/$([System.Web.HttpUtility]::UrlEncode($OrgDeviceId))" }
        Default { "https://api-business.apple.com/v1/orgDevices" }
    }
    $UriBuilder = [System.UriBuilder]::new($Uri)
    $QueryString = [System.Web.HttpUtility]::ParseQueryString($UriBuilder.Query)
    if ($PSBoundParameters.ContainsKey('Fields')) {
        $QueryString.Set('fields[orgDevices]', $Fields -join ',')
    }
    if ($PSBoundParameters.ContainsKey('Limit')) {
        $QueryString.Set('limit', $Limit)
    }
    $UriBuilder.Query = $QueryString.ToString()
    return Invoke-AppleBusinessManagerPagedApiRequest -Uri $UriBuilder.Uri
}

function Get-AppleBusinessManagerOrgDeviceMdmServerId {
    param (
        [Parameter(Mandatory)]
        [string] $OrgDeviceId
    )
    return Invoke-AppleBusinessManagerPagedApiRequest -Uri "https://api-business.apple.com/v1/orgDevices/$([System.Web.HttpUtility]::UrlEncode($OrgDeviceId))/relationships/assignedServer"
}

function Get-AppleBusinessManagerOrgDeviceMdmServer {
    param (
        [Parameter(Mandatory)]
        [string] $OrgDeviceId,
        [string[]] $Fields
    )
    $UriBuilder = [System.UriBuilder]::new("https://api-business.apple.com/v1/orgDevices/$([System.Web.HttpUtility]::UrlEncode($OrgDeviceId))/assignedServer")
    $QueryString = [System.Web.HttpUtility]::ParseQueryString($UriBuilder.Query)
    if ($PSBoundParameters.ContainsKey('Fields')) {
        $QueryString.Set('fields[mdmServers]', $Fields -join ',')
    }
    $UriBuilder.Query = $QueryString.ToString()
    return Invoke-AppleBusinessManagerPagedApiRequest -Uri $UriBuilder.Uri
}

function Get-AppleBusinessManagerMdmServer {
    [Alias('Get-AppleBusinessManagerMdmServers')]
    param (
        [string[]] $Fields,
        [int] $Limit
    )
    $UriBuilder = [System.UriBuilder]::new("https://api-business.apple.com/v1/mdmServers")
    $QueryString = [System.Web.HttpUtility]::ParseQueryString($UriBuilder.Query)
    if ($PSBoundParameters.ContainsKey('Fields')) {
        $QueryString.Set('fields[mdmServers]', $Fields -join ',')
    }
    if ($PSBoundParameters.ContainsKey('Limit')) {
        $QueryString.Set('limit', $Limit)
    }
    $UriBuilder.Query = $QueryString.ToString()
    return Invoke-AppleBusinessManagerPagedApiRequest -Uri $UriBuilder.Uri
}

function Get-AppleBusinessManagerMdmServerDevice {
    param (
        [Parameter(Mandatory)]
        [string] $MdmServerId,
        [int] $Limit
    )
    $UriBuilder = [System.UriBuilder]::new("https://api-business.apple.com/v1/mdmServers/$([System.Web.HttpUtility]::UrlEncode($MdmServerId))/relationships/devices")
    $QueryString = [System.Web.HttpUtility]::ParseQueryString($UriBuilder.Query)
    if ($PSBoundParameters.ContainsKey('Limit')) {
        $QueryString.Set('limit', $Limit)
    }
    $UriBuilder.Query = $QueryString.ToString()
    return Invoke-AppleBusinessManagerPagedApiRequest -Uri $UriBuilder.Uri
}