Building a Microsoft 365 License Audit Report with PowerShell and Microsoft Graph
Introduction
Managing Microsoft 365 licensing at scale is not just about assigning licenses. It is also about visibility, governance, audit readiness, and operational control.
In many organizations, users may receive licenses in different ways:
- Directly assigned to the user
- Assigned through group-based licensing
- Assigned through legacy processes
- Assigned during migration or onboarding activities
Over time, this can make it difficult to answer a simple question:
Who has which Microsoft 365 license, and how was it assigned?
To make this easier, I created a PowerShell script called M365 License Audit Report.
The script uses Microsoft Graph PowerShell to collect user and license assignment information and exports the result into a timestamped CSV report. Microsoft Graph PowerShell supports retrieving users through Get-MgUser, including selected user properties when requested. Microsoft also documents Microsoft Graph PowerShell examples for group-based licensing scenarios, including license assignment and SKU lookup patterns.
Why I Built This
Microsoft 365 admin portals are useful, but they are not always ideal when you need repeatable reporting across a large tenant.
For example, IT and governance teams often need to know:
- Which users have E3, E5, F3, or other configured licenses?
- Are those licenses assigned directly or through a group?
- Are contractors and employees licensed correctly?
- Are direct license assignments being used where group-based licensing should be preferred?
- Can we produce CSV evidence for audit or compliance reviews?
- Are license assignments consistent after migration or cleanup work?
Checking this manually user by user is slow and hard to repeat.
This script helps convert license visibility into a repeatable report.
What the Script Does
The script:
- Connects to Microsoft Graph interactively
- Retrieves Microsoft 365 user details
- Checks configured license groups
- Identifies whether a user has those configured licenses
- Shows whether the assignment is direct or group-based
- Optionally resolves group names
- Exports the output to a CSV file
- Shows progress and estimated time while running
The output file is saved under the reports folder using this format:
m365_license_audit_report_yyyyMMdd_HHmmss.csvConfiguration
The script includes a simple $CONFIG block.
This allows the report to be customized without changing the core logic.
$CONFIG = @{
EmployeeTypes = @("Contractor","Employee")
LicenseGroups = [ordered]@{
F3 = @("SPE_F1", "M365_F1")
E3 = @("SPE_E3", "ENTERPRISEPACK")
E5 = @("SPE_E5", "ENTERPRISEPREMIUM")
}
IncludeAllUsers = $true
ResolveGroupNames = $false
OutputFolder = Join-Path $PSScriptRoot "reports"
}License Groups Are Fully Customizable
Although the example configuration includes F3, E3, and E5, the script is not limited to those licenses.
You can update the LicenseGroups section to track any license SKU that matters to your environment.
Example:
LicenseGroups = [ordered]@{
F3 = @("SPE_F1", "M365_F1")
E3 = @("SPE_E3", "ENTERPRISEPACK")
E5 = @("SPE_E5", "ENTERPRISEPREMIUM")
}The friendly names such as F3, E3, and E5 are just labels.
Each label can map to one or more SKU part numbers.
This is useful because different tenants may have different licensing combinations, product bundles, or historical SKU names.
Key Output Columns
The CSV report includes the following fields:
ObjectId
UserPrincipalName
DisplayName
Mail
EmployeeType
Department
JobTitle
UsageLocation
AccountEnabled
HasConfiguredLicense
LicenseSummary
LicenseAssignmentPathsThese columns are useful for operational review, audit evidence, and license governance.
For example:
HasConfiguredLicenseshows whether the user has any license from the configured license groups.LicenseSummaryshows which configured license was found.LicenseAssignmentPathsshows whether the license was assigned directly or through a group.
Why Assignment Path Matters
Knowing that a user has a license is useful. But knowing how the license was assigned is even more important. For large environments, group-based licensing is usually easier to govern than direct assignment. Microsoft documents group-based licensing as a way to assign one or more product licenses to a group so that members of that group receive those licenses.
This report helps identify:
- Users with direct license assignments
- Users licensed through groups
- Users with unexpected assignment paths
- Licensing drift after onboarding, migration, or cleanup activities
- Assignment inconsistencies across employee types
This visibility helps teams move from assumptions to evidence.
Example Use Cases
1. Monthly License Audit
Generate a monthly CSV report to validate who has configured Microsoft 365 licenses and how they were assigned. This can support recurring governance and operational reviews.
2. Direct Assignment Cleanup
If your organization prefers group-based licensing, this report can help identify users who still have direct license assignments. These users can then be reviewed and cleaned up where appropriate.
3. Contractor and Employee License Review
The script can use EmployeeType filtering when IncludeAllUsers is set to $false. This helps organizations review license allocation across employees, contractors, students, or other worker classifications.
4. Migration Validation
If users are moved from one license type to another, such as F3 to E3, this report can help validate the final state. It gives teams a timestamped CSV record of the license position after migration.
5. Audit and Compliance Evidence
The report can be used as supporting evidence for internal reviews. It helps show:
- Which users were included
- Which configured licenses were detected
- Whether accounts were enabled
- What employee type was recorded
- Whether the license came from a direct or group-based assignment
Performance Considerations
The script includes a setting called:
ResolveGroupNames = $falseWhen this is set to $false, the report runs faster because it does not perform extra lookups to resolve group display names.
When set to $true, the report becomes easier to read because group IDs are resolved to group names, but this may take longer in large tenants. For large environments, I recommend starting with $false. Then enable group name resolution only when needed.
Required Permissions
The script connects to Microsoft Graph using the following scopes:
User.Read.All
Directory.Read.AllThese permissions are needed to read user and directory license information. Depending on your tenant settings, admin consent may be required.
How to Run the Script
From the repository root, run:
pwsh .\M365LicenseAuditReport.ps1Or with Windows PowerShell:
powershell -ExecutionPolicy Bypass -File .\M365LicenseAuditReport.ps1The script will prompt for interactive Microsoft Graph sign-in. After completion, the CSV file will be created in the reports folder.
Security Guidance
The exported CSV may contain sensitive identity and licensing information.
Recommended practices:
- Store the CSV in an approved location
- Limit access to the report folder
- Do not share externally without review
- Remove or mask user data if needed
- Follow your organization’s data retention policies
License reports may look operational, but they can still contain personal and organizational data.
Final Thoughts
The M365 License Audit Report is a practical PowerShell tool for improving Microsoft 365 license visibility.
It helps teams understand:
- Who has configured licenses
- Which license groups are present
- Whether licenses are assigned directly or through groups
- Where cleanup may be needed
- How to produce repeatable audit evidence
This is not intended to replace a full license management platform. Instead, it provides a lightweight and flexible way to generate useful license assignment evidence using PowerShell and Microsoft Graph.
If you manage Microsoft 365 licensing at scale, especially in an environment using group-based licensing, this type of report can save time, improve governance, and support better audit readiness.
You can find the script here: