LIC-01: License Utilization Visibility
Overview
License utilization review is the process of analyzing how Microsoft 365 licenses are being used across your organization. Regular review helps organizations:
- Identify unused or underutilized licenses that can be reclaimed
- Ensure compliance with license agreements
- Optimize license spending by right-sizing subscriptions
- Prevent audit risks from over or under-deployment
- Plan for license renewals and true-ups
Unused licenses represent direct cost waste, while underutilized licenses may indicate opportunities for training or license reassignment.
Prerequisites
Required Roles
- Global Administrator - Full access to licensing and usage data
- License Administrator - License assignment management
- Reports Reader - Access to usage reports
- Billing Administrator - Access to subscription information
Required Licenses
- Any Microsoft 365 subscription (for usage reports)
- No additional license required for utilization review
Required Permissions
- Access to Microsoft 365 admin center
- Access to Microsoft Entra admin center
- Access to Power BI (optional, for advanced analytics)
Time Estimate
| Task | Duration |
|---|---|
| Export current license inventory | 30 minutes |
| Generate usage reports | 30 minutes |
| Analyze utilization data | 2-3 hours |
| Identify reclamation opportunities | 1-2 hours |
| Implement changes | 1-2 hours |
| Documentation | 30 minutes |
| Total | 5-8 hours |
Step-by-Step Instructions
Step 1: Export Current License Inventory
Get a complete picture of your license assignments:
-
Navigate to Microsoft 365 admin center: https://admin.microsoft.com
-
Go to Billing > Licenses
-
Review the summary showing:
- Total licenses owned
- Assigned licenses
- Available licenses
-
Click on each license type to see:
- Users assigned
- Usage statistics
Export License Data via PowerShell
For a detailed export:
# Connect to Microsoft Graph
Connect-MgGraph -Scopes "User.Read.All", "Directory.Read.All", "Organization.Read.All"
# Get all subscriptions
$subscriptions = Get-MgSubscribedSku
# Create report
$licenseReport = @()
foreach ($sku in $subscriptions) {
$licenseReport += [PSCustomObject]@{
SkuId = $sku.SkuId
SkuPartNumber = $sku.SkuPartNumber
CapabilityStatus = $sku.CapabilityStatus
TotalLicenses = $sku.PrepaidUnits.Enabled
AssignedLicenses = $sku.ConsumedUnits
AvailableLicenses = $sku.PrepaidUnits.Enabled - $sku.ConsumedUnits
UtilizationPercent = [math]::Round(($sku.ConsumedUnits / $sku.PrepaidUnits.Enabled) * 100, 2)
}
}
# Export to CSV
$licenseReport | Export-Csv "LicenseInventory-$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation
# Display summary
$licenseReport | Format-Table SkuPartNumber, TotalLicenses, AssignedLicenses, AvailableLicenses, UtilizationPercent
Step 2: Generate Usage Reports
Access Microsoft 365 usage analytics:
-
Navigate to Microsoft 365 admin center > Reports > Usage
-
Review available reports:
- Email activity
- OneDrive usage
- SharePoint usage
- Teams usage
- Microsoft 365 apps usage
- Active users
-
For each report, set date range (30, 90, or 180 days)
-
Export reports to Excel for analysis
Key Metrics to Track
| Metric | Location | What It Shows |
|---|---|---|
| Active users | Usage dashboard | Users with any activity |
| Email active users | Email activity | Users sending/receiving email |
| OneDrive files | OneDrive usage | File activity per user |
| Teams messages | Teams usage | Chat and meeting activity |
| Apps usage | Apps report | Office app usage |
Step 3: Identify Inactive Users
Find users with licenses but no recent activity:
# Connect to Microsoft Graph
Connect-MgGraph -Scopes "User.Read.All", "AuditLog.Read.All", "Reports.Read.All"
# Get all licensed users
$licensedUsers = Get-MgUser -All -Property DisplayName, UserPrincipalName, SignInActivity, AssignedLicenses |
Where-Object { $_.AssignedLicenses.Count -gt 0 }
# Find users with no sign-in in last 90 days
$inactiveThreshold = (Get-Date).AddDays(-90)
$inactiveUsers = $licensedUsers | Where-Object {
($null -eq $_.SignInActivity.LastSignInDateTime) -or
($_.SignInActivity.LastSignInDateTime -lt $inactiveThreshold)
}
# Create report
$inactiveReport = $inactiveUsers | Select-Object `
DisplayName,
UserPrincipalName,
@{N='LastSignIn';E={$_.SignInActivity.LastSignInDateTime}},
@{N='LicenseCount';E={$_.AssignedLicenses.Count}},
@{N='DaysSinceSignIn';E={
if ($_.SignInActivity.LastSignInDateTime) {
((Get-Date) - $_.SignInActivity.LastSignInDateTime).Days
} else {
"Never"
}
}}
# Export
$inactiveReport | Export-Csv "InactiveUsers-$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation
# Summary
Write-Host "`nInactive User Summary:"
Write-Host "Total licensed users: $($licensedUsers.Count)"
Write-Host "Inactive users (90+ days): $($inactiveUsers.Count)"
Write-Host "Potential license savings: $($inactiveUsers.Count) licenses"
Step 4: Analyze Service-Specific Usage
Determine which services users are actually using:
Email Usage Analysis
# Get mailbox statistics
$mailboxStats = Get-MgReportMailboxUsageDetail -Period D90 -OutFile "MailboxUsage.csv"
# Import and analyze
$mailboxData = Import-Csv "MailboxUsage.csv"
# Find inactive mailboxes
$inactiveMailboxes = $mailboxData | Where-Object {
$_.'Last Activity Date' -eq '' -or
(Get-Date $_.'Last Activity Date') -lt (Get-Date).AddDays(-90)
}
Write-Host "Mailboxes with no activity in 90 days: $($inactiveMailboxes.Count)"
OneDrive Usage Analysis
# Get OneDrive usage
$oneDriveStats = Get-MgReportOneDriveUsageAccountDetail -Period D90 -OutFile "OneDriveUsage.csv"
# Import and analyze
$oneDriveData = Import-Csv "OneDriveUsage.csv"
# Find unused OneDrive accounts
$unusedOneDrive = $oneDriveData | Where-Object {
$_.'Last Activity Date' -eq '' -or
(Get-Date $_.'Last Activity Date') -lt (Get-Date).AddDays(-90)
}
Write-Host "OneDrive accounts with no activity in 90 days: $($unusedOneDrive.Count)"
Teams Usage Analysis
# Get Teams user activity
$teamsStats = Get-MgReportTeamsUserActivityUserDetail -Period D90 -OutFile "TeamsUsage.csv"
# Import and analyze
$teamsData = Import-Csv "TeamsUsage.csv"
# Find inactive Teams users
$inactiveTeams = $teamsData | Where-Object {
$_.'Last Activity Date' -eq ''
}
Write-Host "Users with no Teams activity in 90 days: $($inactiveTeams.Count)"
Step 5: Calculate License Waste
Create a comprehensive waste analysis:
| License Type | Monthly Cost | Unused Licenses | Monthly Waste |
|---|---|---|---|
| Microsoft 365 E5 | $57 | 25 | $1,425 |
| Microsoft 365 E3 | $36 | 50 | $1,800 |
| Exchange Online P1 | $4 | 30 | $120 |
| Power BI Pro | $10 | 15 | $150 |
| Total | $3,495 |
Annual potential savings: $41,940
Step 6: Identify Downgrade Opportunities
Find users who could use a lower license tier:
# Define feature usage criteria for downgrade
# E5 -> E3: Not using advanced security, compliance, or voice
# E3 -> Business Premium: < 50 users, no advanced features
# Get users with E5 licenses
$e5Sku = Get-MgSubscribedSku | Where-Object { $_.SkuPartNumber -like "*E5*" }
$e5Users = Get-MgUser -All -Property DisplayName, UserPrincipalName, AssignedLicenses |
Where-Object { $_.AssignedLicenses.SkuId -eq $e5Sku.SkuId }
# Check for E5 feature usage (simplified - would need more detailed analysis)
$downgradeCandiates = @()
foreach ($user in $e5Users) {
# Check usage of E5-specific features
# This would require additional analysis of:
# - Advanced eDiscovery usage
# - Advanced compliance features
# - Phone System usage
# - Power BI Pro features
# For demo, flag users with low overall activity
$signIn = (Get-MgUser -UserId $user.Id -Property SignInActivity).SignInActivity
if ($signIn.LastSignInDateTime -lt (Get-Date).AddDays(-30)) {
$downgradeCandiates += $user
}
}
Write-Host "E5 users to review for potential downgrade: $($downgradeCandiates.Count)"
Step 7: Review Shared/Service Accounts
Identify licenses assigned to non-user accounts:
# Find service accounts with licenses
$potentialServiceAccounts = Get-MgUser -All -Property DisplayName, UserPrincipalName, AssignedLicenses, UserType |
Where-Object {
$_.AssignedLicenses.Count -gt 0 -and
($_.UserPrincipalName -like "svc-*" -or
$_.UserPrincipalName -like "service*" -or
$_.UserPrincipalName -like "admin*" -or
$_.DisplayName -like "*service*" -or
$_.DisplayName -like "*shared*")
}
Write-Host "Potential service/shared accounts with licenses: $($potentialServiceAccounts.Count)"
$potentialServiceAccounts | Select-Object DisplayName, UserPrincipalName | Format-Table
Consider:
- Service accounts often don't need full E3/E5 licenses
- Shared mailboxes don't require licenses (Exchange Online)
- Room/Equipment mailboxes don't require user licenses
Step 8: Create Reclamation Plan
Document licenses to be reclaimed:
| User | Current License | Reason | Action | Savings |
|---|---|---|---|---|
| john.smith@contoso.com | E5 | Terminated | Remove | $57/mo |
| svc-backup@contoso.com | E3 | Service account | Remove | $36/mo |
| jane.doe@contoso.com | E5 | Low usage | Downgrade to E3 | $21/mo |
| conf-room1@contoso.com | E3 | Room mailbox | Remove | $36/mo |
Step 9: Implement License Changes
Remove Licenses from Inactive Users
# Import list of users to remove licenses from
$usersToRemove = Import-Csv "LicenseReclamation.csv"
foreach ($user in $usersToRemove) {
# Get current license details
$mgUser = Get-MgUser -UserId $user.UserPrincipalName -Property AssignedLicenses
# Remove all licenses
foreach ($license in $mgUser.AssignedLicenses) {
Set-MgUserLicense -UserId $user.UserPrincipalName `
-AddLicenses @() `
-RemoveLicenses @($license.SkuId)
Write-Host "Removed license from: $($user.UserPrincipalName)"
}
}
Downgrade Licenses
# Get SKU IDs
$e5Sku = Get-MgSubscribedSku | Where-Object { $_.SkuPartNumber -eq "SPE_E5" }
$e3Sku = Get-MgSubscribedSku | Where-Object { $_.SkuPartNumber -eq "SPE_E3" }
# Downgrade users from E5 to E3
$usersToDowngrade = Import-Csv "LicenseDowngrade.csv"
foreach ($user in $usersToDowngrade) {
Set-MgUserLicense -UserId $user.UserPrincipalName `
-AddLicenses @(@{SkuId = $e3Sku.SkuId}) `
-RemoveLicenses @($e5Sku.SkuId)
Write-Host "Downgraded: $($user.UserPrincipalName) from E5 to E3"
}
Step 10: Establish Ongoing Monitoring
Set up regular license reviews:
Create Scheduled Report
# Save this as a scheduled script
$reportPath = "C:\Reports\LicenseUtilization"
$date = Get-Date -Format "yyyyMMdd"
# Generate reports
# ... (use scripts from earlier steps)
# Email report to stakeholders
$emailParams = @{
To = "it-manager@contoso.com", "finance@contoso.com"
From = "reports@contoso.com"
Subject = "Monthly License Utilization Report - $date"
Body = "Please find attached the monthly license utilization report."
Attachments = "$reportPath\LicenseReport-$date.csv"
SmtpServer = "smtp.contoso.com"
}
Send-MailMessage @emailParams
Key Metrics to Monitor Monthly
| Metric | Target | Action if Exceeded |
|---|---|---|
| License utilization | > 90% | Consider purchasing more |
| Unused licenses (30+ days) | < 5% | Review for reclamation |
| E5 usage of E5 features | > 50% | Downgrade if not using |
| Growth rate | Per forecast | Adjust provisioning |
Verification Checklist
After completing the license utilization review:
- License inventory exported and documented
- Usage reports generated for all services
- Inactive users identified (90+ days no sign-in)
- Service accounts reviewed for license necessity
- Shared/room mailboxes checked
- Downgrade candidates identified
- Reclamation plan created and approved
- License changes implemented
- Savings calculated and documented
- Ongoing monitoring schedule established
- Next review date scheduled
Troubleshooting
Issue: Cannot Access Usage Reports
Cause: Insufficient permissions.
Solution:
- Ensure Reports Reader or Global Admin role
- Check if usage analytics is enabled in settings
- Wait 48 hours after enabling for data to populate
Issue: Sign-In Data Not Available
Cause: Entra ID P1/P2 required for full sign-in activity.
Solution:
- Use usage reports as alternative (available with all licenses)
- Upgrade to P1/P2 for sign-in analytics
- Use audit logs as proxy for activity
Issue: Cannot Remove License from User
Cause: User may have dependencies on license.
Solution:
- Check for active holds (eDiscovery, retention)
- Remove user from licensed groups first
- Disable user before removing license
- Check for license dependencies (e.g., add-on requiring base)
Issue: Service Disruption After License Removal
Cause: License removed from active user.
Solution:
- Immediately reassign license
- Review removal criteria
- Add waiting period before removal
- Notify users before removal
Issue: Usage Data Seems Inaccurate
Cause: Reporting delay or data quality issue.
Solution:
- Reports have 48-72 hour delay
- Some activities may not be tracked
- Cross-reference with audit logs
- Contact Microsoft support for discrepancies
Cost Considerations
Potential Savings by License Type
| License Type | Monthly Cost | Typical Waste % | Savings Potential |
|---|---|---|---|
| Microsoft 365 E5 | $57 | 10-15% | $5.70-8.55/license |
| Microsoft 365 E3 | $36 | 10-20% | $3.60-7.20/license |
| Business Premium | $22 | 5-15% | $1.10-3.30/license |
| Exchange Online P1 | $4 | 15-25% | $0.60-1.00/license |
| Power BI Pro | $10 | 20-30% | $2.00-3.00/license |
Example Savings Calculation
| Organization Size | Typical Waste | Monthly Savings | Annual Savings |
|---|---|---|---|
| 100 users | 15 licenses | $540 | $6,480 |
| 500 users | 75 licenses | $2,700 | $32,400 |
| 1,000 users | 150 licenses | $5,400 | $64,800 |
| 5,000 users | 750 licenses | $27,000 | $324,000 |
License Review ROI
| Investment | Return |
|---|---|
| 8 hours quarterly review | Typical 10-15% license reclamation |
| Automation tools | Reduced review time, continuous monitoring |
| License management platform | 5-10% additional savings through optimization |
Best Practices
-
Review regularly:
- Monthly: Quick utilization check
- Quarterly: Detailed analysis and reclamation
- Annually: True-up planning and forecast
-
Automate monitoring:
- Schedule regular reports
- Set up alerts for thresholds
- Use Power BI for dashboards
-
Implement governance:
- Require manager approval for new licenses
- Automatic removal for terminated users
- Regular attestation of license necessity
-
Right-size from start:
- Start with lower tier, upgrade as needed
- Use trial periods before committing
- Review feature usage before purchasing
-
Track trends:
- Monitor growth patterns
- Forecast future needs
- Plan for renewals early
-
Communicate with stakeholders:
- Share savings with finance
- Report to leadership quarterly
- Educate users on license value
Related Controls
- GOV-01: Stale account review
- GOV-02: Account cleanup
- LOG-01: Audit log retention
Revision History
| Date | Version | Author | Changes |
|---|---|---|---|
| 2025-01-07 | 1.0 | TrueConfig | Initial release |