Entra / Microsoft 365 · Licensing
Report service plans
Script to report the service plans found in subscriptions for a Microsoft 365 tenant.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-MgGraph -Scopes Directory.Read.All -NoWelcome# Get the basic information about tenant subscriptions
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
[string]$RunDate = Get-Date -format "dd-MMM-yyyy HH:mm:ss"$Version = "1.0"$CSVOutputFile = "c:\temp\Microsoft365LicenseServicePlans.CSV"$ReportFile = "c:\temp\Microsoft365LicenseServicePlans.html"# Connect to the Graph and get information about the subscriptions in the tenantConnect-MgGraph -Scopes Directory.Read.All -NoWelcome# Get the basic information about tenant subscriptions[array]$Skus = Get-MgSubscribedSku# The $ProductInfoDataFile variable points to the CSV file downloaded from Microsoft from# https://docs.microsoft.com/en-us/azure/active-directory/enterprise-users/licensing-service-plan-reference# It's used to resolve SKU and service plan code names to human-friendly valuesWrite-Output "Loading product data..."$ProductInfoDataFile = "C:\Temp\Product names and service plan identifiers for licensing.csv"If (!(Test-Path -Path $ProductInfoDataFile)) {Write-Host "No product information data file available - product and service plan names will not be resolved"$ProductData = $false} Else {$ProductData = $true}If ($ProductData) {# If the product data file is available, use it to populate some hash tables to use to resolve SKU and service plan names[array]$ProductData = Import-CSV $ProductInfoDataFile[array]$ProductInfo = $ProductData | Sort-Object GUID -Unique# Create Hash table of the SKUs used in the tenant with the product display names from the Microsoft data file$TenantSkuHash = @{}ForEach ($P in $SKUs) {$ProductDisplayName = $ProductInfo | Where-Object {$_.GUID -eq $P.SkuId} | `Select-Object -ExpandProperty Product_Display_NameIf ($Null -eq $ProductDisplayName) {$ProductDisplayname = $P.SkuPartNumber}$TenantSkuHash.Add([string]$P.SkuId, [string]$ProductDisplayName)}# Extract service plan information and build a hash table[array]$ServicePlanData = $ProductData | Select-Object Service_Plan_Id, Service_Plan_Name, Service_Plans_Included_Friendly_Names | `Sort-Object Service_Plan_Id -Unique$ServicePlanHash = @{}ForEach ($SP in $ServicePlanData) {$ServicePlanHash.Add([string]$SP.Service_Plan_Id,[string]$SP.Service_Plans_Included_Friendly_Names)}}# Generate a report about the subscriptions used in the tenantWrite-Host "Generating product subscription information..."$SkuReport = [System.Collections.Generic.List[Object]]::new()ForEach ($Sku in $Skus) {$AvailableUnits = ($Sku.PrepaidUnits.Enabled - $Sku.ConsumedUnits)If ($ProductData) {$SkuDisplayName = $TenantSkuHash[$Sku.SkuId]} Else {$SkuDisplayName = $Sku.SkuPartNumber}$DataLine = [PSCustomObject][Ordered]@{'Sku Part Number' = $SkuDisplayNameSkuId = $Sku.SkuId'Active Units' = $Sku.PrepaidUnits.Enabled'Warning Units' = $Sku.PrepaidUnits.Warning'Consumed Units' = $Sku.ConsumedUnits'Available Units' = $AvailableUnits}$SkuReport.Add($Dataline)}# Get the renewal data$Uri = "https://graph.microsoft.com/beta/directory/subscriptions"[array]$SkuData = Invoke-MgGraphRequest -Uri $Uri -Method Get# Put the renewal information into a hash table$SkuHash = @{}ForEach ($Sku in $SkuData.Value) { $SkuHash.Add($Sku.SkuId,$Sku.nextLifecycleDateTime) }# Update the report with the renewal informationForEach ($R in $SkuReport) {$DaysToRenew = $Null$SkuRenewalDate = $SkuHash[$R.SkuId]If ($SkuRenewalDate) {$SkuRenewalDate = (Get-Date $SkuRenewalDate -format "dd-MMM-yyy")}$R | Add-Member -NotePropertyName "Renewal date" -NotePropertyValue $SkuRenewalDate -ForceIf ($SkuRenewalDate) {$DaysToRenew = (New-TimeSpan $SkuRenewalDate).Days$R | Add-Member -NotePropertyName "Days to renewal" -NotePropertyValue $DaysToRenew -Force}}# Now process the service plans in the subscriptionsWrite-Host "Extracting service plan information..."$ServicePlanReport = [System.Collections.Generic.List[Object]]::new()ForEach ($Sku in $Skus) {[array]$ServicePlans = $Sku.ServicePlansForEach ($SP in $ServicePlans) {If ($ProductData) {$SkuDisplayName = $TenantSkuHash[$Sku.SkuId]$ServicePlanDisplayName = $ServicePlanHash[$SP.servicePlanId]} Else {$SkuDisplayName = $Sku.SkuPartNumber$ServicePlanDisplayName = $SP.servicePlanName}$SPDataLine = [PSCustomObject][Ordered]@{SkuId = $Sku.SkuIdSku = $Sku.SkuPartNumber'SKU Name' = $SkuDisplayName'Service Plan' = $SP.servicePlanId'Service Plan Name' = $ServicePlanDisplayName}$ServicePlanReport.Add($SPDataLine)}}Write-Host "Generating report..."$OrgName = (Get-MgOrganization).DisplayName# Create the HTML report. First, define the header.$HTMLHead="<html><style>BODY{font-family: Arial; font-size: 8pt;}H1{font-size: 22px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;}H2{font-size: 18px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;}H3{font-size: 16px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;}TABLE{border: 1px solid black; border-collapse: collapse; font-size: 8pt;}TH{border: 1px solid #969595; background: #dddddd; padding: 5px; color: #000000;}TD{border: 1px solid #969595; padding: 5px; }td.pass{background: #B7EB83;}td.warn{background: #E3242B;}td.fail{background: #FF2626; color: #ffffff;}td.info{background: #85D4FF;}</style><body><div align=center><p><h1>Microsoft 365 Subscriptions and Service Plan Report</h1></p><p><h2><b>For the " + $Orgname + " tenant</b></h2></p><p><h3>Generated: " + $RunDate + "</h3></p></div>"# This section highlights subscriptions that have less than 3 remaining licenses.# Idea from https://stackoverflow.com/questions/37662940/convertto-html-highlight-the-cells-with-special-values# First, convert the output SKU Report to HTML and then import it into an XML structure$HTMLTable = $SkuReport | ConvertTo-Html -Fragment[xml]$XML = $HTMLTable# Create an attribute class to use, name it, and append to the XML table attributes$TableClass = $XML.CreateAttribute("class")$TableClass.Value = "AvailableUnits"$XML.table.Attributes.Append($TableClass) | Out-Null# Conditional formatting for the table rows. The number of available units is in table row 6, so we update td[5]ForEach ($TableRow in $XML.table.SelectNodes("tr")) {# each TR becomes a member of class "tablerow"$TableRow.SetAttribute("class","tablerow")## If row has TD and TD[5] is 3 or lessIf (($TableRow.td) -and ([int]$TableRow.td[5] -le 3)) {## tag the TD with eirher the color for "warn" or "pass" defined in the heading$TableRow.SelectNodes("td")[5].SetAttribute("class","warn")} ElseIf (($TableRow.td) -and ([int]$TableRow.td[5] -gt 3)) {$TableRow.SelectNodes("td")[5].SetAttribute("class","pass")}}# Wrap the output table with a div tag$HTMLBody = [string]::Format('<div class="tablediv">{0}</div>',$XML.OuterXml)[string]$HTMLSkuSeparator = "<p><h2>Service Plans in Microsoft 365 Subscriptions</h2></p>"[string]$HTMLSkuOutput = $null# For each SKU, extract its service plans from the list created earlier and convert it into a HTML segmentForEach ($Sku in $Skus) {If ($ProductData) {$SkuDisplayName = $TenantSkuHash[$Sku.SkuId]} Else {$SkuDisplayName = $Sku.SkuPartNumber}$SkuServicePlans = $ServicePlanReport | Where-Object {$_.SkuId -eq $Sku.SkuId} | Sort-Object 'Service Plan Name'$HTMLSkuHeader = "<h3>Service Plans for product <b>" + $SkuDisplayName + "<b></h3><p>"$HTMLSkuContent = $SkuServicePlans | ConvertTo-HTML -Fragment$HTMLSkuOutput = $HTMLSkuOutput + $HTMLSkuHeader + $HTMLSkuContent + "<p><p>"}# End stuff to output$HTMLTail = "<p>Report created for the " + $OrgName + " tenant on " + $RunDate + "<p>" +"<p>-----------------------------------------------------------------------------------------------------------------------------</p>"+"<p>Number of subscriptions found: " + $SkuReport.Count + "</p>" +"<p>-----------------------------------------------------------------------------------------------------------------------------</p>"+"<p>Microsoft 365 Subscriptions and Service Plan Report<b> " + $Version + "</b>"$HTMLReport = $HTMLHead + $HTMLBody + $HTMLSkuSeparator + $HTMLSkuOutput + $HTMLtail$HTMLReport | Out-File $ReportFile -Encoding UTF8$SkuReport | Export-CSV -NoTypeInformation $CSVOutputFileWrite-Host ""Write-Host "All done. Output files are" $CSVOutputFile "and" $ReportFile
Attribution
Author
Office365itpros