Export-LicenseDetails
Exports Microsoft 365 license and subscription details to an Excel report.
Description
Connects to Microsoft Graph to retrieve subscription and license assignment data for the tenant. Resolves SKU part numbers to friendly display names using Microsoft's published mapping, calculates commitment and billing terms, and exports the results to a formatted Excel workbook.
Parameters
-GraphCloud
The Microsoft Graph cloud environment to connect to. Valid values: Global, USGov, USGovDoD, Germany, China. Defaults to Global.
| Property | Value |
|---|---|
| Type | String |
| Required | false |
| Default | Global |
-Tenant
The tenant ID to connect to. If omitted, the default tenant for the authenticated account is used.
| Property | Value |
|---|---|
| Type | String |
| Required | false |
-Out
Output file path for the Excel report. Defaults to the user's Downloads folder with a timestamped filename. Automatically appends .xlsx if not present.
| Property | Value |
|---|---|
| Type | String |
| Required | false |
Examples
EXAMPLE 1
EXAMPLE 2
EXAMPLE 3
Requires -Modules Microsoft.Graph.Authentication, ImportExcel
Script
Download Export-LicenseDetails.ps1
<#
.SYNOPSIS
Exports Microsoft 365 license and subscription details to an Excel report.
.DESCRIPTION
Connects to Microsoft Graph to retrieve subscription and license assignment data for
the tenant. Resolves SKU part numbers to friendly display names using Microsoft's
published mapping, calculates commitment and billing terms, and exports the results
to a formatted Excel workbook.
.PARAMETER GraphCloud
The Microsoft Graph cloud environment to connect to.
Valid values: Global, USGov, USGovDoD, Germany, China. Defaults to Global.
.PARAMETER Tenant
The tenant ID to connect to. If omitted, the default tenant for the authenticated
account is used.
.PARAMETER Out
Output file path for the Excel report. Defaults to the user's Downloads folder
with a timestamped filename. Automatically appends .xlsx if not present.
.EXAMPLE
.\Export-LicenseDetails.ps1
.EXAMPLE
.\Export-LicenseDetails.ps1 -Tenant "contoso.onmicrosoft.com" -Out "C:\Reports\licenses.xlsx"
.EXAMPLE
.\Export-LicenseDetails.ps1 -GraphCloud USGov
#>
#Requires -Modules Microsoft.Graph.Authentication, ImportExcel
[CmdletBinding()]
param(
[ValidateSet('Global', 'USGov', 'USGovDoD', 'Germany', 'China')]
[string]$GraphCloud = 'Global',
[string]$Tenant,
[string]$Out
)
function Log { param([string]$Msg, [ConsoleColor]$Color = 'White') Write-Host "[$(Get-Date -Format 'HH:mm')] $Msg" -ForegroundColor $Color }
$scopes = @(
'User.Read.All',
"Directory.Read.All",
'Organization.Read.All'
)
Log "Connecting to Graph..."
if ($Tenant) { Connect-MgGraph -Scopes $scopes -Environment $GraphCloud -NoWelcome -ErrorAction Stop -TenantId $Tenant }
else { Connect-MgGraph -Scopes $scopes -Environment $GraphCloud -NoWelcome -ErrorAction Stop }
$Org = Get-MgOrganization
Log "Connected to Graph: $($Org.DisplayName)" Green
if (!$Out) {
$friendlyName = "LicenseDetails"
$Out = Join-Path $env:USERPROFILE "Downloads\$($Org.DisplayName)-$friendlyName-$(Get-Date -Format 'yyyy-MM-dd_HH-mm').xlsx"
}
elseif ($Out -notlike '*.xlsx') { $Out += '.xlsx' }
# Download Microsoft's SKU display names
Log "Downloading SKU display names..."
$csv = Invoke-RestMethod "https://download.microsoft.com/download/e/3/e/e3e9faf2-f28b-490a-9ada-c6089a1fc5b0/Product%20names%20and%20service%20plan%20identifiers%20for%20licensing.csv"
$productNames = $csv | ConvertFrom-Csv
# Create lookup table
$skuLookup = @{}
foreach ($product in $productNames | Sort-Object String_Id -Unique) {
if ($product.String_Id) {
$skuLookup[$product.String_Id] = $product.Product_Display_Name
}
}
# Get subscription data
Log "Fetching subscription data..."
$subs = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/beta/directory/subscriptions"
# Get SKU data for assigned licenses
$skus = Get-MgSubscribedSku
$skuAssigned = @{}
foreach ($sku in $skus) {
$skuAssigned[$sku.SkuId] = @{
Consumed = $sku.ConsumedUnits
Total = $sku.PrepaidUnits.Enabled
Available = $sku.PrepaidUnits.Enabled - $sku.ConsumedUnits
}
}
# Create results array
$results = @()
foreach ($sub in $subs.value) {
# Handle null dates
$createdDate = if ($sub.createdDateTime) { [DateTime]$sub.createdDateTime } else { $null }
$renewalDate = if ($sub.nextLifecycleDateTime) { [DateTime]$sub.nextLifecycleDateTime } else { $null }
# Calculate commitment term
$commitmentTerm = "Unknown"
$billingTerm = "Unknown"
if ($createdDate -and $renewalDate) {
$termDays = ($renewalDate - $createdDate).Days
if ($termDays -le 31) {
$commitmentTerm = "Monthly"
$billingTerm = "Monthly"
}
elseif ($termDays -le 366) {
$commitmentTerm = "1-Year"
$billingTerm = "Annual"
}
elseif ($termDays -le 732) {
$commitmentTerm = "2-Year"
$billingTerm = "Annual"
}
else {
$commitmentTerm = "3-Year"
$billingTerm = "Annual"
}
}
elseif ($sub.isTrial) {
$commitmentTerm = "Trial"
$billingTerm = "Trial"
}
# Get display name
$displayName = if ($skuLookup[$sub.skuPartNumber]) {
$skuLookup[$sub.skuPartNumber]
}
else {
$sub.skuPartNumber
}
# Get assigned license info
$assignedInfo = $skuAssigned[$sub.skuId]
$results += [PSCustomObject]@{
'Subscription Name' = $displayName
'Status' = $sub.status
'Total Licenses' = $sub.totalLicenses
'Assigned Licenses' = if ($assignedInfo) { $assignedInfo.Consumed } else { 0 }
'Available Licenses' = if ($assignedInfo) { $assignedInfo.Available } else { $sub.totalLicenses }
'Created Date' = if ($createdDate) { $createdDate.ToString("yyyy-MM-dd") } else { "N/A" }
'Renewal Date' = if ($renewalDate) { $renewalDate.ToString("yyyy-MM-dd") } else { "N/A" }
'Commitment' = $commitmentTerm
'Billing' = $billingTerm
'Is Trial' = $sub.isTrial
'Subscription ID' = $sub.commerceSubscriptionId
'SKU Part Number' = $sub.skuPartNumber
'SKU ID' = $sub.skuId
}
}
# Export to Excel
$results | Export-Excel -Path $Out `
-AutoSize `
-AutoFilter `
-TableName "Subscriptions" `
-TableStyle Medium2 `
-FreezeTopRow `
-WorksheetName "Subscriptions"
Log "Exported report: $Out" Green
$answer = Read-Host "Open the report now? [Y/n]"
if ($answer -eq '' -or $answer -match '^y') {
Start-Process $Out
}