contents
The Problem
Group-based licensing in Entra ID is great until it is not. License assignments get stuck in error states. A user’s license shows “Error” in the admin center. Maybe the license pool ran out temporarily and now there are available licenses, but Entra never retried. Maybe a service plan conflict caused a failure that has since been resolved. Maybe you just migrated a batch of users and the license processing stalled.
The Entra admin center lets you reprocess one user at a time. Click user, click Licenses, click Reprocess. Now do that for 200 users who all hit the same error during a bulk migration.
Enter Invoke-MgLicenseUser – the Graph PowerShell SDK cmdlet that triggers the reprocessLicenseAssignment endpoint for a user.
How It Works
One script, three modes:
# The nuclear option -- every licensed member in the tenant
.\Invoke-UserLicenseReprocessing.ps1
# The surgical option -- specific users from a CSV
.\Invoke-UserLicenseReprocessing.ps1 -CsvPath "C:\Reports\Users.csv"
# The single-user fix -- one person, right now
.\Invoke-UserLicenseReprocessing.ps1 -UserPrincipalName "[email protected]"
All three modes do the same thing: look up the target users, call Invoke-MgLicenseUser for each, and report results. The -TenantId parameter is available on all modes for multi-tenant environments.
Prerequisites
- PowerShell 5.1+ with the Microsoft Graph PowerShell SDK
Microsoft.Graph.Authentication(forConnect-MgGraph)Microsoft.Graph.Users(forGet-MgUser)Microsoft.Graph.Users.Actions(forInvoke-MgLicenseUser)- Permission:
User.ReadWrite.All(delegated). If running unattended via a service principal, this is an application permission that grants write access to all user objects in the tenant – scope it carefully. - For CSV mode: a CSV file with a
UPNcolumn
Implementation
All Licensed Users
Without parameters, the script retrieves every licensed member user and reprocesses them all:
[Array]$allUsers = Get-MgUser `
-Filter "assignedLicenses/`$count ne 0 and userType eq 'Member'" `
-ConsistencyLevel eventual `
-CountVariable Records `
-All `
-Property Id, AssignedLicenses, UserPrincipalName |
Sort-Object UserPrincipalName
The filter assignedLicenses/$count ne 0 and userType eq 'Member' excludes guest users and only targets users who actually have license assignments. -ConsistencyLevel eventual is required for the $count filter to work – without it, Graph returns a 400.
Specific Users from CSV
With -CsvPath, the script reads UPNs and looks up each user individually:
.\Invoke-UserLicenseReprocessing.ps1 -CsvPath "C:\Reports\Users.csv"
If a UPN doesn’t resolve, the script warns and moves on to the next user – it doesn’t stop. This is intentional: in a batch of 200 users, you don’t want one typo in the CSV to block the other 199.
The CSV format is just a UPN column:
Single User
For one-off fixes, pass the UPN directly:
.\Invoke-UserLicenseReprocessing.ps1 -UserPrincipalName "[email protected]"
If the user isn’t found, the script exits with a warning. No ambiguity, no partial processing.
The Reprocessing Loop
All three modes feed into the same loop:
$successCount = 0
$errorCount = 0
foreach ($user in $users) {
try {
Invoke-MgLicenseUser -UserId $user.Id -ErrorAction Stop | Out-Null
Write-Host "Reprocessed: $($user.UserPrincipalName)" -ForegroundColor Green
$successCount++
}
catch {
Write-Host "Error: $($user.UserPrincipalName): $_" -ForegroundColor Red
$errorCount++
}
}
Write-Host "Done. $successCount succeeded, $errorCount failed." -ForegroundColor Cyan
Invoke-MgLicenseUser triggers an asynchronous reprocessing – it tells Entra to re-evaluate the user’s license state. The actual processing happens in the background. Don’t expect instant results; give it a few minutes.
When to Use Which Mode
All users – after a licensing SKU change (E3 to E5 migration), a known outage that caused widespread failures, or a major group membership restructure.
CSV – after a bulk user migration where specific users hit errors, or when you have a support ticket with a list of affected users.
Single user – when one person’s license is stuck and you just need to fix it now.
Verifying Results
After reprocessing, give it a few minutes and then check whether the errors cleared:
Get-MgUser -UserId "[email protected]" -Property licenseAssignmentStates |
Select-Object -ExpandProperty LicenseAssignmentStates |
Select-Object AssignedByGroup, State, Error
State should show Active and Error should be empty. If errors persist, the underlying issue hasn’t been resolved – a service plan conflict, insufficient licenses, or a misconfigured group assignment.
Security Considerations
User.ReadWrite.Allis a powerful permission.Invoke-MgLicenseUserdoesn’t modify licenses directly – it triggers reprocessing – but the permission scope allows writing to user objects broadly. In an application context (service principal), this grants tenant-wide user write access. Be deliberate about who gets this.- The “all users” mode processes every licensed user in the tenant. In a large tenant, that’s thousands of API calls. The Graph SDK handles 429 throttling automatically by respecting the
Retry-Afterheader – you don’t need to addStart-Sleepcalls. - Each
reprocessLicenseAssignmentcall is idempotent. Running it on a user whose licenses are already healthy does nothing – it re-evaluates and finds nothing to fix. Harmless.
When to Use This / When Not To
Use this when license assignments are stuck in error state, after license SKU changes, after resolving service plan conflicts, or after restoring license availability.
Don’t use this as a routine task. If you find yourself running this regularly, the problem is not licenses – it’s your licensing model. Figure out what keeps breaking. And reprocessLicenseAssignment does not fix conflicts caused by incompatible service plans – you need to resolve the conflict first, then reprocess.


