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

TaskDuration
Export current license inventory30 minutes
Generate usage reports30 minutes
Analyze utilization data2-3 hours
Identify reclamation opportunities1-2 hours
Implement changes1-2 hours
Documentation30 minutes
Total5-8 hours

Step-by-Step Instructions

Step 1: Export Current License Inventory

Get a complete picture of your license assignments:

  1. Navigate to Microsoft 365 admin center: https://admin.microsoft.com

  2. Go to Billing > Licenses

  3. Review the summary showing:

    • Total licenses owned
    • Assigned licenses
    • Available licenses
  4. 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:

  1. Navigate to Microsoft 365 admin center > Reports > Usage

  2. Review available reports:

    • Email activity
    • OneDrive usage
    • SharePoint usage
    • Teams usage
    • Microsoft 365 apps usage
    • Active users
  3. For each report, set date range (30, 90, or 180 days)

  4. Export reports to Excel for analysis

Key Metrics to Track

MetricLocationWhat It Shows
Active usersUsage dashboardUsers with any activity
Email active usersEmail activityUsers sending/receiving email
OneDrive filesOneDrive usageFile activity per user
Teams messagesTeams usageChat and meeting activity
Apps usageApps reportOffice 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 TypeMonthly CostUnused LicensesMonthly Waste
Microsoft 365 E5$5725$1,425
Microsoft 365 E3$3650$1,800
Exchange Online P1$430$120
Power BI Pro$1015$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:

UserCurrent LicenseReasonActionSavings
john.smith@contoso.comE5TerminatedRemove$57/mo
svc-backup@contoso.comE3Service accountRemove$36/mo
jane.doe@contoso.comE5Low usageDowngrade to E3$21/mo
conf-room1@contoso.comE3Room mailboxRemove$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

MetricTargetAction 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 ratePer forecastAdjust 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:

  1. Ensure Reports Reader or Global Admin role
  2. Check if usage analytics is enabled in settings
  3. 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:

  1. Use usage reports as alternative (available with all licenses)
  2. Upgrade to P1/P2 for sign-in analytics
  3. Use audit logs as proxy for activity

Issue: Cannot Remove License from User

Cause: User may have dependencies on license.

Solution:

  1. Check for active holds (eDiscovery, retention)
  2. Remove user from licensed groups first
  3. Disable user before removing license
  4. Check for license dependencies (e.g., add-on requiring base)

Issue: Service Disruption After License Removal

Cause: License removed from active user.

Solution:

  1. Immediately reassign license
  2. Review removal criteria
  3. Add waiting period before removal
  4. Notify users before removal

Issue: Usage Data Seems Inaccurate

Cause: Reporting delay or data quality issue.

Solution:

  1. Reports have 48-72 hour delay
  2. Some activities may not be tracked
  3. Cross-reference with audit logs
  4. Contact Microsoft support for discrepancies

Cost Considerations

Potential Savings by License Type

License TypeMonthly CostTypical Waste %Savings Potential
Microsoft 365 E5$5710-15%$5.70-8.55/license
Microsoft 365 E3$3610-20%$3.60-7.20/license
Business Premium$225-15%$1.10-3.30/license
Exchange Online P1$415-25%$0.60-1.00/license
Power BI Pro$1020-30%$2.00-3.00/license

Example Savings Calculation

Organization SizeTypical WasteMonthly SavingsAnnual Savings
100 users15 licenses$540$6,480
500 users75 licenses$2,700$32,400
1,000 users150 licenses$5,400$64,800
5,000 users750 licenses$27,000$324,000

License Review ROI

InvestmentReturn
8 hours quarterly reviewTypical 10-15% license reclamation
Automation toolsReduced review time, continuous monitoring
License management platform5-10% additional savings through optimization

Best Practices

  1. Review regularly:

    • Monthly: Quick utilization check
    • Quarterly: Detailed analysis and reclamation
    • Annually: True-up planning and forecast
  2. Automate monitoring:

    • Schedule regular reports
    • Set up alerts for thresholds
    • Use Power BI for dashboards
  3. Implement governance:

    • Require manager approval for new licenses
    • Automatic removal for terminated users
    • Regular attestation of license necessity
  4. Right-size from start:

    • Start with lower tier, upgrade as needed
    • Use trial periods before committing
    • Review feature usage before purchasing
  5. Track trends:

    • Monitor growth patterns
    • Forecast future needs
    • Plan for renewals early
  6. 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

DateVersionAuthorChanges
2025-01-071.0TrueConfigInitial release