Summary

This article explains how to retrieve all active Microsoft 365 subscriptions in your tenant using Microsoft Graph PowerShell and map the technical SKU identifiers to friendly product names. The result is a clear CSV export that includes subscription status, license counts, and lifecycle details.


The quick and dirty version of this script, that does not map the Product Names, looks like this:

Connect-MgGraph -Scopes "Directory.Read.All"
Get-MgDirectorySubscription | Select-Object SkuPartNumber, NextLifecycleDateTime


Purpose

The script connects to Microsoft Graph with Directory.Read.All permissions, retrieves subscription data, and matches the SkuPartNumber to a friendly name based on the official Microsoft SKU reference file. The output helps administrators quickly understand which products are active, when they were created, and how many licenses are available.

Prerequisites

  • PowerShell 5.1 or later (or PowerShell 7)
  • Microsoft Graph PowerShell SDK installed
    Run: Install-Module Microsoft.Graph -Scope CurrentUser
  • Sign-in credentials with permission to read directory data

How to Run

  1. Open PowerShell as Administrator.
  2. Copy and paste the full script below into a new file named Get-TenantSubscriptionsFriendlyNames.ps1.
  3. Run the script: .\Get-TenantSubscriptionsFriendlyNames.ps1
  4. When prompted, sign in to Microsoft Graph.
  5. After completion, check the output CSV file in the same folder — it will be named like:
    2025-10-28_14-35-02_SubscriptionReport_FriendlyNames.csv

Output

The exported file includes the following columns:

  • SkuPartNumber
  • FriendlyProductName
  • SkuId
  • CreatedDateTime
  • NextLifecycleDateTime
  • Status
  • TotalLicenses

The file uses a semicolon (;) as delimiter for easier import into Excel.

PowerShell Script


<#
.SYNOPSIS
    Retrieves Microsoft Graph subscription data for the tenant and translates SKU identifiers to friendly product names.

.DESCRIPTION
    This script connects to Microsoft Graph using the required scope (Directory.Read.All),
    retrieves subscription/license data from the tenant (including SkuPartNumber, SkuId, CreatedDateTime,
    NextLifecycleDateTime, Status, TotalLicenses),
    downloads a SKU-to-friendly-name mapping file, builds a lookup table,
    maps each subscription record to a friendly product name (if available),
    and produces console output and an exported CSV report with a timestamped filename.

.PARAMETER mappingUrl
    The URL of the CSV file containing the mapping of String_ID / SkuPartNumber to friendly Product_Display_Name.
    Default value: https://raw.githubusercontent.com/InfoSecGeoff/365SKUTranslator/main/365Products.csv

.PARAMETER exportDirectory
    The directory where the resulting CSV report will be saved.
    Default value: the current directory.

.INPUTS
    None. The script does not accept piped input.

.OUTPUTS
    System.Management.Automation.PSCustomObject[] — An array of custom objects, each containing:
      SkuPartNumber, FriendlyProductName, SkuId, CreatedDateTime,
      NextLifecycleDateTime, Status, TotalLicenses
    Also produces a CSV file at the location specified.

.EXAMPLE
    PS C:\> .\Get-TenantSubscriptionsFriendlyNames.ps1
    Runs the script with default mapping URL and default export path. Produces console output and a CSV file
    named “2025-10-28_14-35-02_SubscriptionReport_FriendlyNames.csv” (with current timestamp) in the current directory.

.EXAMPLE
    PS C:\> .\.\Get-TenantSubscriptionsFriendlyNames.ps1 -mappingUrl "https://example.com/myMapping.csv" -exportDirectory "C:\Reports"
    Runs the script using a custom mapping URL and exports the report to the given directory.

.NOTES
    Author: YourNameHere
    Date: 2025-10-28
    Version: 1.0
    Requirements: Microsoft Graph PowerShell module installed, account with Directory.Read.All permission,
                  PowerShell version 3.0 or higher. If Invoke-WebRequest is unavailable, the script will
                  fallback to using System.Net.WebClient.DownloadString for downloading the mapping file.
#>

Param (
    [string]$mappingUrl      = "https://raw.githubusercontent.com/InfoSecGeoff/365SKUTranslator/main/365Products.csv",
    [string]$exportDirectory = "."
)

# Import Graph module if needed
# clImport-Module Microsoft.Graph -ErrorAction SilentlyContinue

Write-Host "=> Connecting to Microsoft Graph…" -ForegroundColor Cyan
Connect-MgGraph -Scopes "Directory.Read.All"
Write-Host "✔ Connected." -ForegroundColor Green

Write-Host "=> Retrieving subscription data…" -ForegroundColor Cyan
$skuData = Get-MgDirectorySubscription -Property SkuPartNumber, SkuId, CreatedDateTime, NextLifecycleDateTime, Status, TotalLicenses |
           Select-Object SkuPartNumber, SkuId, CreatedDateTime, NextLifecycleDateTime, Status, TotalLicenses
Write-Host ("✔ Retrieved {0} subscription records." -f $skuData.Count) -ForegroundColor Green

Write-Host "=> Downloading SKU mapping file…" -ForegroundColor Cyan
$csvContent = $null

if (Get-Command -Name Invoke-WebRequest -ErrorAction SilentlyContinue) {
    try {
        $response   = Invoke-WebRequest -Uri $mappingUrl -UseBasicParsing -ErrorAction Stop
        $csvContent = $response.Content
        Write-Host "✔ Mapping file downloaded (Invoke-WebRequest)." -ForegroundColor Green
    }
    catch {
        Write-Host "WARNING: Invoke-WebRequest failed: $_. Trying fallback download…" -ForegroundColor Yellow
    }
}

if (-not $csvContent) {
    try {
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        $webClient   = New-Object System.Net.WebClient
        $csvContent  = $webClient.DownloadString($mappingUrl)
        Write-Host "✔ Mapping file downloaded (WebClient fallback)." -ForegroundColor Green
    }
    catch {
        Write-Host "ERROR: Could not download mapping file via fallback: $_" -ForegroundColor Red
        exit 1
    }
}

try {
    $mapping = $csvContent | ConvertFrom-Csv
    Write-Host ("✔ Mapping rows: {0}" -f $mapping.Count) -ForegroundColor Green
}
catch {
    Write-Host "ERROR: Could not parse CSV content: $_" -ForegroundColor Red
    exit 1
}

Write-Host "=> Building lookup table…" -ForegroundColor Cyan
$mapByStringId = @{}
foreach ($m in $mapping) {
    if ($m.String_ID) {
        $mapByStringId[$m.String_ID] = $m.Product_Display_Name
    }
}
Write-Host ("✔ Lookup table contains {0} keys." -f $mapByStringId.Keys.Count) -ForegroundColor Green

Write-Host "=> Processing and mapping subscription data…" -ForegroundColor Cyan
$counter = 0
$total   = $skuData.Count
$report  = $skuData | ForEach-Object {
    $counter++
    Write-Progress -Activity "Mapping SKUs" -Status ("Processing {0}/{1}" -f $counter, $total) -PercentComplete (($counter/$total)*100)

    $skuPart  = $_.SkuPartNumber
    $skuId    = $_.SkuId
    $friendly = if ($mapByStringId.ContainsKey($skuPart)) { $mapByStringId[$skuPart] } else { "Unknown product for $skuPart" }

    [PSCustomObject]@{
        SkuPartNumber         = $skuPart
        FriendlyProductName   = $friendly
        SkuId                 = $skuId
        CreatedDateTime       = $_.CreatedDateTime
        NextLifecycleDateTime = $_.NextLifecycleDateTime
        Status                = $_.Status
        TotalLicenses         = $_.TotalLicenses
    }
}
Write-Progress -Activity "Mapping SKUs" -Completed
Write-Host "✔ Processing complete." -ForegroundColor Green

Write-Host "=> Displaying report:" -ForegroundColor Cyan
$report | Format-Table -AutoSize

# Generate timestamp and export filename
$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
$exportFilename = "{0}_SubscriptionReport_FriendlyNames.csv" -f $timestamp
$exportPath     = Join-Path -Path $exportDirectory -ChildPath $exportFilename

Write-Host ("=> Exporting report to '{0}'…" -f $exportPath) -ForegroundColor Cyan
$report | Export-Csv -Path $exportPath -NoTypeInformation -Delimiter ";"
Write-Host "✔ Export done." -ForegroundColor Green

Write-Host "=== Script finished successfully ===" -ForegroundColor Magenta

Troubleshooting

  • If Invoke-WebRequest fails, the script automatically uses a .NET WebClient fallback.
  • If no data is retrieved, confirm your Graph permissions include Directory.Read.All.
  • If you encounter “Unknown product” entries, the SKU may not yet exist in the Microsoft mapping file.