Microsoft Graph – Revoke user licenses

12/10/2023

If you haven’t converted your scripts from using the AzureAD and MSOnline modules to Microsoft Graph, you are way behind!

With Graph, you can do nearly any administrative action programmatically that you’d normally do in the M365/Azure web portal.

There are two primary ways I see admins interacting with Graph API. One method is by using the Invoke-WebRequest cmdlet to interact directly with the API.

The other method, which is my preferred method, is to use the Graph PowerShell modules. In this post, I will show you how to revoke a user’s M365 license using Graph Powershell.

#install the pre-requisite powershell module if you don't already have it:
Install-module microsoft.graph.users

#connect to microsoft graph
Connect-Graph -scopes user.readwrite.all -NoWelcome
Write-host "Connected to Microsoft Graph" -Foregroundcolor Green

$user = Read-Host "Enter the user's UserPrincipalName"
Get-MgUserLicenseDetail -UserId $user -All | Select-Object -ExpandProperty SkuId

The result of the Get-MgUserLicenseDetail cmdlet above is that it will output an array of SKU IDs of each license that is assigned to the user. Now that we know which SKU IDs are assigned to the user, we can remove them. The easiest way to do this is to assign the SKU IDs array to a variable, and then reference that variable to remove all licenses in the array using a foreach loop. For example:

$skuids = Get-MgUserLicenseDetail -UserId $user -All | Select-Object -ExpandProperty SkuId
foreach ($skuid in $skuids) {
     Set-MgUserLicense -UserId $user -RemoveLicenses @($skuid) -AddLicenses @{}
}

Now, the licenses have been removed. However, there are two ways we can improve this script. First, how do we know what licenses we are actually removing? Also, the Set-MgUserLicense cmdlet outputs verbose information that we don’t care about, so how can we prevent that output?

To make things more user-friendly, we need to know what each SKU ID is. Fortunately, Microsoft has a .csv file available for us to reference here. (taken from this Microsoft Learn page). Using this .csv file, we can construct a hash table in PowerShell which connects each SKU ID to the friendly license name. See below:

#copy over the contents from the Microsoft-provided .csv file. Note below, I have only included a small snippet of it for brevity
$licenselist = @"
friendlyname,skuid
Microsoft 365 E3,05e9a617-0261-4cee-bb44-138d3ef5d965
Microsoft 365 E3 Extra Features,f5b15d67-b99e-406b-90f1-308452f94de6
Microsoft 365 E3 - Unattended License,c2ac2ee4-9bb1-47e4-8541-d689c7e83371
Office 365 E3,6fd2c87f-b296-42f0-b197-1e91e994b900
"@

#convert the csv variable into a hash table.
#licenseHashTable = @{}
$licenselist | ConvertFrom-Csv | Foreach-Object {
     $licenseHashTable[$_.skuid] = $_.friendlyname
}

The .csv file I referenced has many more licenses listed, and you might choose to include all of them in your hash table and call it a day. You can certainly do that, and in fact, that is what I do in my production script. Now that we have a $licenseHashTable variable, let’s put it to use!

Let’s also go ahead and take care of the verbose output that we don’t need. To do that, we will append 1>$null 4>$null 6>$null to the Set-MgUserLicense cmdlet. Essentially, we are sending output streams 1, 4, and 6 to the netherworld by redirecting their output to $null. The output streams in PowerShell are as follows:

Stream #DescriptionWrite Cmdlet
1Success streamWrite-Output
2Error streamWrite-Error
3Warning streamWrite-Warning
4Verbose streamWrite-Verbose
5Debug streamWrite-Debug
6Information streamWrite-Information
n/aProgress streamWrite-Progress


Study the script below and try to follow along in your head each step of the way.

#Use a foreach loop, since we don't know if the user will have 1 license or 100 licenses.
foreach ($skuid in $skuids)
{

    # If the SKU ID is found in the hash table, run the block of code below
    if ($licenseHashTable.ContainsKey($skuid))
    {
        $friendlyName = $licenseHashTable[$skuid]
        Write-Host "Removing license " -NoNewline -ForegroundColor DarkMagenta
        Write-Host "$friendlyname" -ForegroundColor Magenta -NoNewline
        Write-Host " from $user." -ForegroundColor DarkMagenta
        Set-MgUserLicense -UserId $user-RemoveLicenses @($skuid) -AddLicenses @{} 1>$null 4>$null 6>$null
    }

    
    # if the SKU ID is NOT found in the hash table, then run the code block below
    elseif (!($licenseHashTable.ContainsKey($skuid)))
    {
        Write-Host "`nCould not find the display name for the license ID $Skuid" -ForegroundColor DarkGray
        Write-Host "You can search the web for the SKUID $($skuid) if you would like to know what this license is." -ForegroundColor DarkGray
        Write-Host "`nRemoving license " -NoNewline -ForegroundColor DarkMagenta
        Write-Host "$($skuid).skuid" -ForegroundColor Magenta -NoNewline
        Write-Host " from $($user)." -ForegroundColor DarkMagenta
        Set-MgUserLicense -UserId $user -RemoveLicenses @($skuid) -AddLicenses @{} 1>$null 4>$null 6>$null
    }

    # here we account for some unknown situation. This block should not get run.
    else
    {
        Write-Host "`nFailure when revoking M365 license $skuid " -ForegroundColor Red
    }
}

And that’s all there is to it!

You could always add more bells and whistles to the script, such as enabling logging, making it completely automated as part of user offboarding, and/or have it trigger an email if it fails, etc. And if you don’t like the colors I use, pick your own! lol

Till next time!
Trevor