The Cost of Unmanaged Microsoft 365 Licenses
In the dynamic landscape of the modern workplace, employee turnover is a constant. While offboarding procedures often cover account deactivation, the crucial step of reclaiming associated Microsoft 365 licenses is frequently overlooked. This oversight leads to a silent drain on IT budgets, as organizations continue to pay for licenses assigned to inactive users. Beyond the financial implications, orphaned licenses can pose a security risk, providing potential backdoor access if not properly managed.
This guide outlines a systematic approach to identify and reclaim these unused licenses using PowerShell, transforming a common operational headache into an automated, cost-saving process.
Prerequisites and Environment Checklist
To implement automated license reclamation, ensure your environment meets the following requirements:
- Microsoft 365 Licensing: Applicable to all Microsoft 365 business and enterprise plans (e.g., Business Basic, Business Standard, Business Premium, E3, E5). The methodology focuses on managing assigned licenses, not the plan itself.
- Administrative Privileges: A global administrator or a User Administrator role in Microsoft 365 is required to manage user accounts and licenses.
- PowerShell Modules:
- Microsoft Graph PowerShell SDK: This module is essential for interacting with Microsoft 365 services, including user and license management.
- Azure AD PowerShell module (MSOnline): While Graph is the preferred modern approach, MSOnline might still be used for specific legacy commands or hybrid scenarios. For this guide, we primarily focus on Graph.
Installing Required PowerShell Modules
If you do not have the necessary modules installed, execute the following commands in an elevated PowerShell session:
# Install the Microsoft Graph PowerShell SDK
Install-Module -Name Microsoft.Graph -Scope CurrentUser
# Install the Azure AD PowerShell module (MSOnline) - Optional, but good to have
Install-Module -Name MSOnline -Scope CurrentUser
Step-by-Step Implementation: Automated License Reclamation
Our strategy involves identifying inactive users, confirming their status, and then removing their assigned licenses. We will define “inactive” as users whose accounts have been disabled.
Step 1: Connect to Microsoft Graph
First, establish a connection to Microsoft Graph with the necessary permissions.
Connect-MgGraph -Scopes "User.Read.All", "User.ReadWrite.All", "Organization.Read.All"
You will be prompted to authenticate. Ensure you use an account with the required administrative privileges.
Step 2: Identify Disabled User Accounts
We will retrieve all user accounts and filter for those that are disabled.
# Get all users and filter for disabled accounts
$DisabledUsers = Get-MgUser -All -Filter "accountEnabled eq false" -Property Id, DisplayName, UserPrincipalName, AccountEnabled
if ($DisabledUsers) {
Write-Host "Found $($DisabledUsers.Count) disabled user accounts."
$DisabledUsers | Select-Object DisplayName, UserPrincipalName | Format-Table -AutoSize
} else {
Write-Host "No disabled user accounts found."
}
This script fetches all users and specifically filters for accountEnabled eq false. It then displays the DisplayName and UserPrincipalName of these accounts.
Step 3: Identify Licenses Assigned to Disabled Users
Next, for each disabled user, we need to determine which Microsoft 365 licenses they currently hold.
$LicensesToReclaim = @()
foreach ($User in $DisabledUsers) {
Write-Host "Processing user: $($User.UserPrincipalName)"
$AssignedLicenses = Get-MgUserLicenseDetail -UserId $User.Id
if ($AssignedLicenses) {
foreach ($License in $AssignedLicenses) {
$LicenseSKU = (Get-MgSubscribedSku -SubscribedSkuId $License.SkuId).SkuPartNumber
$LicensesToReclaim += [PSCustomObject]@{
DisplayName = $User.DisplayName
UserPrincipalName = $User.UserPrincipalName
SKUId = $License.SkuId
SKUName = $LicenseSKU
}
Write-Host " - Found assigned license: $($LicenseSKU)"
}
} else {
Write-Host " - No licenses found for this user."
}
}
if ($LicensesToReclaim.Count -gt 0) {
Write-Host "`nSummary of licenses to reclaim:"
$LicensesToReclaim | Format-Table -AutoSize
} else {
Write-Host "`nNo licenses found on disabled accounts for reclamation."
}
This loop iterates through each disabled user, retrieves their assigned license details, and maps the SkuId to a human-readable SkuPartNumber. This provides a clear overview of which licenses are candidates for reclamation.
Step 4: Remove Licenses from Disabled User Accounts
Before proceeding with this step, carefully review the $LicensesToReclaim summary. This is the critical action where licenses are removed.
# IMPORTANT: This script will remove licenses. Review the output from Step 3 carefully.
# Uncomment the following section to proceed with license removal.
# foreach ($LicenseEntry in $LicensesToReclaim) {
# Write-Host "Attempting to remove license $($LicenseEntry.SKUName) from $($LicenseEntry.UserPrincipalName)..."
# try {
# $UserObjectId = (Get-MgUser -Filter "UserPrincipalName eq '$($LicenseEntry.UserPrincipalName)'").Id
#
# # Create an array of LicenseAssignmentState objects to remove.
# # We need to specify the SkuId of the license to remove.
# $LicenseRemoveParam = @{
# RemoveLicenses = @($LicenseEntry.SKUId)
# }
#
# Set-MgUserLicense -UserId $UserObjectId -Body $LicenseRemoveParam
# Write-Host " SUCCESS: License $($LicenseEntry.SKUName) removed from $($LicenseEntry.UserPrincipalName)."
# } catch {
# Write-Warning " ERROR: Failed to remove license $($LicenseEntry.SKUName) from $($LicenseEntry.UserPrincipalName). Error: $($_.Exception.Message)"
# }
# }
Write-Host "`nScript completed. Manually review the status of licenses in the Microsoft 365 admin center."
Note: The license removal section is commented out (#) by default. Uncomment it only after you have thoroughly reviewed the list of licenses to be reclaimed and are confident in proceeding. The Set-MgUserLicense cmdlet is used to modify the licenses assigned to a user. We specify the RemoveLicenses parameter with the SkuId of the license we want to remove.
Verification and Functional Testing
After running the script, verify the license reclamation through the Microsoft 365 admin center or via PowerShell.
GUI Verification (Microsoft 365 Admin Center)
- Navigate to the Microsoft 365 admin center.
- Go to Users > Active users.
- Filter for “Disabled users” or search for a specific user you processed.
- Select the user, go to the Licenses and apps tab, and confirm that the licenses are no longer assigned.
- Check Billing > Licenses to see the updated count of available licenses.
PowerShell Verification
You can re-run a modified version of Step 3 to confirm licenses have been removed:
$UserPrincipalNameToCheck = "disableduser@yourdomain.com" # Replace with a disabled user's UPN
try {
$User = Get-MgUser -Filter "UserPrincipalName eq '$UserPrincipalNameToCheck'" -Property Id, DisplayName, UserPrincipalName, AccountEnabled
if ($User) {
Write-Host "Checking licenses for $($User.DisplayName) ($($User.UserPrincipalName))..."
$AssignedLicenses = Get-MgUserLicenseDetail -UserId $User.Id
if ($AssignedLicenses) {
Write-Host " Licenses still assigned:"
foreach ($License in $AssignedLicenses) {
$LicenseSKU = (Get-MgSubscribedSku -SubscribedSkuId $License.SkuId).SkuPartNumber
Write-Host " - $($LicenseSKU)"
}
} else {
Write-Host " No licenses found for this user. Reclamation successful."
}
} else {
Write-Host "User '$UserPrincipalNameToCheck' not found."
}
} catch {
Write-Warning "Error checking user licenses: $($_.Exception.Message)"
}
This snippet allows you to query a specific disabled user and confirm their license status post-reclamation.
Common Troubleshooting Pitfalls and Rollback
Common Pitfalls
- Permissions Issues: Ensure the account used to connect to Microsoft Graph has the necessary
User.ReadWrite.Allpermissions. Without it, license removal will fail. - User Not Found: If a user’s
UserPrincipalNameis incorrect or the user has been hard-deleted,Get-MgUserwill not return an object, leading to errors. - Sync Delays: In hybrid environments, changes might take time to synchronize from on-premises Active Directory to Entra ID (Azure AD). Allow for replication delays.
- Licenses Not Visible: Sometimes, newly reclaimed licenses might take a few minutes to reflect in the Microsoft 365 admin center’s available license count.
- Licenses with Dependencies: Be aware of licenses that might have dependencies or are part of bundled SKUs. Removing a core license might impact other services.
Rollback (Re-assigning a License)
If a license was mistakenly removed, you can re-assign it using PowerShell. You will need the SkuId of the license you wish to re-assign and the UserPrincipalName of the user.
$UserPrincipalName = "user.mistake@yourdomain.com" # Replace with the affected user's UPN
$LicenseSkuIdToAssign = "YOUR_LICENSE_SKU_ID_HERE" # Example: "b05e124f-c7cc-45a0-a68e-04f877943070" for an E3 license
try {
$UserObjectId = (Get-MgUser -Filter "UserPrincipalName eq '$UserPrincipalName'").Id
if ($UserObjectId) {
Write-Host "Attempting to re-assign license to $($UserPrincipalName)..."
# Get existing licenses to construct the correct body for Set-MgUserLicense
$ExistingLicenses = Get-MgUserLicenseDetail -UserId $UserObjectId | Select-Object -ExpandProperty SkuId
$LicensesToAssign = $ExistingLicenses + $LicenseSkuIdToAssign | Select-Object -Unique
$LicenseAddParam = @{
AddLicenses = $LicensesToAssign
}
Set-MgUserLicense -UserId $UserObjectId -Body $LicenseAddParam
Write-Host " SUCCESS: License re-assigned to $($UserPrincipalName)."
} else {
Write-Warning " ERROR: User '$UserPrincipalName' not found."
}
} catch {
Write-Warning " ERROR: Failed to re-assign license to $($UserPrincipalName). Error: $($_.Exception.Message)"
}
To find the SkuId for your licenses, you can use:
Get-MgSubscribedSku | Select-Object SkuPartNumber, SkuId
Need Urgent IT Assistance or Perimeter Defense?
Is your organization vulnerable to active edge exploits, credential harvesting, or compliance audit penalties? Helpdesk.ca provides 24/7/365 managed IT support, emergency incident triage, and co-managed IT escalation for Canadian enterprises.
- Schedule an IT Discovery Call: Book a 15-Minute Consultation With Our Engineering Team
- Emergency Incident Escalation: Connect directly with our frontline operations desk via Helpdesk.ca Support
Modernize Your IT Support with Guaranteed 15-Minute SLAs
From zero-touch Intune provisioning to automated employee onboarding and proactive 24/7 endpoint management, Helpdesk.ca keeps Canadian teams productive and secure.
