From m365
Manages Microsoft 365 licenses via Graph API: checks seats/SKUs, assigns/removes licenses, audits usage, finds unused licenses for MSP tenant optimization.
npx claudepluginhub wyre-technology/msp-claude-plugins --plugin m365This skill uses the workspace's default tool permissions.
M365 licensing is a top billing concern for MSPs. Licenses are purchased as SKU subscriptions, each containing bundles of service plans (Exchange, Teams, SharePoint, etc.). Efficient license management — finding unused seats, rightsizing SKUs, ensuring all users have what they need — directly impacts both the MSP's margin and the customer's costs.
Compares coding agents like Claude Code and Aider on custom YAML-defined codebase tasks using git worktrees, measuring pass rate, cost, time, and consistency.
Designs and optimizes AI agent action spaces, tool definitions, observation formats, error recovery, and context for higher task completion rates.
Designs, implements, and audits WCAG 2.2 AA accessible UIs for Web (ARIA/HTML5), iOS (SwiftUI traits), and Android (Compose semantics). Audits code for compliance gaps.
M365 licensing is a top billing concern for MSPs. Licenses are purchased as SKU subscriptions, each containing bundles of service plans (Exchange, Teams, SharePoint, etc.). Efficient license management — finding unused seats, rightsizing SKUs, ensuring all users have what they need — directly impacts both the MSP's margin and the customer's costs.
M365 Business Premium (subscription)
└── GUID: cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46
├── Exchange Online (service plan)
├── Microsoft Teams (service plan)
├── SharePoint Online (service plan)
├── Intune (service plan)
└── Entra ID P1 (service plan)
| State | Meaning |
|---|---|
Enabled | Service plan is active and usable |
Disabled | Plan is turned off for this user (license still assigned) |
Error | Assignment failed — usually missing usageLocation |
LockedOut | Tenant billing issue |
PendingInput | Waiting for additional configuration |
GET /v1.0/subscribedSkus?$select=skuPartNumber,skuId,consumedUnits,prepaidUnits,servicePlans
Response:
{
"value": [
{
"skuPartNumber": "SPE_E3",
"skuId": "05e9a617-0261-4cee-bb44-138d3ef5d965",
"consumedUnits": 42,
"prepaidUnits": {
"enabled": 50,
"suspended": 0,
"warning": 0
},
"servicePlans": [...]
}
]
}
Available seats = prepaidUnits.enabled - consumedUnits
GET /v1.0/users?$select=id,displayName,userPrincipalName,accountEnabled,assignedLicenses,usageLocation&$top=999
Filter by SKU GUID:
GET /v1.0/users?$filter=assignedLicenses/any(x:x/skuId eq cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46)&$select=id,displayName,userPrincipalName,accountEnabled
GET /v1.0/users?$filter=assignedLicenses/$count eq 0&$count=true&$select=id,displayName,userPrincipalName,accountEnabled
Requires
ConsistencyLevel: eventualheader and$count=true
POST /v1.0/users/{userId}/assignLicense
Content-Type: application/json
{
"addLicenses": [
{
"skuId": "cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46",
"disabledPlans": []
}
],
"removeLicenses": []
}
usageLocationmust be set on the user before assigning. UsePATCH /v1.0/users/{id}with"usageLocation": "US"first.
POST /v1.0/users/{userId}/assignLicense
Content-Type: application/json
{
"addLicenses": [],
"removeLicenses": ["cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46"]
}
Assign a license but disable specific plans (e.g., give E3 without Teams):
POST /v1.0/users/{userId}/assignLicense
Content-Type: application/json
{
"addLicenses": [
{
"skuId": "05e9a617-0261-4cee-bb44-138d3ef5d965",
"disabledPlans": ["57ff2da0-773e-42df-b2af-ffb7a2317929"]
}
],
"removeLicenses": []
}
Pull subscribedSkus and calculate:
warning state (near renewal, overallocated)Find licenses assigned to disabled accounts — these are reclaim candidates:
GET /v1.0/users?$filter=accountEnabled eq false and assignedLicenses/$count ne 0&$count=true&$select=id,displayName,userPrincipalName,assignedLicenses
Users licensed but not signing in (90+ days):
GET /v1.0/users?$filter=accountEnabled eq true&$select=id,displayName,userPrincipalName,assignedLicenses,signInActivity
Filter results where signInActivity.lastSignInDateTime < (today - 90 days).
| Optimization | Estimated Saving |
|---|---|
| Remove licenses from disabled accounts | # disabled × monthly seat cost |
| Downgrade inactive users to lighter SKU | SKU price delta × count |
| Recover unused purchased seats | (purchased - consumed) seats available |
| SKU Part Number | GUID | Notes |
|---|---|---|
SPE_E3 | 05e9a617-0261-4cee-bb44-138d3ef5d965 | M365 E3 |
SPE_E5 | 06ebc4ee-1bb5-47dd-8120-11324bc54e06 | M365 E5 |
O365_BUSINESS_PREMIUM | cbdc14ab-d96c-4c30-b9f4-6ada7cdc1d46 | M365 Business Premium |
ENTERPRISEPACK | 6fd2c87f-b296-42f0-b197-1e91e994b900 | Office 365 E3 |
AAD_PREMIUM | 078d2b04-f1bd-4111-bbd4-b4b1b354cef4 | Entra ID P1 |
AAD_PREMIUM_P2 | 84a661c4-e949-4bd2-a560-ed7766fcaf2b | Entra ID P2 |
EMS | efccb6f7-5641-4e0e-bd10-b4976e1bf68e | EMS E3 |
| Error | Cause | Resolution |
|---|---|---|
LicenseAssignmentError | No usageLocation on user | Set usageLocation first |
MutuallyExclusiveLicenses | Two conflicting SKUs | Remove old SKU before assigning new |
Request_ResourceNotFound | Invalid SKU GUID | Verify GUID against subscribedSkus |
Authorization_RequestDenied | Missing Directory.ReadWrite.All | Grant admin consent |
| Task | Microsoft Graph Permission |
|---|---|
| View subscribed SKUs | Directory.Read.All |
| View user licenses | User.Read.All |
| Assign/remove licenses | User.ReadWrite.All or Directory.ReadWrite.All |
| Sign-in activity | AuditLog.Read.All |