Automate License Assignment and Cost Optimization in M365

Automate License Assignment and Cost Optimization in M365

For IT Managers and Microsoft 365 Administrators, Microsoft 365 license management often feels like a leaky faucet. Every month, premium licenses (like Microsoft 365 E3 or E5) are consumed by users who have either left the company, changed roles, or simply never logged in. At $30 to $50 per user per month, this “License Sprawl” translates to thousands of dollars wasted annually.

Manually auditing the Microsoft 365 Admin Center to unassign licenses is tedious and prone to human error. In 2026, modern IT administration demands Automated Cost Optimization. By leveraging Microsoft Entra Group-Based Licensing and Microsoft Graph PowerShell, you can easily find unused Microsoft 365 licenses, ensure they are dynamically assigned when needed, and automatically reclaim Microsoft 365 licenses when they aren’t.

In this guide, we will walk you through a comprehensive strategy to automate Microsoft 365 license removal, detect inactive licensed users, and optimize your M365 spend.

The M365 License Lifecycle

A robust Microsoft 365 license optimization strategy treats licenses as a dynamic resource that flows through a strict lifecycle.

1

Procurement

Purchasing block licenses via EA/CSP and loading them into the tenant.

2

Assignment

Dynamic provisioning via Entra ID Security Groups upon onboarding.

3

Auditing

Continuous monitoring of sign-in activity vs license consumption.

4

Revocation

Automated removal during offboarding or prolonged inactivity.

Phase 1: Embrace Group-Based Licensing

The first rule of Entra ID license management is: Stop assigning licenses directly to users.

Direct assignment is an administrative nightmare. If an employee transfers from Sales to Marketing, someone has to manually remove their Dynamics 365 license and assign them the Marketing tools.

Instead, use Microsoft Entra Group-Based Licensing (requires Entra ID P1 or P2).

  1. Create a Dynamic Security Group (e.g., All-FTE-Employees) based on realistic user properties that indicate an active employee. For example:
    (user.accountEnabled -eq true) and (user.employeeType -eq "Employee")
  2. Navigate to Microsoft 365 Admin Center > Billing > Licenses. (Microsoft now strongly emphasizes managing user/group license assignments directly in the Microsoft 365 Admin Center rather than the Entra portal).
  3. Assign the required Microsoft 365 SKUs directly to that Dynamic Group.

When an employee is onboarded, their Entra attributes pull them into the group, and they automatically receive the appropriate license, typically within a few minutes (as it is processed asynchronously by the licensing engine). When they are terminated (and their accountEnabled status is set to false), they fall out of the group, and the license is freed up for the next hire automatically.

License Management Methods Comparison

MethodComplexityAutomationRisk of Sprawl
Direct User AssignmentLowLowHigh
Group-Based LicensingMediumHighLow
Group-Based + Graph AuditMediumVery HighLow
Third-party AutomationLowVery HighVery Low

Phase 2: Finding License Waste with Microsoft Graph PowerShell

Even with Group-Based Licensing, you will encounter users who have active accounts but haven’t signed into Microsoft 365 for months. To reduce Microsoft 365 licensing costs, you must identify these dormant accounts.

Using the modern Microsoft Graph PowerShell SDK, we can generate an inactive licensed users report.

Accounts to Exclude From License Reclamation

Do not simply assume that “no sign-in for 90 days = candidate for license removal.” Many accounts are non-interactive but absolutely critical. You should exclude:

  • Emergency access (break-glass) accounts
  • Service accounts and Azure automation identities
  • Hybrid synchronization accounts
  • Shared mailbox accounts (though these typically shouldn’t have paid licenses anyway)
  • Long-term leave users approved by HR

The Microsoft Graph License Audit Script

The following script identifies any licensed user who has not successfully signed in for the past 90 days, while calculating potential cost savings.

# Requires Microsoft Graph permissions that allow access to SignInActivity 
# (e.g. AuditLog.Read.All, User.Read.All).
# Availability of sign-in data depends on your Entra licensing and audit 
# retention configuration.
Connect-MgGraph -Scopes "User.Read.All", "AuditLog.Read.All"

$InactiveDays = 90
$CostPerUser = 36 # Example cost for an E3/E5 license
$CutoffDate = (Get-Date).AddDays(-$InactiveDays)
$ExportPath = "C:\temp\M365_License_Waste_Report.csv"

Write-Host "Fetching users and sign-in activity..." -ForegroundColor Cyan
$Users = Get-MgUser -Filter "userType eq 'Member'" `
    -Property Id, DisplayName, UserPrincipalName, AssignedLicenses, SignInActivity `
    -All
$Report = @()

foreach ($User in $Users) {
    if ($User.AssignedLicenses.Count -gt 0) {
        # We intentionally use LastSuccessfulSignInDateTime rather than 
        # LastSignInDateTime because failed authentication attempts can 
        # otherwise make dormant accounts appear active.
        $LastSignIn = $User.SignInActivity.LastSuccessfulSignInDateTime
        
        if ($null -eq $LastSignIn -or $LastSignIn -lt $CutoffDate) {
            $Report += [PSCustomObject]@{
                DisplayName       = $User.DisplayName
                UserPrincipalName = $User.UserPrincipalName
                LastSignIn        = if ($LastSignIn) { $LastSignIn.ToString("yyyy-MM-dd") } else { "Never" }
                LicenseCount      = $User.AssignedLicenses.Count
            }
        }
    }
}

$ExportDir = Split-Path $ExportPath -Parent
if (-not (Test-Path $ExportDir)) { New-Item -ItemType Directory -Path $ExportDir | Out-Null }

$Report | Export-Csv -Path $ExportPath -NoTypeInformation -Encoding UTF8
Write-Host "Audit Complete! Found `$($Report.Count) inactive licensed users." -ForegroundColor Green
Write-Host "Report saved to: $ExportPath" -ForegroundColor Yellow

# Estimate Potential Savings
$PotentialSavings = $Report.Count * $CostPerUser
Write-Host "Potential monthly savings: `$`${PotentialSavings}" -ForegroundColor Cyan

For example, discovering 50 inactive E3 licenses at $36/user/month equals approximately $21,600 in annual waste. This is why a Microsoft Graph signInActivity report is so powerful.

Phase 3: Automating the Audit Workflow

Writing PowerShell scripts is highly effective, but running them manually on a weekly basis doesn’t scale. Enterprise organizations should move towards Automating the Audit end-to-end.

A typical enterprise architecture looks like this:

1. Azure Automation RunbookExecutes PowerShell Script Weekly
2. Microsoft Graph APIFetches User & Sign-in Data
3. Generate ReportCreates CSV of Inactive Users
4. Power Automate / Logic AppsEmails IT Team & HR with Report
5. Review & ApproveConfirm Exclusions & Approvals
6. Automated License RemovalReclaim Licenses via Group Sync

By hosting the script in Azure Automation (or Windows Task Scheduler for smaller environments), you create a continuous governance process rather than a quarterly cleanup project.

Phase 4: Automated License Revocation

Once you have identified the wasted licenses and cleared them with HR, you can reclaim them. If you are using Group-Based Licensing, simply disabling the user account (or updating their properties so they fall out of the Dynamic Group) is the safest method.

Note: By default, Microsoft 365 retains deleted user data for a limited period, though actual retention depends on workload-specific settings, retention policies, and organizational compliance configurations (such as Litigation Holds).

For organizations relying on direct license assignments, you can use the Set-MgUserLicense cmdlet to forcibly strip the license from an inactive account. Proceed with extreme caution—removing a license instantly removes access to associated services.

$UserId = "inactive-user@yourdomain.com"
$SkuToRemove = "SPE_E5" # Example SKU part number

$Sku = Get-MgSubscribedSku -All | Where-Object SkuPartNumber -eq $SkuToRemove

if ($Sku) {
    Set-MgUserLicense -UserId $UserId -AddLicenses @() `
        -RemoveLicenses @($Sku.SkuId)
    Write-Host "License `$($SkuToRemove) removed successfully." -ForegroundColor Green
}

Automated Cost Optimization with SPClean

If you don’t want to maintain custom Azure Automation Runbooks or write complex Graph API scripts, dedicated solutions like SPClean provide automated intelligence out-of-the-box.

SPClean continually scans your tenant for orphaned accounts, unused premium licenses, and abandoned SharePoint storage. With proactive alerts and automated remediation workflows, it helps organizations continuously optimize their M365 spend securely and efficiently.

Conclusion

Organizations commonly recover 5% to 15% of their Microsoft 365 licensing spend simply by implementing group-based licensing and regularly auditing inactive accounts. Combined with Microsoft Graph automation, license optimization becomes a continuous governance process rather than a stressful quarterly cleanup project. Take control of your M365 billing today, and redirect those IT funds to more impactful initiatives.

© 2026 M365 Automation. All rights reserved. Designed for M365 Admins.