Switch Microsoft 365 Licenses While Preserving the Set of Service Plans
After reading the article about license management with the Microsoft Graph PowerShell SDK, a reader asked about the best way to switch Microsoft 365 licenses for Entra ID accounts while preserving service continuity (here’s the full question). The problem here is that each of the account licenses that need to be processed might have a different set of disabled service plans. Switching one or two licenses is easy to do manually; switching hundreds rapidly becomes a very boring exercise. That’s when automation through PowerShell becomes an invaluable tool for tenant administrators.
The exercise involved switching user licenses from Exchange Online Plan 2 to Microsoft 365 E3. I anticipate that this requirement will arise for tenants with Office 365 E3 or Office 365 E5 licenses who discover that they must upgrade to Microsoft 365 E3 or Microsoft 365 E5 to become eligible to use Microsoft 365 Copilot. The license upgrade must happen before a tenant can move ahead to purchase the minimum of 300 Copilot seats and decide who gets the expensive $30/month Copilot licenses.
Group-based licensing is a good solution for automated license assignment. Administrators can customize license configurations by disabling features. What we explore here is scripting license switching by removing an old license and replacing it with a new license. A tenant might decide to take this approach to have total control over the flow of license assignments.
All About Service Plans
Usually, there’s no problem maintaining the feature set available to users when upgrading licenses. A higher-level license always offers more functionality than a lower-level license. Microsoft controls feature availability through service plans. A service plan dictates whether the functionality it controls is enabled or disabled. For instance, if a tenant doesn’t want to use Viva Engage (Yammer), it should disable the Viva Engage Core service plan in the licenses assigned to user accounts. Tenants often disable obsolete service plans that Microsoft still bundles, like StaffHub, Sway, and Kaizala. Figure 1 shows an account with an Office 365 E3 license with two disabled service plans.
The task is to make sure that the upgraded license assigned to user accounts keeps the same functionality. In other words, if a service plan is disabled in the old license, it should be disabled in the new.
The AssignedLicenses property stores license information for user accounts. Among the information kept for a license is the set of disabled service plans. For instance, this output shows that four disabled service plans are present for a Microsoft 365 E5 license.
$User = Get-MgUser -UserId Chris.Bishop@office365itpros.com -Property AssignedLicenses $User.Assignedlicenses | fl DisabledPlans : {0d0c0d31-fae7-41f2-b909-eaf4d7f26dba, 0898bdbb-73b0-471a-81e5-20f1fe4dd66e, 8c7d2df8-86f0-4902-b2ed-a0458298f3b3, 7547a3fe-08ee-4ccb-b430-5077c5041653…} SkuId : 06ebc4ee-1bb5-47dd-8120-11324bc54e06
Memorizing product and service plan GUIDs is not an easy task. You can download a CSV file containing information about the GUIDs and associated names for products and service plans. Microsoft refreshes the CSV file periodically and it’s possible to read the information from the CSV into arrays to make the data accessible in PowerShell scripts.
You could take the set of disabled service plans and use it when assigning a new product license. This will work if the new license supports the same service plans as the old. Unfortunately, this is not the case. For instance, different versions of service plans for apps like To Do, Forms, and Exchange Online exist. To be sure that the set of disabled service plans carries across to the new license, you must work out the equivalents for the disabled plans from the old license and use those values when assigning the new license.
Building a Script to Switch Microsoft 365 Licenses
To figure out the steps necessary to switch licenses for user accounts, I decided to write a script to upgrade accounts from an Office 365 E3 license to have a Microsoft 365 license. The code:
- Retrieves the set of subscriptions (purchased products) for the tenant.
- Imports the product and service plan information published by Microsoft.
- Builds hash tables for the products used in the tenant and the service plans. These hash tables are used for fast lookup to resolve GUIDs into product and service plan names.
- Builds arrays of the service plans in the old and new licenses and uses this data to figure out the matching service plan GUIDs to make sure that disabled features carry across. The output is an array that the script can check for a disabled service plan in the old license to find out the correct value to use when assigning the new license. The script makes a further change to the array to add an entry for Kaizala because the service plan names are quite different in the two licenses (KAIZALA_O365_P3 and KAIZALA_STANDALONE).
- Finds the set of user accounts with Office 365 E3 licenses.
- Builds a list of accounts and disabled service plan information. An entry in the list looks like this:
UserId : 96155a51-6885-4c8f-a8b6-e1614af08675 DisplayName : John James SkuId : 6fd2c87f-b296-42f0-b197-1e91e994b900 DisabledPlans : {aebd3021-9f8f-4bf8-bbe3-0ed2f4f047a1, 7547a3fe-08ee-4ccb-b430-5077c5041653}
- Loops through the list. For each account, figure out the set of disabled service plans for the new license and then run the Set-MgUserLicense cmdlet to add the new license to the account. If the license assignment succeeds, a separate Set-MgUserLicense command removes the old license. Despite the support in applications like Exchange Online for “license stacking,” it’s safer to remove licenses only when sure that a replacement license is in place. Here’s the code to add the new license and remove the old. You can see that the set of disabled service plans are passed as an array in the DisabledPlans parameter:
$Status = Set-MgUserLicense -UserId $User.UserId -AddLicenses @{SkuId = $NewSKU; DisabledPlans = $DisabledServicePlansToUse} ` -RemoveLicenses @() -ErrorAction SilentlyContinue If (!($Status)) { Write-Host "Error assigning license - please check availability" -ForegroundColor Red } Else { Write-Host ("{0} license assigned to account {1}" -f ($TenantSkuHash[$NewSKU]), $User.DisplayName ) # Now to remove the old license $Status = Set-MgUserLicense -UserId $User.UserId -AddLicenses @() -RemoveLicenses $OriginalSku -ErrorAction SilentlyContinue If ($Status) { Write-Host ("{0} license removed from account {1}" -f ($TenantSkuHash[$OriginalSKU]), $User.DisplayName ) } }
You can download the script from GitHub. As always, the caveat applies that although the code works, it is not production-ready. Some additional testing and probably some development is necessary to bullet-proof the code and make it ready for use in your tenant. For instance, consider adding a check that a license is available before proceeding to assign it. But the code is straightforward PowerShell and should be easy enough to follow.
Multiple Routes to a Solution
The advantage of using PowerShell to automate administrative activities in Microsoft 365 is its flexibility and the degree of control it affords. That’s no reason to always choose PowerShell when out-of-the-box software is available. Group-based licensing will likely handle many of the license assignment scenarios that you’ll run into. But if group-based licensing can’t handle your requirements, it’s nice to know that there’s always PowerShell.
Hello Tony,
How can I use this script and switch licenses based of a CSV file( list of users)? Please let me know