Do you know how many users have a license in your Azure environment? Do you know if they have this license assigned directly or through a group(s) membership? How about duplicate assignments?
One way to answer this is by going to Azure Portal and looking at a user’s license details

Scaling this approach to thousands of users, however, is not ideal. Which is why PowerShell exists to help you administer at scale.
Using PowerShell, we have MSOnline online module (which is decommissioned) and cannot be used with Service Principals for unattended scripts.
A better way is to use Microsoft Graph. See below example,
Import-Module Microsoft.Graph.Users # Faster than loading all modules.
Connect-MgGraph # Prompts interactive authentication
Get-MgUser -All -Property LicenseAssignmentStates # Gets all users (https://graph.microsoft.com/v1.0/users?$select=licenseAssignmentStates)
The output includes a property, LicenseAssignmentStates, which has several entries for each license assignment for a user,
Property | Description |
---|---|
assignedByGroup | The id of the group that assigns this license. If the assignment is a direct-assigned license, this field will be Null. |
disabledPlans | The service plans that are disabled in this assignment. Read-Only. |
error | License assignment failure error. If the license is assigned successfully, this field will be Null. |
lastUpdatedDateTime | The timestamp when the state of the license assignment was last updated. |
skuId | The unique identifier for the SKU. |
state | Indicate the current state of this assignment. Read-Only. The possible values are Active , ActiveWithError , Disabled , and Error . |

Translating the GUIDs
Starting with the groups, we first want to get the list of groups that are assigning licenses to get their display names.
$users.LicenseAssignmentStates.assignedByGroup | select -unique # Get all the users in Azure AD and their license state
$groupGUIDs = $users.LicenseAssignmentStates.assignedByGroup | select -unique #Get the unqiue GUIDs of all the groups assigning license
$groups = ($groupGUIDs | foreach {Get-MgGroup -GroupId $_}).DisplayName # Get the display name of the each of the groups
For the license, we can get the names by going to Product names and service plan identifiers for licensing – Azure AD | Microsoft Docs which offers a helpful CSV that we can utilize.
$licenseTableURL = 'https://download.microsoft.com/download/e/3/e/e3e9faf2-f28b-490a-9ada-c6089a1fc5b0/Product%20names%20and%20service%20plan%20identifiers%20for%20licensing.csv'
# We download the file as a string, convert it to CSV, and select the needed properties
$licenseTable = (Invoke-WebRequest -Uri $LicenseTableGitGubURL).ToString() | ConvertFrom-Csv | Select-Object -Property GUID, Product_Display_Name
# Create a hash table of the license names, this is faster to search for the next step
$licenseTableHash = @{}
$licenseTable | foreach { $licenseTableHash[$_.GUID] = $_.Product_Display_Name }
You can also get the names of the licenses from Graph API by looking at Subscribed SKUs. This is useful as some display names are missing from the CSV and the Display Names are not included in Graph API (upvote here to get it: Include License Display Name in Subscribed SKU – Microsoft Tech Community)
$skusHash = @{} # An empty hashtable
Get-MgSubscribedSku | ForEach-Object { # Gets information about all the licenses purchased in the tenant
# If we have name in the CSV use it, otherwise, use the Part Number
$DisplayName = if ($licenseTableHash[$_.SkuId]) { $licenseTableHash[$_.SkuId] } else { $_.SkuPartNumber }
# Add the display name as a property to the SKU
$_ | Add-Member -MemberType NoteProperty -Name DisplayName -Value $DisplayName
# Update the hash table, hashtable is faster to search by SKU ID than an array.
$skusHash[$_.SkuId] = $_
}
Now, to put it all together, we list each license entry with information about the user and each assignment.
$report = foreach ($user in $users) {
if ($user.LicenseAssignmentStates) {
#user has license assigned
$userHasLicense = $true
foreach ($assignment in $user.LicenseAssignmentStates) {
$assignedLicenseName = $skusHash[$assignment.SkuId].DisplayName
if (-Not $assignedLicenseName) { continue } # This is a zombie license that is not showing in Azure AD purchased SKUs.
if ($assignment.AssignedByGroup) {
# License is assigned through a group
$assignmentType = "Group"
$assignmentGroup = $assignmentGroups[$assignment.AssignedByGroup] # This is why we use hash tables, faster than Where-Object
}
else {
# Direct License Assignment
$assignmentType = "Direct"
$assignmentGroup = $null
}
[PSCustomObject]@{
UserPrincipalName = $user.UserPrincipalName
Name = $user.DisplayName
IsLicensed = $userHasLicense
AssignedLicenseId = $assignment.SkuId
AssignedLicense = $assignedLicenseName
AssignmentType = $assignmentType
AssignmentGroup = $assignmentGroup
}
}
}
else {
# user does not have any assigned license
$userHasLicense = $false
[PSCustomObject]@{
UserPrincipalName = $user.UserPrincipalName
Name = $user.DisplayName
IsLicensed = $userHasLicense
UserType = $user.UserType
AssignedLicenseId = $null
AssignedLicense = $null
AssignmentType = $null
AssignmentGroup = $null
}
}
}
Below is the output as a CSV opened in Excel.

You can even take this step further and build an Azure function that collects this data frequently and visualizes it with a Power BI dashboard

If you want to see how this was built, leave a comment below!
Cheers!
Amazing post! Is it possible to see how you have build it in power BI?