# m365-license-usage.ps1 — assigned vs total per M365 SKU via Microsoft Graph. # # One-time setup: # 1. Register an Azure AD application (App registrations → New registration). # 2. Under API permissions: Microsoft Graph → Application permissions # → Organization.Read.All. Grant admin consent. # 3. Certificates & secrets → New client secret. Copy the VALUE (not the id). # 4. Note your Directory (tenant) ID and Application (client) ID. # 5. Fill in the three values below — or read them from a vault / env vars. $Url = "YOUR_URL/m365licenses" $TenantId = "YOUR_TENANT_ID" $ClientId = "YOUR_APP_ID" $ClientSecret = "YOUR_APP_SECRET" # --- exchange client credentials for a Graph token -------------------------- try { $Token = Invoke-RestMethod -Method Post ` -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" ` -Body @{ client_id = $ClientId client_secret = $ClientSecret scope = "https://graph.microsoft.com/.default" grant_type = "client_credentials" } } catch { $Body = (@{ error = "token request failed: $($_.Exception.Message)" }) | ConvertTo-Json -Compress Invoke-RestMethod -Uri $Url -Method Post -Body $Body -ContentType "application/json" -TimeoutSec 15 | Out-Null exit 1 } # --- enumerate subscribed SKUs --------------------------------------------- $Subs = Invoke-RestMethod -Method Get ` -Uri "https://graph.microsoft.com/v1.0/subscribedSkus" ` -Headers @{ Authorization = "Bearer $($Token.access_token)" } $Skus = @{} $AnyFull = $false foreach ($s in $Subs.value) { $consumed = [int]$s.consumedUnits $enabled = [int]$s.prepaidUnits.enabled $available = $enabled - $consumed $Skus[$s.skuPartNumber] = @{ consumed = $consumed enabled = $enabled available = $available pct_used = if ($enabled -gt 0) { [math]::Round(($consumed / $enabled) * 100, 1) } else { 0 } } if ($enabled -gt 0 -and $available -le 0) { $AnyFull = $true } } $Body = (@{ tenant_id = $TenantId sku_count = $Skus.Count skus = $Skus any_full = $AnyFull }) | ConvertTo-Json -Compress -Depth 5 Invoke-RestMethod -Uri $Url -Method Post -Body $Body ` -ContentType "application/json" -TimeoutSec 15 | Out-Null