Finding Who’s Assigned a License for Individual Office 365 Applications

The Science of Licensing Office 365 Users

The basics of Office 365 licensing are well known. Users access services through licenses in composite plans like Office 365 E3 or E5 or individual offerings like Azure AD Premium P1. Users must have the relevant licenses to access a service like Exchange Online or Teams. Information about the licenses assigned to users are stored in their Azure AD accounts. This context helps us understand how to begin answering questions about licensing that isn’t available in the Microsoft 365 admin center (Figure 1).

Licensing information for a tenant as listed in the Microsoft 365 admin center
Figure 1: Licensing information for a tenant as listed in the Microsoft 365 admin center

The admin center tells you what licenses you have, the licenses assigned and available, and the accounts with assigned licenses. You can export lists of users with a selected license to a CSV file for reporting purposes or to import into Power BI for analysis. But one thing you can’t do is to find out what users have licenses for applications assigned through a composite license.

Individual Application Licenses

Take the example of Teams, Exchange Online, SharePoint Online. These are core services bundled into the Office 365 E3 and E5 plans. You could assume that everyone with an E3 or E5 license can use these applications, but that’s not true because administrators can remove the service plans for applications from individual user accounts (a service plan is effectively a license for a specific application bundled into a plan; you can’t buy a service plan). Take the example shown in Figure 2. The user has an Office 365 E3 license but the service plans for Bookings, Forms, and Kaizala have been removed.

Viewing licenses for individual service plans removed from a user account
Figure 1: Viewing licenses for individual service plans removed from a user account

It’s relatively common to find that organizations remove individual service plans from users until they are ready to deploy an application. For instance, you might want to use Exchange, SharePoint, and OneDrive for Business immediately but want to block user access to Teams, Forms, Stream, and other applications bundled in Office 365 E3 or E5 until local support is ready and user training is available.

Accessing License Information with PowerShell

While the admin center doesn’t support reporting of service plans for individual applications, it’s possible to do this with some straightforward PowerShell. The key is to discover how to retrieve the licensing information from Azure AD accounts.

Licensing information is in the AssignedPlans property of an Azure AD account. If we examine the property, you’ll see a bunch of assignments and deletions as licenses are added and removed from the account.

(Get-AzureADUser -ObjectId

AssignedTimestamp   CapabilityStatus Service            ServicePlanId
-----------------   ---------------- -------            -------------
28/01/2021 22:11:05 Deleted          OfficeForms        2789c901-c14e-48ab-a76a-be334d9d793a
28/01/2021 22:11:05 Deleted          MicrosoftKaizala   aebd3021-9f8f-4bf8-bbe3-0ed2f4f047a1
28/01/2021 22:11:05 Enabled          CRM                95b76021-6a53-4741-ab8b-1d1f3d66a95a

The ServicePlanId is the important piece of information because it stores the unique identifier (a GUID) for the plan. Microsoft publishes an online list of application service plan identifiers for reference. The point to remember is that the same service plan identifier is always used. For instance, 2789c901-c14e-48ab-a76a-be334d9d793a is always Forms Plan E3 (the license for the Forms application included in Office 365 E3).

To confirm this, let’s use the Get-AzureADSubscribedSku cmdlet to retrieve the set of licenses known in a tenant.

$Licenses = (Get-AzureADSubscribedSku)
$Licenses | Select -Property SkuPartNumber, ConsumedUnits -ExpandProperty PrepaidUnits | Format-Table

SkuPartNumber                ConsumedUnits Enabled Suspended Warning
-------------                ------------- ------- --------- -------
STREAM                                   4   10000         0       0
EMSPREMIUM                               5       5         0       0
ENTERPRISEPACK                          22      25         0       0
FLOW_FREE                                3   10000         0       0
POWER_BI_STANDARD                        5 1000000         0       0
ENTERPRISEPREMIUM_NOPSTNCONF             5       5         0       0
TEAMS_EXPLORATORY                        0     100         0       0
SMB_APPS                                 2       3         0       0
RIGHTSMANAGEMENT_ADHOC                   3   50000         0       0

The online documentation tells us that the name of the Office 365 E3 SKU is ENTERPRISEPACK. It is license number three in our list, so we can look at this object to find out what’s included. As expected, the Service Plan Identifier for FORMS_PLAN_E3 is 2789c901-c14e-48ab-a76a-be334d9d793a.

$Licenses[2].ServicePlans | Format-Table ServicePlanName, ServicePlanId

ServicePlanName              ServicePlanId
---------------              -------------
POWER_VIRTUAL_AGENTS_O365_P2 041fe683-03e4-45b6-b1af-c0cdc516daee
CDS_O365_P2                  95b76021-6a53-4741-ab8b-1d1f3d66a95a
PROJECT_O365_P2              31b4e2fc-4cd6-4e7d-9c1b-41407303bd66
DYN365_CDS_O365_P2           4ff01e01-1ba7-4d71-8cf8-ce96c3bbcf14
MICROSOFTBOOKINGS            199a5c09-e0ca-4e37-8f7c-b05d533e1ea2
KAIZALA_O365_P3              aebd3021-9f8f-4bf8-bbe3-0ed2f4f047a1
MICROSOFT_SEARCH             94065c59-bc8e-4e8b-89e5-5138d471eaff
WHITEBOARD_PLAN2             94a54592-cd8b-425e-87c6-97868b000b91
MIP_S_CLP1                   5136a095-5cf0-4aff-bec3-e84448b38ea5
MYANALYTICS_P2               33c4f319-9bdd-48d6-9c4d-410b750a4a5a
BPOS_S_TODO_2                c87f142c-d1e9-4363-8630-aaea9c4d9ae5
FORMS_PLAN_E3                2789c901-c14e-48ab-a76a-be334d9d793a
STREAM_O365_E3               9e700747-8b1d-45e5-ab8d-ef187ceec156
Deskless                     8c7d2df8-86f0-4902-b2ed-a0458298f3b3
FLOW_O365_P2                 76846ad7-7776-4c40-a281-a386362dd1b9
POWERAPPS_O365_P2            c68f8d98-5534-41c8-bf36-22fa496fa792
TEAMS1                       57ff2da0-773e-42df-b2af-ffb7a2317929
PROJECTWORKMANAGEMENT        b737dad2-2f6c-4c65-90e3-ca563267e8b9
SWAY                         a23b959c-7ce8-4e57-9140-b90eb88a9e97
INTUNE_O365                  882e1d05-acd1-4ccb-8708-6ee03664b117
YAMMER_ENTERPRISE            7547a3fe-08ee-4ccb-b430-5077c5041653
RMS_S_ENTERPRISE             bea4c11e-220a-4e6d-8eb8-8ea15d019f90
OFFICESUBSCRIPTION           43de0ff5-c92c-492b-9116-175376d08c38
MCOSTANDARD                  0feaeb32-d00e-4d66-bd5a-43b5b83db82c
SHAREPOINTWAC                e95bec33-7c88-4a70-8e19-b10bd9d0c014
SHAREPOINTENTERPRISE         5dbe027f-2339-4123-9542-606e4d348a72
EXCHANGE_S_ENTERPRISE        efb87545-963c-4e0d-99df-69c6916d9eb0

Reporting Accounts Licensed for an Application

Now that we know how service plan identifiers work and how to find their values, we can use this knowledge to build a script to interrogate Azure AD user accounts to find license data for an application.

Not everyone likes inputting GUIDs, so we’ll make it easier by allowing an application name to be used for the query. The code creates a hash table of service plan identifiers and names (feel free to add more if you want) and then retrieves details of Azure AD user accounts. We ask the user to enter an application to check and validate the response against the hash table. Finally, we loop through the set of Azure AD accounts to check if the license is in their assigned set and report the details. Here’s the code (you can download it from GitHub):

$Plans = @{}
$Plans.Add(“199a5c09-e0ca-4e37-8f7c-b05d533e1ea2”, “Bookings”)
$Plans.Add(“efb87545-963c-4e0d-99df-69c6916d9eb0”, “Exchange Online”)
$Plans.Add(“5dbe027f-2339-4123-9542-606e4d348a72”, “SharePoint Online”)
$Plans.Add(“7547a3fe-08ee-4ccb-b430-5077c5041653”, “Yammer”)
$Plans.Add(“882e1d05-acd1-4ccb-8708-6ee03664b117”, “Intune”)
$Plans.Add(“57ff2da0-773e-42df-b2af-ffb7a2317929”, “Teams”)
$Plans.Add(“2789c901-c14e-48ab-a76a-be334d9d793a”, “Forms”)
$Plans.Add(“9e700747-8b1d-45e5-ab8d-ef187ceec156”, “Stream”)
$Plans.Add(“b737dad2-2f6c-4c65-90e3-ca563267e8b9”, “Planner”)
Write-Host “Finding Azure AD Account Information”
$Users = Get-AzureADUser -All $True -Filter "Usertype eq 'Member'"
$Product = Read-Host "Enter the Office 365 application for a license check"
if (!($Plans.ContainsValue($Product))) { # Not found
   Write-Host “Can’t find” $Product “in our set of application SKUs”; break }
Foreach ($Key in $Plans.Keys) { # Lookup hash table to find product SKU
   If ($Plans[$Key] -eq $Product) { $PlanId = $Key }
$PlanUsers = [System.Collections.Generic.List[Object]]::new() 
ForEach ($User in $Users) {
  If ($PlanId -in $User.AssignedPlans.ServicePlanId) {
    $Status = ($User.AssignedPlans | ? {$_.ServicePlanId -eq $PlanId} | Select -ExpandProperty CapabilityStatus )
    $ReportLine  = [PSCustomObject] @{
          User       = $User.DisplayName 
          UPN        = $User.UserPrincipalName
          Department = $User.Department
          Country    = $User.Country
          SKU        = $PlanId
          Product    = $Product
          Status    = $Status } 
    $PlanUsers.Add($ReportLine) }
Write-Host "Total Accounts scanned:" $PlanUsers.Count
$DisabledCount = $PlanUsers | ?{$_.Status -eq "Deleted"}
$EnabledCount = $PlanUsers | ? {$_.Status -eq "Enabled"}
Write-Host (“{0} is enabled for {1} accounts and disabled for {2} accounts” -f $Product, $EnabledCount.Count, $DisabledCount.Count)
$PlanUsers | Sort User | Out-GridView

The Graph Alternative

You can also use the Users Graph API to fetch license information for Azure AD accounts by running a call like:$filter=userType eq 'Member'&$select=id, displayName, licenseassignmentstates, assignedplans

The code to check the AssignedPlans data for a product identifier is the same. Although the Graph is usually faster than PowerShell cmdlets, in this instance only one call is needed, and the speed difference is marginal.

As ever, if you plan to use the Graph to fetch data, testing call syntax and returns using the Graph Explorer tool is a good thing to do. Figure 3 shows the result of querying the Graph to return user license data.

The Graph Explorer runs a query to retrieve license information for a user account
Figure 3: The Graph Explorer runs a query to retrieve license information for a user account

Processing Licenses in Different Plans

Because the script looks for a specific service plan identifier, it finds every instance of a licensed application. In other words, if you search for an application like Exchange Online, which included as EXCHANGE_S_ENTERPRISE (efb87545-963c-4e0d-99df-69c6916d9eb0) in both Office 365 E3 and E5), the report will list accounts enabled for Exchange in both plans. If you want to differentiate between the two plans, you need to check the AssignedLicenses property of each account for the identifier of the plan. For instance, looking at Microsoft’s reference list, we find that:

  • 6fd2c87f-b296-42f0-b197-1e91e994b900 is the identifier for Office 365 E3.
  • c7df2760-2c81-4ef7-b578-5b5392b571df is for Office 365 E5.
  • 26d45bd9-adf1-46cd-a9e1-51e9a5524128 is for Office 365 E5 without audio conferencing.

The script available from GitHub includes code to output the names of license SKUs.

Outputting the License Data

The information in the report can be saved to a CSV file or viewed online. Figure 4 shows the result of the script as viewed through the Out-GridView cmdlet. We can see that the user we removed the Forms license in Figure 1 is reported accurately.

Reporting license data
Figure 3: Reporting license data

You might not need to interrogate Azure AD for details of individual licenses very often, but if you do (as when preparing to enable an application for a bunch of users), it’s much faster to get the information with PowerShell than using the admin center GUI.

For more great information about how licensing works, subscribe to the Office 365 for IT Pros eBook.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.