Azure License Management with Microsoft Graph

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

License view in Azure Portal

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,

PropertyDescription
assignedByGroupThe id of the group that assigns this license. If the assignment is a direct-assigned license, this field will be Null.
disabledPlansThe service plans that are disabled in this assignment. Read-Only.
errorLicense assignment failure error. If the license is assigned successfully, this field will be Null.
lastUpdatedDateTimeThe timestamp when the state of the license assignment was last updated.
skuIdThe unique identifier for the SKU.
stateIndicate the current state of this assignment. Read-Only. The possible values are ActiveActiveWithErrorDisabled, and Error.
License Assignment State property
Licenses assigned to a user

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.

Output as an excel sheet

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

Power BI dashboard

If you want to see how this was built, leave a comment below!

Cheers!

Author