commit 740994ae40bd5d44b09dadd0dc5629834d7bdef7 Author: Liam Steckler Date: Thu Jun 12 10:19:06 2025 -0700 Initial Version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0b84df0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.html \ No newline at end of file diff --git a/AppleBusinessManager.psd1 b/AppleBusinessManager.psd1 new file mode 100644 index 0000000..fdc4a25 --- /dev/null +++ b/AppleBusinessManager.psd1 @@ -0,0 +1,132 @@ +# +# Module manifest for module 'ABMPS' +# +# Generated by: Liam Steckler +# +# Generated on: 6/11/2025 +# + +@{ + +# Script module or binary module file associated with this manifest. +RootModule = 'AppleBusinessManager.psm1' + +# Version number of this module. +ModuleVersion = '1.0' + +# Supported PSEditions +# CompatiblePSEditions = @() + +# ID used to uniquely identify this module +GUID = 'ba7191aa-8343-45d4-941b-52ddc74d7cc7' + +# Author of this module +Author = 'Liam Steckler' + +# Company or vendor of this module +CompanyName = 'Unknown' + +# Copyright statement for this module +Copyright = '(c) Liam Steckler. All rights reserved.' + +# Description of the functionality provided by this module +# Description = '' + +# Minimum version of the PowerShell engine required by this module +PowerShellVersion = '7.0.0' + +# Name of the PowerShell host required by this module +# PowerShellHostName = '' + +# Minimum version of the PowerShell host required by this module +# PowerShellHostVersion = '' + +# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# DotNetFrameworkVersion = '' + +# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. +# ClrVersion = '' + +# Processor architecture (None, X86, Amd64) required by this module +# ProcessorArchitecture = '' + +# Modules that must be imported into the global environment prior to importing this module +# RequiredModules = @() + +# Assemblies that must be loaded prior to importing this module +# RequiredAssemblies = @() + +# Script files (.ps1) that are run in the caller's environment prior to importing this module. +# ScriptsToProcess = @() + +# Type files (.ps1xml) to be loaded when importing this module +# TypesToProcess = @() + +# Format files (.ps1xml) to be loaded when importing this module +# FormatsToProcess = @() + +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +# NestedModules = @() + +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = '*' + +# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. +CmdletsToExport = '*' + +# Variables to export from this module +VariablesToExport = '*' + +# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. +AliasesToExport = '*' + +# DSC resources to export from this module +# DscResourcesToExport = @() + +# List of all modules packaged with this module +# ModuleList = @() + +# List of all files packaged with this module +# FileList = @() + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + # ProjectUri = '' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + # Prerelease string of this module + # Prerelease = '' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + +} # End of PrivateData hashtable + +# HelpInfo URI of this module +# HelpInfoURI = '' + +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = '' + +} + diff --git a/AppleBusinessManager.psm1 b/AppleBusinessManager.psm1 new file mode 100644 index 0000000..0f9057b --- /dev/null +++ b/AppleBusinessManager.psm1 @@ -0,0 +1,81 @@ +#Requires -Version 7.0 +#Requires -Module jwtPS +function Connect-AppleBusinessManager { + 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 + + $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) { + $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-AppleBusinessManagerOrgDevices { + return Invoke-AppleBusinessManagerPagedApiRequest -Uri "https://api-business.apple.com/v1/orgDevices" +} + +function Get-AppleBusinessManagerMdmServers { + return Invoke-AppleBusinessManagerPagedApiRequest -Uri "https://api-business.apple.com/v1/mdmServers" +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..dc9f16b --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Apple Business Manager API Module for PowerShell +To use this module, you'll need to convert the private key from Apple to a .NET friendly format: +```openssl pkcs8 -topk8 -inform PEM -outform PEM -in [path to your key from Apple] -out [path to where you want to save the converted key] -nocrypt``` \ No newline at end of file