Microsoft licensing can cost a lot of money. It’s not logical to have a disabled account have licenses. Some licenses can cost up to $25 USD a month. So if you have 4 of these disabled accounts with licenses, that could easily be 100 per month. Let us Find Disabled Users with Graph API using PowerShell and find which ones have licenses.

Today’s post will be using the Microsoft.graph.users module. We can connect to graph API via the API, or we can use the Powershell module. In previous posts, I have shown how to connect to the API and pull information. This is still one of the best methods to use as the Graph API module has no way to search for things like shared mailboxes. I will cover that in a later post. So, let’s dive into the graph users module. You can read the documentation here.

The script

import-module Microsoft.Graph.Users
Connect-MgGraph -Scopes "User.Read.All"
$users = Get-MgUser -filter "accountenabled eq false"
foreach ($user in $users) {
    if ($null -ne (Get-MgUserLicenseDetail -UserId $user.Id)) {
        [pscustomobject][ordered]@{
            UPN = $user.UserPrincipalName
            Licenses = (Get-MgUserLicenseDetail -UserId $user.id).SkuPartNumber -join ", "
        }
    }
}
Disconnect-mggraph

The Breakdown

First of all, look at how little code there is compared to the previous post. The Connect-MGGraph removes so much back end work. When you run this command it will prompt you to grant it permissions. Once you disconnect, those permissions disappear. if you have a global admin account, if you don’t put in a scope, you gain access to everything. Which is super nice and scary. I prefer to have scope so I don’t break things.

If you don’t have the “Microsoft.Graph.Users” module, install it. You can install it by using the install-module commandlet.

Connect-MgGraph -Scopes "User.Read.All"

Like I said before, The connect-MGgraph command allows you to do scopes. This is very important. Back in the day we used msol. When we connected we had full control unless we limited our accounts which caused issues. In this case we are creating a temporary azure app. See the previous post about how to make an azure app. You will see how time saver this command can be. That azure app will have the scopes that you give it. If you don’t give it a scope, it has your rights. So, if you are a global admin, you can do damage on accident. It’s nice not to be in the scary ages anymore. So give it scope. In this case we are using User.Read.All as our scope. The User.Read.All permissions will help us Find Disabled Users.

$users = Get-MgUser -filter "accountenabled eq false"

The next part is where we grab all the disabled accounts. Using the Get-MgUser commandlet, we trigger the filter option. We want only accounts that are not enabled. thus the account enabled is equal to false. Very simple idea. If you run users[1] you can see the items you can search with. I do not suggest searching for anything to crazy.

The loop

Now we have all the disabled accounts we want to find the Licensed ones. We need to create a for each loop. Foreach loops are a staple for data controls. Without it… it’s just a pain. As we loop, we want to find the ones with a licenses. We need the user id from the account. So the best thing to do is do an if statement.

foreach ($user in $users) {
    if ($null -ne (Get-MgUserLicenseDetail -UserId $user.Id)) {
       #Do Something
    }
}

In this if statement we pull the licenses using the Get-MgUserLicenseDetail with the users id. If there is nothing that comes from this command it will return a null. So we test null against the command. It’s slightly faster than testing the command against null. Every user inside this if statement that is true will have a licensing. We want to display that information.

[pscustomobject][ordered]@{
        UPN = $user.UserPrincipalName
        Licenses = (Get-MgUserLicenseDetail -UserId $user.id).SkuPartNumber -join ", "
}

Here we create a PowerShell custom object. We want to display the User Principal Name, also known as, the sign name. We do this by using the $user from the foreach loop and just tag the user principal name. Next, we want to display all the licenses. The licenses come as an array. For example, my test account has 3 licenses. I want all that as a string. So, we use the Get-mguserlicensedetail command. We pull out the SKU part number. Then we do some array magic. The -join “, ” converts the array into a string. At the end of each line it adds a “,” and a space. Which makes it easier to read. The cool part about this is if it’s just one license, we it will not add the “, ” to the end. This makes it super readable.

One catch though, thanks to how Microsoft likes to hide things, the SKU is going to be coded. For example, it might say spe_f1. which means it’s an F1 license for Microsoft office. while of1 could mean the same thing but purchased from a different location. I use to try to keep a living list, however, these sku change daily and finding them will be hard. This is where Google is your friend.

Disconnect-MgGraph

Finally, we disconnect from graph API. We don’t want that app to stay there do we? Yeah, we disconnect like good admins we are. Once you are disconnected, you can review all the information this script provided. I am not a big fan of automating the removal of licenses through this method because many times other admins will licenses disabled accounts to keep email alive as a shared mailbox or other oddities. Right now, graph API poweshell module just doesn’t work with shared mailboxes. It is on the workbooks though.

Now go, Find Disabled Users With Graph API and Powershell, and Enjoy your day.