๐ณ Recipe ยท Licensing & Cost Optimization
Export a Report of All Users and Their Assigned Licenses
Generate a complete CSV inventory of every Microsoft 365 user with their assigned license SKUs, resolved to friendly product names
Complexity
Beginner
Impact
read-only + reporting + license-audit + compliance + bulk
Context
Why This Matters
License reporting is one of the most common recurring tasks for a Microsoft 365 administrator. Finance wants to reconcile monthly spend, security wants to verify who holds privileged SKUs (E5 Security, P2, etc.), and IT leadership wants to identify unused or overprovisioned licenses before renewal.
This recipe produces a full per-user license inventory across the tenant: display name, UPN, and every assigned SKU (translated from GUIDs to human-readable product names like ENTERPRISEPACK or SPE_E5). Run it whenever you need an audit-ready snapshot โ typically before a renewal, during a license true-up, or as part of quarterly access reviews.
Microsoft 365 does not expose a single built-in export that joins users with their resolved license names, so admins either click through the GUI one user at a time, use the Licenses blade's limited export, or script against Microsoft Graph. Scripting is the fastest and most repeatable option.
Expected Outcomes
After running this recipe you will have:
- A CSV file (
User_License_Report.csv) with one row per user containing Display Name, User Principal Name, Account Enabled, and a comma-separated Licenses column. - License SKU GUIDs resolved to friendly
skuPartNumbervalues (e.g.ENTERPRISEPACK,SPE_E5,FLOW_FREE). - A clear view of unlicensed accounts โ useful for spotting guest users, shared mailboxes, room resources, and stale enabled users who are wasting no license but may still pose a risk.
- A reusable script you can schedule (Task Scheduler / Azure Automation) for monthly reporting.
Risks & Considerations
Before you run this:
- Data sensitivity. The output contains every user's UPN and license entitlements. Treat the CSV as confidential and store it in a location governed by your DLP / retention policy.
- Read-only scopes only. This recipe needs
User.Read.AllandOrganization.Read.All. Do not consent to write scopes (User.ReadWrite.All,Directory.ReadWrite.All) for a reporting task. - Guest users are included.
GET /usersreturns members and guests by default. If you only want employees, filter onuserType eq 'Member'. - Disabled accounts are included. Disabled accounts can still hold licenses (common licensing waste). Review before deprovisioning โ if you remove a license from a mailbox-enabled user without a grace-period plan, mailbox data can be deleted after 30 days.
- Friendly SKU names are internal codes.
skuPartNumbervalues likeSPE_E5are Microsoft's internal identifiers. Use Microsoft's product names and service plan identifiers reference to translate for non-technical stakeholders. - Large tenants require paging. Tenants over 999 users need
@odata.nextLinkhandling โ the script provided does this automatically.
Required Permissions
| Permission | Why It's Needed |
|---|---|
| User.Read.All | Read every user's profile and assignedLicenses property |
| Organization.Read.All | Read tenant subscribedSkus to map SKU GUIDs to skuPartNumber friendly names |
| Directory.Read.All | Alternative broader scope if User.Read.All is not available; grants the same read-only view |
The fastest way to get this done: just ask Dex. Copy the prompt below and paste it into your Dex conversation.
For IT Admins
Paste into Dex Pro
Export a license report from the Microsoft 365 Admin Center
The built-in GUI path produces a simpler report (no per-user SKU detail in a single file) but requires no scripting. Use this when you need a quick snapshot or when your account lacks Graph consent rights.
Option A โ Licenses blade (per-SKU view)
- Sign in to the Microsoft 365 Admin Center with a Global Reader, License Administrator, or Global Administrator account.
- Navigate to .
- Click a product (e.g. Microsoft 365 E3) to open the SKU detail page.
- Select the Assigned licenses tab.
- Click Export users (top of list) to download a CSV of users holding that SKU.
- Repeat for each SKU you care about. Merge the CSVs in Excel if you need a unified view.
Option B โ Active users export (all users, limited license detail)
- Go to .
- Click Export users at the top of the list.
- Confirm the export โ a CSV is downloaded with columns including
DisplayName,UserPrincipalName, andLicenses(semicolon-separated SKU part numbers). - Open in Excel and filter/pivot as needed.
Limitations of the GUI path: Option A requires multiple exports and manual merge. Option B gives one file but the Licenses column uses friendly names that differ slightly from Graph's skuPartNumber, and there is no filter for disabled users or guests. For any serious reporting, use the API or script path below.
Export via Microsoft Graph API
Two calls are needed: one to list users with their assignedLicenses, and one to get the tenant's SKU catalog so you can translate GUIDs to names.
1. Get all users with assigned licenses
GET https://graph.microsoft.com/v1.0/users?$select=id,displayName,userPrincipalName,accountEnabled,assignedLicenses&$top=999
Authorization: Bearer {access_token}Response (abbreviated):
{
"@odata.context": "...",
"@odata.nextLink": "https://graph.microsoft.com/v1.0/users?$skiptoken=...",
"value": [
{
"id": "b6d7d5f3-...",
"displayName": "Jane Doe",
"userPrincipalName": "jane@contoso.com",
"accountEnabled": true,
"assignedLicenses": [
{ "disabledPlans": [], "skuId": "6fd2c87f-b296-42f0-b197-1e91e994b900" }
]
}
]
}Paging: If @odata.nextLink is present in the response, follow it until it is absent. Tenants with more than 999 users will have multiple pages.
2. Get the tenant SKU catalog
GET https://graph.microsoft.com/v1.0/subscribedSkus
Authorization: Bearer {access_token}Response:
{
"value": [
{
"skuId": "6fd2c87f-b296-42f0-b197-1e91e994b900",
"skuPartNumber": "ENTERPRISEPACK",
"prepaidUnits": { "enabled": 100 },
"consumedUnits": 87
}
]
}3. Join in your client
Build a dictionary of skuId โ skuPartNumber from call 2, then iterate call 1's users and translate each assignedLicenses[].skuId. Emit a CSV row per user.
Alternative: per-user license detail
If you need service-plan-level detail (which sub-features are enabled/disabled), call GET /v1.0/users/{id}/licenseDetails per user. Note this is one request per user โ use only when you genuinely need service plan info.
Run via PowerShell (Microsoft.Graph module)
The script below uses the official Microsoft.Graph PowerShell module. It handles paging, resolves SKU GUIDs to friendly names, and exports a CSV.
Prerequisites
- Install the module (once per machine):
Install-Module Microsoft.Graph -Scope CurrentUser - Connect with read-only scopes:
Connect-MgGraph -Scopes "User.Read.All","Organization.Read.All"
What the script does
- Pulls all users with
Get-MgUser -All(automatic paging). - Pulls the SKU catalog with
Get-MgSubscribedSku. - Builds a hashtable to translate each
SkuIdinto aSkuPartNumber. - Emits one row per user with comma-joined license names.
- Writes
User_License_Report.csvto the current directory.
Run it, review the output, and share the CSV with whoever needs it.
Next steps