Public/Get-TntLicenseReport.ps1
|
function Get-TntLicenseReport { <# .SYNOPSIS Retrieves Microsoft 365 license allocation and usage information. .DESCRIPTION This function connects to Microsoft Graph and retrieves detailed subscription and license information for the Microsoft 365 tenant. It provides insights into license allocation, consumption, available capacity, and utilization rates across all subscribed SKUs. .PARAMETER TenantId The Azure AD Tenant ID (GUID) to connect to. .PARAMETER ClientId The Application (Client) ID of the app registration created for security reporting. .PARAMETER ClientSecret The client secret for the app registration. Use this for automated scenarios. .PARAMETER CertificateThumbprint The thumbprint of the certificate to use for authentication instead of client secret. .EXAMPLE Get-TntLicenseReport -TenantId $tenantId -ClientId $clientId -ClientSecret $secret Retrieves and displays license allocation information. .OUTPUTS System.Management.Automation.PSCustomObject Returns a structured object containing: - Summary: High-level statistics on subscriptions and licenses - Licenses: Detailed list of licenses with usage and friendly names .NOTES Author: Tom de Leeuw Website: https://systom.dev Module: TenantReports Required Permissions: - Organization.Read.All (Application) - Directory.Read.All (Application) .LINK https://systom.dev #> [CmdletBinding(DefaultParameterSetName = 'ClientSecret')] [OutputType([System.Management.Automation.PSCustomObject])] param( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ClientSecret')] [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Certificate')] [Parameter(Mandatory = $false, ParameterSetName = 'Interactive')] [ValidateNotNullOrEmpty()] [Alias('Tenant')] [string]$TenantId, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ClientSecret')] [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Certificate')] [Parameter(Mandatory = $false, ParameterSetName = 'Interactive')] [ValidatePattern('^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$')] [Alias('ApplicationId')] [string]$ClientId, [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret', ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [Alias('Secret', 'ApplicationSecret')] [SecureString]$ClientSecret, [Parameter(Mandatory = $true, ParameterSetName = 'Certificate', ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [Alias('Thumbprint')] [string]$CertificateThumbprint, [Parameter(Mandatory = $true, ParameterSetName = 'Interactive')] [switch]$Interactive ) begin { # Load SKU Translation table for retrieving friendly license names $SkuHashTable = @{} $SkuTable = Get-SkuTranslationTable if ($SkuTable) { foreach ($SkuGroup in ($SkuTable | Group-Object GUID)) { $SkuHashTable[$SkuGroup.Name] = ($SkuGroup.Group | Select-Object -First 1).Product_Display_Name } } else { Write-Verbose "SKU Translation Table not available." } Write-Information 'STARTED : License report retrieval...' -InformationAction Continue } process { try { $ConnectionParams = Get-ConnectionParameters -BoundParameters $PSBoundParameters $ConnectionInfo = Connect-TntGraphSession @ConnectionParams # Retrieve subscription information Write-Verbose 'Retrieving subscription and license information...' $SubscribedSkus = Get-MgSubscribedSku -All -ErrorAction Stop $LicenseData = $SubscribedSkus.ForEach({ # Translate SKU ID to friendly name $ResolvedName = Resolve-SkuName -SkuId $_.SkuId -SkuHashTable $SkuHashTable $FriendlyName = if ($ResolvedName -eq $_.SkuId) { Write-Verbose "SKU $($_.SkuId) not found in translation table. Using SkuPartNumber." $_.SkuPartNumber } else { $ResolvedName } # Build license information object [PSCustomObject]@{ SkuId = $_.SkuId SkuPartNumber = $_.SkuPartNumber FriendlyName = $FriendlyName CapabilityStatus = $_.CapabilityStatus ConsumedUnits = $_.ConsumedUnits PrepaidUnits = $_.PrepaidUnits.Enabled WarningUnits = $_.PrepaidUnits.Warning SuspendedUnits = $_.PrepaidUnits.Suspended AvailableUnits = if ($_.PrepaidUnits.Enabled) { $_.PrepaidUnits.Enabled - $_.ConsumedUnits } else { 0 } Utilization = if ($_.PrepaidUnits.Enabled -gt 0) { [math]::Round(($_.ConsumedUnits / $_.PrepaidUnits.Enabled) * 100, 2) } else { 0 } ServicePlansCount = $_.ServicePlans.Count } }) | Sort-Object FriendlyName # Calculate summary statistics $TotalPrepaid = ($LicenseData | Measure-Object -Property PrepaidUnits -Sum).Sum $TotalConsumed = ($LicenseData | Measure-Object -Property ConsumedUnits -Sum).Sum $TotalAvailable = ($LicenseData | Measure-Object -Property AvailableUnits -Sum).Sum $Summary = [PSCustomObject]@{ TotalSubscriptions = if ($LicenseData) { $LicenseData.Count } else { 0 } ActiveSubscriptions = if ($LicenseData) { $LicenseData.Where({ $_.CapabilityStatus -eq 'Enabled' }).Count } else { 0 } TotalLicensesPurchased = $TotalPrepaid TotalLicensesAssigned = $TotalConsumed TotalLicensesAvailable = $TotalAvailable } Write-Information "FINISHED : License report - $($Summary.TotalSubscriptions) licenses found" -InformationAction Continue [PSCustomObject] @{ Summary = $Summary Licenses = $LicenseData } } catch { $errorRecord = [System.Management.Automation.ErrorRecord]::new( [System.Exception]::new("Get-TntLicenseReport failed: $($_.Exception.Message)", $_.Exception), 'GetTntLicenseReportError', [System.Management.Automation.ErrorCategory]::OperationStopped, $TenantId ) $PSCmdlet.ThrowTerminatingError($errorRecord) } finally { if ($ConnectionInfo.ShouldDisconnect) { Disconnect-TntGraphSession -ConnectionState $ConnectionInfo } } } } |