Skip to content
Home » Articles » Automating Microsoft 365 License Reclamation

Automating Microsoft 365 License Reclamation

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)

  1. Navigate to the Microsoft 365 admin center.
  2. Go to Users > Active users.
  3. Filter for “Disabled users” or search for a specific user you processed.
  4. Select the user, go to the Licenses and apps tab, and confirm that the licenses are no longer assigned.
  5. 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

  1. Permissions Issues: Ensure the account used to connect to Microsoft Graph has the necessary User.ReadWrite.All permissions. Without it, license removal will fail.
  2. User Not Found: If a user’s UserPrincipalName is incorrect or the user has been hard-deleted, Get-MgUser will not return an object, leading to errors.
  3. 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.
  4. Licenses Not Visible: Sometimes, newly reclaimed licenses might take a few minutes to reflect in the Microsoft 365 admin center’s available license count.
  5. 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.


MANAGED MODERN WORKPLACE & IT INFRASTRUCTURE

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.

Sources & References

Helpdesk.ca Assistant

Please enter your details to start chatting.

Hello! How can I help you today?