Some Cmdlets Cease Working in 2022
Sometimes blessed serendipity erupts, even in the world of technology. Such was the case for my TEC 2021 session about using the Microsoft Graph to manage Office 365 tenants (available online) when I discussed the Microsoft Graph PowerShell SDK. The organizing team recorded the TEC sessions in advance to ensure that the conference ran smoothly, so I couldn’t use the example dropped into my lap when Microsoft announced that the Azure AD cmdlets for license management will stop working on June 30, 2022. (Update March 15, 2022: Microsoft has pushed the retirement date out to August 26, 2022).
This was a follow-on from the heads-up Microsoft gave about the future of the Azure AD Graph in June. In a nutshell, the Azure AD Graph is an older component separate from the Microsoft Graph and is being retired. The Azure AD and Microsoft Online Services modules which depend on the Azure AD Graph enter an unsupported state in 2022. Most of the cmdlets in the modules will continue working, but the license management cmdlets will stop returning data on June 30, 2022 because Microsoft is moving to a new license management platform.
Update: Microsoft has pushed the deprecation date for the Azure AD and MSOL modules to the end of 2022.
Obviously, the deprecation has a knock-on effect on the many scripts people use to manage different aspects of Azure AD, with the immediate need to come up with a replacement for the license management cmdlets before they stop working. Microsoft’s guidance is to use either the assignLicense Graph API or the Set-MgUserLicense cmdlet from the Microsoft Graph PowerShell SDK.
Upgrading to the Microsoft Graph PowerShell SDK
How difficult can it be to swap out an Azure AD cmdlet and replace it with an SDK cmdlet? On the surface, the replacement should be straightforward, but as usually happens, that perception lasts about five minutes. At least, that’s what I discovered when I upgraded my script to remove a single service plan from an Office 365 SKU (product) for multiple accounts. This script uses Azure AD calls to process license information, so it’s a good example of the kind of script impacted by the looming deadline next June.
The first issue I met is the documentation for the SDK cmdlets. As I understand the situation, Microsoft generates much of the documentation from the code, which accounts for some odd phrasing and total incomprehensive nature of the text. Naturally, I moaned bitterly and after some probing, discovered some helpful hints to point me in the right direction.
Steps to Upgrade
The Microsoft Graph PowerShell SDK supports delegated and app-only authentication. In this example, we’ll use delegated access to fetch an access token from Azure AD, mostly because this is what happens in the Azure AD-based version of the script. In outline, here are the steps I took to upgrade the code:
Connect to the Graph. The Connect-MgGraph cmdlet connects to the Graph. The connection request can specify the permissions needed to interact with the data. For example, this connection requests the permissions to read and write directory information.
Connect-MgGraph -Scopes "User.ReadWrite.All","Directory.ReadWrite.All"
Select the Beta profile. Graph APIs come in different versions (typically, V1.0 for full production and Beta for development). License management information for user accounts is available using the Get-MgUser cmdlet only if you select the Beta profile. This code checks the loaded profile and selects the Beta if not set.
$Profile = (Get-MgProfile).Name If ($Profile -ne "beta") { Select-MgProfile Beta }
Fetch the product licenses (SKUs) in the tenant. The script allows the administrator to select an individual service plan (like Forms, Whiteboard, or Planner) to remove from a SKU (like Office 365 E3). To allow the administrator to choose, we fetch the list of SKUs available in the tenant using the Get-MgSubscribedSku cmdlet.
[array]$Skus = (Get-MgSubscribedSku)
We can then use the SKU information to present menus of SKUs and service plans to the administrator for them to choose the service plan to remove. The result is a selected SKU and selected service plan, both identified as GUIDs.
Select the user accounts to process. The Get-MgUser cmdlet is a good way to select a set of Azure AD accounts for processing. For instance, to find all the accounts assigned a specific SKU, you can use a command like:
[array]$Users = Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq $SelectedSkuId)" -all
However, this script uses Get-ExoMailbox because its filtering capabilities are superior. The important thing is to have a set of account GUIDs or user principal names to use as an input when the code updates license information. Get-ExoMailbox can provide this information, so I didn’t upgrade this section of the code.
Process license information for accounts. The script loops through the set of selected accounts to retrieve the current license information (using Get-MgUser) and then build a license object (MicrosoftGraphAssignedLicense) containing information about service plans already disabled for the account plus the service plan we want to remove now. After populating the license object, we call Set-MgUserLicense to apply the update.
$ExistingDisabledPlans = $Null ForEach ($S in $User.AssignedLicenses) { # Check for existing disabled licenses If ($S.SkuId -eq $SelectedSkuId) { $ExistingDisabledPlans = $S.DisabledPlans } } $ExistingDisabledPlans += $ServicePlanId # Add the plan we want to remove to the set of disabled plans $LicenseToRemove = New-Object -TypeName Microsoft.Graph.PowerShell.Models.MicrosoftGraphAssignedLicense $LicenseToRemove.SkuId = $SelectedSkuId $LicenseToRemove.DisabledPlans = $ExistingDisabledPlans $Status = Set-MgUserLicense -UserId $User.id -AddLicenses $LicenseToRemove -RemoveLicenses @() }
I’d be the first to say that the syntax for Set-MgUserLicense is a tad odd. Given that the syntax to add a new SKU to an account is:
$User = Get-MgUser -UserId Imran.Khan@office365itpros.com $NewSkuId ="8c4ce438-32a7-4ac5-91a6-e22ae08d9c8b" Set-MgUserLicense -UserId $User.id -AddLicenses @{SkuId = $NewSkuId} -RemoveLicenses @()
I expected to use the license object with the RemoveLicenses parameter instead of AddLicenses, but that’s not the way thI expected to update the license object by passing information in the RemoveLicenses parameter instead of AddLicenses, but that’s not the way things work. Adding a new SKU to an account enables all the service plans contained in the SKU. In this case, we pass updated license information to tell the Graph to disable the specified service plans from the specified SKU. It would be nice if the documentation was clearer on this subject.
Note that passing a blank array to RemoveLicenses is necessary to make the cmdlet happy. The parameter requires a value even if it is blank and will object if it finds nothing passed.
Removing Licenses from Accounts
To remove one or more product licenses (SKUs) from an account, the process is:
- Create an array of the product licenses to remove. You can remove multiple product licenses at one time. The array holds the GUIDs identifying the product SKUs to remove.
- Run Set-MgUserLicense and pass the array as the RemoveLicenses parameter. This time we pass a blank array to the AddLicenses parameter to make the cmdlet content. This example removes two products from an account:
[array]$SkuToRemove = "1f2f344a-700d-42c9-9427-5cea1d5d7ba6", "4016f256-b063-4864-816e-d818aad600c9" Set-MgUserLicense -UserId $User.id -RemoveLicenses $SkuToRemove -AddLicenses @()
Validating Service Plan Removal
To test that everything worked, I used the script to remove the MyAnalytics service plan (which Microsoft is renaming as Viva Insights) from a bunch of Office 365 E3 accounts. Everything went smoothly to update the license assignments for the accounts. You can verify the assignments with PowerShell or by viewing user information through the Microsoft 365 admin center (Figure 1).
How Long to Upgrade
I’m sure that people will be worried about the time required to upgrade PowerShell scripts before the hammer comes down on June 30. Several variables exist, including:
- Developer familiarity with the Microsoft Graph PowerShell SDK.
- How quickly Microsoft improves the SDK and its documentation.
- The complexity of the existing scripts and how many Azure AD or Microsoft Online Services license management cmdlets need replacement.
- The environment where scripts run (for instance, the authentication method used).
We have just over nine months left before the older cmdlets stop working. The number of affected scripts in a tenant is likely small and you don’t need to rewrite everything from scratch. That is, unless you’ve created your own license management solution. If that’s the case, you have some work to do. If not, updating most scripts should be a matter of days rather than weeks.
Explore and Discover Ahead of Module Deprecation
You can grab a copy of my script from GitHub. As a reminder, this is code written to explore and demonstrate a principle rather than to be a production-quality solution. Use the code to learn, explore, and interact with license management through the Microsoft Graph PowerShell SDK. Working with the SDK will get easier over time. At least, if it doesn’t, Microsoft might have some unhappy customers when the deprecation happens. It’s time to get coding.
Hi,
can I with one cmdlet Set-MgUserLicense add more SkuIds at the same time? How to construct AddLicenses parameter.
Thanks.
AddLicenses is a hash table.
But I would add the licenses separately so that you can detect errors, like not having a license available for a particular SKU.
How can we use it using Azure Runbook. I tried with delegated access and also with application access. but it is not working?
Any help is much appreciated.
Thanks
I’m trying to get this to work. However, it seems I cannot set the scopes.
Connect-MgGraph -Scopes “User.ReadWrite.All”,”Directory.ReadWrite.All” -ClientID X -TenantId X -CertificateThumbprint X
I get Connect-MgGraph : Parameter set cannot be resolved using the specified named parameters. If I try with just -Scopes, it wants me to login (this needs to be automated via the task scheduler).
If I try just Get-MgSubscribedSku:
Get-MgSubscribedSku : Insufficient privileges to complete the operation.
I granted the app these rights in Azure:
Directory.Read.All,
Group.Read.All,
Group.ReadWrite.All,
User.Read.All,
User.ReadWrite.All
Very tempted to just grant it every right in the world. Why does it have to be so hard Microsoft?
I think I figured it out, had to use delegated access not app-only authenication.
The joys of dealing with the Graph…
Thank you, this is very helpful, yay to changes from Microsoft 😀
$ExistingDisabledPlans = $Null
ForEach ($S in $User.AssignedLicenses) { # Check for existing disabled licenses
If ($S.SkuId -eq $SelectedSkuId) {
$ExistingDisabledPlans = $S.DisabledPlans }
}
$ExistingDisabledPlans += $ServicePlanId # Add the plan we want to remove to the set of disabled plans
$LicenseToRemove = New-Object -TypeName Microsoft.Graph.PowerShell.Models.MicrosoftGraphAssignedLicense
$LicenseToRemove.SkuId = $SelectedSkuId
$LicenseToRemove.DisabledPlans = $ExistingDisabledPlans
$Status = Set-MgUserLicense -UserId $User.id -AddLicenses $LicenseToRemove -RemoveLicenses @()
}
Is not working as there is a “}” missing in the code.
Also as per the Microsoft article they have mentioned about the hash table to be used to assign required license.
The code works. What you see in the article is an extract. You should use the complete script from GitHub to test.
What hash table are you referring to?