๐ณ Recipe ยท Licensing & Cost Optimization
Bulk Remove All Licenses from Multiple M365 Users via Microsoft Graph
Reclaim Microsoft 365 SKUs from a list of users by looking up their current license assignments and calling assignLicense with a removeLicenses payload
Complexity
Intermediate
Impact
licensing + cost-optimization + offboarding + bulk-operation + graph-api
Context
Why This Matters
Removing licenses from users is a routine but high-impact task. Common triggers include offboarding, role changes, cost optimization reviews, converting users to shared mailboxes, or reclaiming unused SKUs flagged in a license utilization report.
The Microsoft 365 Admin Center supports this one user at a time, but when you have a list of 5, 50, or 500 users, a scripted approach against Microsoft Graph is far faster and auditable. This recipe walks through the full pattern: resolve each UPN to a user ID, enumerate currently assigned SKUs via licenseDetails, then POST to /users/{id}/assignLicense with the SKUs in removeLicenses.
Run this when you have a confirmed list of UPNs and you intend to remove all direct license assignments from those users. Licenses inherited from group-based licensing cannot be removed with this call โ you must remove the user from the licensing group instead.
Expected Outcomes
After completing this recipe you will have:
- Verified each target user exists and resolved their object ID
- A snapshot of every SKU assigned directly to each user (for rollback if needed)
- All direct license assignments removed from the specified users
- Released SKU seats returned to your tenant's available pool
- A log/report of successes and failures per user
Risks & Considerations
Warnings and gotchas
- Data loss risk: Removing an Exchange Online license starts a 30-day grace period, after which the mailbox is permanently deleted. Removing OneDrive licenses similarly triggers retention timers. Do not run against active users without a plan.
- Group-based licenses:
assignLicensewithremoveLicensesonly removes direct assignments. If the user inherits the same SKU via a group, it will be reassigned immediately. CheckassignedByGroupon eachlicenseDetailsentry. - Teams and SharePoint access: Users will lose access to Teams, SharePoint, and most M365 services immediately after license removal.
- Always capture a before-state of assigned SKUs so you can re-add them if removal was in error.
- Test with one user first before running against the full list.
$expand=licenseDetailson/usersis not supported by Graph โ you must fetch licenseDetails per user in a separate call or via$batch.
Required Permissions
| Permission | Why It's Needed |
|---|---|
| User.ReadWrite.All | Required to call assignLicense and modify user license assignments |
| Directory.Read.All | Required to read user objects and licenseDetails for each target user |
| Organization.Read.All | Recommended to resolve SKU IDs to friendly SKU part numbers in your reports |
The fastest way to get this done: just ask Dex. Copy the prompt below and paste it into your Dex conversation.
For IT Admins
Paste into Dex Pro
Remove licenses via the Microsoft 365 Admin Center
Best for one-off removals or when you need to review each user before acting. For 5+ users, prefer the API or PowerShell path.
- Sign in to the Microsoft 365 Admin Center as a Global Administrator or License Administrator.
- In the left navigation, go to .
- For each user in your list:
- Click the user's display name to open their details pane.
- Select the Licenses and apps tab.
- Uncheck every assigned license checkbox.
- Click Save changes.
- To confirm, refresh the user list and filter by โ the target users should now appear.
Alternative: Billing โ Licenses view
- Go to .
- Click the SKU you want to reclaim (e.g., Microsoft 365 E5).
- On the Assigned licenses tab, select the target users and click Unassign licenses.
Note: The Admin Center will not remove licenses that are inherited from a group. For those, go to , find the licensing group, and remove the user from group membership.
Remove licenses via Microsoft Graph
The removal pattern is a three-step process per user: resolve the UPN to an ID, list currently assigned direct SKUs, then POST an assignLicense call with those SKUs in the removeLicenses array.
Step 1 โ Resolve each UPN to an object ID
GET https://graph.microsoft.com/v1.0/users/{upn}?$select=id,displayName,userPrincipalNameExample response:
{
"id": "7eacb09b-1e01-4974-b3ec-7271c8c61eeb",
"displayName": "Pradeep Gupta",
"userPrincipalName": "PradeepG@contoso.onmicrosoft.com"
}Step 2 โ Get current license assignments
GET https://graph.microsoft.com/v1.0/users/{id}/licenseDetailsNote: $expand=licenseDetails on the /users collection is not supported. You must fetch licenseDetails per user.
Example response:
{
"value": [
{
"skuId": "c42b9cae-ea4f-4ab7-9717-81576235ccac",
"skuPartNumber": "ENTERPRISEPACK"
}
]
}Step 3 โ Remove the licenses
POST https://graph.microsoft.com/v1.0/users/{id}/assignLicense
Content-Type: application/json
{
"addLicenses": [],
"removeLicenses": [
"c42b9cae-ea4f-4ab7-9717-81576235ccac"
]
}A 200 OK with the updated user object indicates success. Repeat the POST for each user in your target list, passing the SKU IDs you collected in step 2.
Optimization: use $batch
For larger lists, combine up to 20 requests per call using POST https://graph.microsoft.com/v1.0/$batch to reduce latency.
Error handling
- 404 โ User not found. Verify UPN spelling.
- 400 License assignment failed โ Typically a conflict with group-based licensing or a disabled SKU. Inspect
assignedByGroupon the licenseDetails entry. - 429 โ Throttled. Honor the
Retry-Afterheader.
Remove licenses with Microsoft Graph PowerShell
The Microsoft.Graph PowerShell SDK provides Set-MgUserLicense, which wraps the same assignLicense endpoint.
Prerequisites
# Install the Microsoft Graph PowerShell SDK (one-time)
Install-Module Microsoft.Graph -Scope CurrentUser
# Connect with the required scopes
Connect-MgGraph -Scopes "User.ReadWrite.All","Directory.Read.All"Core pattern
# Define target users
$upns = @(
'alice@contoso.com',
'bob@contoso.com'
)
foreach ($upn in $upns) {
$user = Get-MgUser -UserId $upn
$licenses = Get-MgUserLicenseDetail -UserId $user.Id
$skuIds = $licenses.SkuId
if ($skuIds) {
Set-MgUserLicense -UserId $user.Id -AddLicenses @() -RemoveLicenses $skuIds
}
}The downloadable script below adds pre-flight validation, per-user logging, CSV export, a -WhatIf dry-run mode, and detection of group-inherited licenses.
Next steps