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 UserPrincipalNameThe 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 CyanInvoke-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, ErrorState 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.