Covering Microsoft 365 License Management Basics
Updated 11-Jul-2023 for Microsoft Graph PowerShell SDK V2
Microsoft’s revised schedule to retire the Azure AD and Microsoft Online Services (MSOL) PowerShell modules gives tenant administrators a little extra time to upgrade scripts before support ceases in early 2023. The new schedule includes some extra breathing space for license management cmdlets as Microsoft now plans to introduce the new license management platform for Microsoft 365 on August 26, 2022, instead of June 30. The extra eight weeks is appreciated, but time is slipping away to inventory and update license management scripts.
Update (July 29): Microsoft has extended the deadline to retire the Azure AD and MSOL licensing cmdlets to March 31, 2024.
Microsoft’s suggests that tenant use cmdlets from the Microsoft Graph PowerShell SDK to replace those from the two deprecated modules. I show how to approach using the SDK for license management in an example which creates a licensing report for a tenant. It’s evident from the questions I received since and what I see in internet searches that the basics of license assignment and removal needs more discussion and examples, which is what I attempt here.
To start off, connect to the Microsoft Graph using the Connect-MgGraph cmdlet. You need to sign into an administrator account with at least the Directory.Read.All permission to have full access to license information.
$RequiredScopes = @(Directory.AccessAsUser.All, Directory.ReadWrite.All) Connect-MgGraph -Scopes $RequiredScopes
Assign a New Microsoft 365 License
The most basic operation is to assign a license to an Azure AD account using the Set-MgUserLicense cmdlet. You need to know:
- The UPN or object identifier of the target Azure AD account.
- The SKU identifier of the product you wish to license for the account. The Get-MgSubscribedSku cmdlet returns the set of products the tenant has subscriptions for (now or at some time in the past). See the article about creating a licensing report for information about how to use this data.
Equipped with this information, we can assign a license. In this case, we’re assigning a Viva Topics license (the SkuID is 4016f256-b063-4864-816e-d818aad600c9).
Set-MgUserLicense -UserId "John.West@Office365itpros.com" -Addlicenses @{SkuId = '4016f256-b063-4864-816e-d818aad600c9’} -RemoveLicenses @()
Note that you must pass a null array in the RemoveLicenses parameter. This applies even if you don’t want the command to remove any licenses.
Table 1 lists some reasons why a license assignment will fail.
Reason | Example error |
No licenses for the specified product are available. | Subscription with SKU 26d45bd9-adf1-46cd-a9e1-51e9a5524128 does not have any available licenses. |
The license is unknown in the tenant (a subscription for the license has never existed). | License e82ae690-a2d5-4d76-8d30-7c6e01e6022e does not correspond to a valid company License. |
An invalid identifier for a service plan in a compound license is specified. | Service plan 2078e8df-cff6-4290-98cb-5408261a760a for license 26d45bd9-adf1-46cd-a9e1-51e9a5524128 is not valid. |
Attempt to remove a license from an account that it doesn’t have. | User does not have a corresponding license. |
If the Set-MgUserLicense cmdlet doesn’t report an error, the license assignment worked (or you attempted to assign a license to an account which it already had). To check, run the Get-MgUser cmdlet to examine the AssignedLicenses property for the account.
Get-MgUser -UserId John.West@Office365itpros.com -Property Id, displayName, assignedLicenses | Select -ExpandProperty AssignedLicenses DisabledPlans SkuId ------------- ----- {} 4016f256-b063-4864-816e-d818aad600c9
Assigning Compound Licenses
Microsoft 365 products can cover a single piece of functionality (like Viva Topics) or they can be a compound product/SKU where a single license covers multiple service plans. Each service plan allows the user to access specific functionality, like Yammer or Planner. Office 365 E3 and E5 are examples of compound SKUs. Microsoft publishes full details of the service plans included in compound SKUs online.
You can assign a license for a compound SKU and remove access to one or more service plans to deny access to that functionality. Educational establishments often do this to limit access to features that they don’t want students to use.
In this example, we assign an Office 365 A1 (student) license to an account with several disabled service plans. The license information is held in a hash table, which is then passed in the AddLicenses parameter for the Set-MgUserLicense cmdlet. Hash tables are commonly used to pass information to SDK cmdlets. The hash table defines:
- The SKU identifier of the compound license (SkuId).
- A list of the identifiers for the service plans to disable (DisabledPlans). This includes Teams (57ff2da0-773e-42df-b2af-ffb7a2317929), Yammer for Education (2078e8df-cff6-4290-98cb-5408261a760a), and Exchange standard (9aaf7827-d63c-4b61-89c3-182f06f82e5c).
$StudentLicenseOptions = @{SkuId = "e82ae690-a2d5-4d76-8d30-7c6e01e6022e"; DisabledPlans = @("57ff2da0-773e-42df-b2af-ffb7a2317929", "2078e8df-cff6-4290-98cb-5408261a760a", "9aaf7827-d63c-4b61-89c3-182f06f82e5c")} Set-MgUserLicense -UserID John.West@Office365itpros.com -AddLicenses @($StudentLicenseOptions) -RemoveLicenses @()
Another way of passing the information is to create an array for the service plans you want to disable. In this example, we assign an Office 365 E3 license and disable the Sway and Yammer service plans:
$DisabledServicePlans = @("a23b959c-7ce8-4e57-9140-b90eb88a9e97", "7547a3fe-08ee-4ccb-b430-5077c5041653”) $Office365E3Sku = “6fd2c87f-b296-42f0-b197-1e91e994b900” Set-MgUserLicense -UserId John.West@Office365itpros.com -AddLicenses @{SkuId = $Office365E3Sku; DisabledPlans = $DisabledServicePlans} -RemoveLicenses @()
Running Get-MgUser to examine the assigned licenses reveals the disabled service plans.
Get-MgUser -UserId John.West@Office365itpros.com -Property Id, displayName, assignedLicenses | Select -ExpandProperty AssignedLicenses DisabledPlans SkuId ------------- ----- {a23b959c-7ce8-4e57-9140-b90eb88a9e97, 7547a3fe-08ee-4ccb-b430-5077c5041653} 6fd2c87f-b296-42f0-b197-1e91e994b900 {} 4016f256-b063-4864-816e-d818aad600c9
Another way to see the licenses assigned to an account is with the Get-MgUserLicenseDetail cmdlet:
Get-MgUserLicenseDetail -UserId John.West@Office365itpros.com | fl Id : PzFitvwUokOaetLif080eFbyFkBjsGRIgW7YGKrWAMk ServicePlans : {GRAPH_CONNECTORS_SEARCH_INDEX_TOPICEXP, CORTEX} SkuId : 4016f256-b063-4864-816e-d818aad600c9 SkuPartNumber : TOPIC_EXPERIENCES AdditionalProperties : {} Id : PzFitvwUokOaetLif080eH_I0m-WsvBCsZcekemUuQA ServicePlans : {VIVA_LEARNING_SEEDED, Nucleus, ContentExplorer_Standard, POWER_VIRTUAL_AGENTS_O365_P2...} SkuId : 6fd2c87f-b296-42f0-b197-1e91e994b900 SkuPartNumber : ENTERPRISEPACK AdditionalProperties : {}
The disabled service plans are not obvious in the listing, but we can see them by running:
Get-MgUserLicenseDetail -UserId John.West@Office365itpros.com | ? {$_.SkuId -eq "6fd2c87f-b296-42f0-b197-1e91e994b900"} | Select-Object -ExpandProperty ServicePlans
Removing Licenses
To remove licenses, include the license identifiers in an array and pass the array in the RemoveLicenses parameter. This example command removes the two licenses we assigned earlier.
[array]$LicensesToRemove = "4016f256-b063-4864-816e-d818aad600c9", “6fd2c87f-b296-42f0-b197-1e91e994b900” Set-MgUserLicense -UserId "John.West@Office365itpros.com” -RemoveLicenses $LicensesToRemove -AddLicenses @()
Set-MgUserLicense cannot remove a license from an account if the assignment is via group membership.
Assigning Multiple Licenses in One Operation
To add multiple Microsoft 365 licenses in a single operation, create a hash table containing details of the licenses to add and pass the hash table in the BodyParameter parameter for Set-UserMgLicense instead of using the AddLicenses parameter.
$LicenseParams = @{ AddLicenses = @( @{ DisabledPlans = @() SkuId = "4016f256-b063-4864-816e-d818aad600c9" } @{ DisabledPlans = @() SkuId = "a403ebcc-fae0-4ca2-8c8c-7a907fd6c235" } ) RemoveLicenses = @( ) } Set-MgUserLicense -UserId John.West@office365itpros.com -BodyParameter $LicenseParams
As before, you can disable service plans when you assign licenses. In this example, we assign licenses for Enterprise Mobility and Security (EMS) E5 and Viva Topics and disable the Intune plan included in EMS E5.
$LicenseParams = @{ AddLicenses = @( @{ DisabledPlans = @("c1ec4a95-1f05-45b3-a911-aa3fa01094f5") SkuId = "b05e124f-c7cc-45a0-a6aa-8cf78c946968" } @{ DisabledPlans = @() SkuId = "4016f256-b063-4864-816e-d818aad600c9" } ) RemoveLicenses = @( ) } Set-MgUserLicense -UserId John.West@office365itpros.com -BodyParameter $LicenseParams
The same technique can remove multiple licenses in one operation. This command removes the licenses for Power BI (standard) and Viva Topics:
$LicenseParams = @{ AddLicenses = @() RemoveLicenses = @( "a403ebcc-fae0-4ca2-8c8c-7a907fd6c235" "4016f256-b063-4864-816e-d818aad600c9" ) } Set-MgUserLicense -UserId John.West@office365itpros.com -BodyParameter $LicenseParams
You can combine license assignments and removals in a single command by populating the appropriate data in the hash table.
Finding Users with Licenses
To find accounts assigned a specific license, use a filter with a lambda operator with Get-MgUser. This command finds users assigned an Office 365 E3 license:
Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq 6fd2c87f-b296-42f0-b197-1e91e994b900)" -All
Another way is to find all tenant accounts and filter against the AssignedLicenses property:
[array]$AllUsers = Get-MgUser -Filter "UserType eq 'Member'" -All -Property Id, assignedLicenses, UserPrincipalName, DisplayName [array]$Office365E3Users = $AllUsers | Where-Object {$_.AssignedLicenses.SkuId -contains "6fd2c87f-b296-42f0-b197-1e91e994b900"} Write-Host (“You have {0} tenant accounts and {1} have Office 365 E3 licenses.” -f $AllUsers.Count, $Office365E3Users.Count)
Being able to search for Microsoft 365 license assignments means that we can create a simple licensing report by looping through the subscribed licenses in the tenant and retrieving the set of accounts which hold each license.
[array]$Skus = Get-MgSubscribedSku | Select SkuId, SkuPartNumber, ConsumedUnits $Report = [System.Collections.Generic.List[Object]]::new() ForEach ($Sku in $Skus) { $Command = 'Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq ' + $Sku.SkuId + ')" -All' [array]$SkuUsers = Invoke-Expression $Command $ReportLine = [PSCustomObject][Ordered]@{ Sku = $Sku.SkuId Product = $Sku.SkuPartNumber 'Consumed Units' = $Sku.ConsumedUnits 'Calculated Units' = $SkuUsers.Count 'Assigned accounts' = $SkuUsers.UserPrincipalName -Join ", " } $Report.Add($ReportLine) } # End ForEach $Report | Sort-Object Product | Out-GridView
The result is shown in Figure 1.
The Same but Different
Managing Microsoft 365 licenses for Azure AD accounts using the Microsoft Graph PowerShell SDK follows the same principles as before. Accounts receive product licenses, some of which allow access to multiple service plans. Individual service plans can be disabled. Cmdlets exist to assign and remove licenses, and you can assign or remove multiple licenses in a single operation. The cmdlets and syntax are different, but it shouldn’t be a huge task to move from older MSOL and Azure AD cmdlets to embrace the new paradigm.
can you give the github link to this script
There is no link. There are only code examples.
Hey i need to assign licenses to multiple users , using csv,can give me the scripts for that
Whatever happened to the “new license management platform” announced by Alex Simonds here:
https://techcommunity.microsoft.com/t5/microsoft-entra-azure-ad-blog/migrate-your-apps-to-access-the-license-managements-apis-from/ba-p/2464366
in which he says: “Group licensing will be extended. In the new licensing platform, Azure AD Premium or Office 365 E3 will no longer be required to use group-based licensing for license assignments”
?
I asked Alex Simons about this recently. The answer from his team is that work is still ongoing to finalize the new product license management platform. When that’s done, they’ll remove the P1 requirement.
I have a script that pulls from a csv file where I create temp student accounts. I have the script create the account, then change the password, then add office license, then add PBI license. During each license phase assignment every piece of meta information about the account that was assigned a license is displayed. So, student 1 -10 shows me each account meta info and it happens twice since it runs through assigning one license then the other. Below is a snippet of the PBI assignment section (other license assignment section is the same). Looks the same as above in article other than the throttle control statement. Why would all that info be show after each assignment?
#Add Office License
$batchCount = 0
Import-Csv -Path .\PbiXX.csv |
ForEach-Object {
if ($batchCount -eq $batchSize){
Start-Sleep -Seconds $wait
$batchCount = 0
}
Set-MgUserLicense -UserId “$($_.FirstName)@pbiclass.com” -Addlicenses @{SkuId = ‘3b555118-da6a-4418-894f-7df1e2096870’} -RemoveLicenses @()
$batchCount++
}
Do you mean that you don’t want to see the Set-MgUserLicense cmdlet output anything? If so, just assign the output to a variable:
$Status = Set-MgUserLicense -UserId “$($_.FirstName)@pbiclass.com” -Addlicenses @{SkuId = ‘3b555118-da6a-4418-894f-7df1e2096870’} -RemoveLicenses @()
BTW, I cover bulk assignment in https://practical365.com/bulk-license-assignment-with-the-microsoft-graph-powershell-sdk/
Hi,
when I use this command:
Set-MgUserLicense -UserId $user.Id -AddLicenses $addLicenses -RemoveLicenses @()
I have this error:
Set-MgUserLicense : License assignment failed because service plans 63038b2c-28d0-45f6-bc36-33062963b498,0a4983bb-d3e5-4a09-95d8-b2d0127b3df5 are mutually exclusive Status: 400 (BadRequest)
What’s in $AddLicenses?
What licenses are you attempting to assign?
Hola Tony, una pregunta, como puedo asignar un único tipo de licencia a un listado CSV, tendrás ese ejemplo ? agradezco el apoyo.
I’ve just written a script to read users in from a CSV to assign licenses. I need to add some error handling and I will post the results in an article soon.
https://practical365.com/bulk-license-assignment-with-the-microsoft-graph-powershell-sdk/ is what you need.
Great article! No questions here… everything worked as described. You’re my new hero!
Hi Tony, I’m trying to remove licenses for bulk deleted users, can you help if you have written something?
I just deleted a user account by running Remove-MgUser. The license was removed from the account. Why do you think you need to remove licenses from bulk-deleted users?
Hi Tony, When I checked the deleted users in admin portal I see licenses are tagged against that deleted user hence came the thought, is this expected behavior if you know?
Licenses are noted for deleted accounts so that Microsoft 365 can reassign the license to an account if it is restored. However, once an account is deleted, any licenses it had prior to deletion are available for reassignment.
Hi Tony,
Can you help me here with error below.
My command.
$CSV = Import-CSV -Path C:\Temp\users5.csv #-Delimiter “;”
ForEach ($Users in $CSV)
{
Set-MgUserLicense -UserId $User.UserId -AddLicenses @{SkuId = ‘c1821944-af26-46d5-ae15-4f65450761cc_66b55226-6b4f-492c-910c-a3b7a3c9d993’} -RemoveLicenses @()
}
Here’s the error
Set-MgUserLicense : Cannot convert the literal ‘c1821944-af26-46d5-ae15-4f65450761cc_66b55226-6b4f-492c-910c-a3b7a3c9d993’ to the expected type ‘Edm.Guid’.
Status: 400 (BadRequest)
ErrorCode: Request_BadRequest
Date: 2023-07-28T19:41:51
Headers:
Transfer-Encoding : chunked
Vary : Accept-Encoding
Strict-Transport-Security : max-age=31536000
request-id : 3073e24e-5de4-4928-b509-116c8d0c96d8
client-request-id : e084dd65-ea84-4cb4-8044-9b666aed98bf
x-ms-ags-diagnostic : {“ServerInfo”:{“DataCenter”:”South Central US”,”Slice”:”E”,”Ring”:”5″,”ScaleUnit”:”004″,”RoleInstance”:”SA2PEPF0000110C”}}
x-ms-resource-unit : 1
Cache-Control : no-cache
Date : Fri, 28 Jul 2023 19:41:50 GMT
At line:7 char:1
The SKU id that you’re trying to use is not a valid GUID (it’s too long). Where did you get it?
Hello,
$st = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationRequirement
$st.RelyingParty = “*”
$st.State = “Enforced”
$sta = @($st)
Update-MgUser -UserId $line.username -StrongAuthenticationRequirements $sta
I used the above command to enable MFA after creating the account. Please, how can I do it using Update-MgUser?
Thank you,
You can’t enforce MFA using the Update-MgUser cmdlet. This is an acknowledged gap with the Graph (add your comments at https://feedbackportal.microsoft.com/feedback/idea/9b1b2216-9ab2-ed11-a81b-002248519701).
Microsoft knows this is an API parity issue and will fix it in time. In the interim, continue using the MSOL cmdlet or (better) enforce MFA using a conditional access policy.
I having issues adding BULK license. How do I do that?
You don’t give enough detail in your question to allow any sort of a reasonable response.
In general, if you want to assign licenses to several accounts, you’d loop through each account and add the license to the account.
Great article! This is extremely helpful, but how you would pull when a license was assigned to an individual? Thanks!
That information isn’t kept with the license data for an account. It is captured in Azure AD “update user” records and you could search for those events in either Azure AD or the Unified audit log and report license assignments that way.
Hello, I am very new to Graph and am having an issue working with Bulk License removals from a CSV user list.
In the CSV (header “Users” and the UPNs under) i have the UPN of the users. This is what i am trying to do.
$Users = Import-csv “C:\Removals\removal_audioconferencing.csv”
$SkuToRemove = “0c266dff-15dd-4b49-8397-2bb16070ed52”
$users |ForEach-Object {Set-MgUserLicense -UserId $Users.id -RemoveLicenses $SkuToRemove -AddLicenses @()}
Do you have the user identifier in the CSV?
Also, $Users.Id refers to the set of values in the Id property of the $Users array.
Maybe try:
ForEach ($User in $Users) {
Set-MgUserLicense -UserId $User.Id -RemoveLicenses $SkuToRemove -AddLicenses @() }
No the only entry in the CSV is the UPN –
Basically i am doing a dump to CSV for the licensed users and want to use that CSV to inject the UserID. Does that need to be the ID now instead of the UPN?
I can use the UPN for a single removal with Set-MgUserLicense -UserId “user@domain.com” -RemoveLicenses @($MCOMEETADV.SkuId) -AddLicenses @{}
What’s the column name? Is it ID or something else? Whatever it is, you need to reference the column name in what you feed to Set-MgUserLicense (the identity used can be either the UPN or the object identifier). For instance, if the column name is Id, then it’s Set-MgUserLicense -UserId $User.Id, if it’s UPN, then it’s Set-MgUserLicense -UserId $User.UPN.
If there’s only one value per user in the CSV, then this should work:
ForEach ($User in $Users) {
Set-MgUserLicense -UserId $User -RemoveLicenses $SkuToRemove -AddLicenses @() }
That was it Thanks a Bunch!
Hi, Tx for this great read.
What about getting the licenses including the commitment term (NCE) Year/Month.
Hi Tony,
You provide examples of disabling service plans when assigning licenses. How about an example of a script that ENABLES service plans that the user currently has disabled?
Geez… I have to leave something out that forces people to buy the Office 365 for IT Pros eBook!
In any case, simply omit the service plan identifier from the list of identifiers passed in the DisabledPlan element, and run Set-MgUserLicense again. I just did this by removing a service plan from the $DisabledServicePlans variable shown below and running the command. The removed service plan is reenabled because it is part of the Office 365 E3 product.
$DisabledServicePlans = @(“7547a3fe-08ee-4ccb-b430-5077c5041653”)
Office365E3Sku = “6fd2c87f-b296-42f0-b197-1e91e994b900”
Set-MgUserLicense -UserId Terry.Hegarty@Office365itpros.com -AddLicenses @{SkuId = $Office365E3Sku; DisabledPlans = $DisabledServicePlans} -RemoveLicenses @()