All guides

Find inactive Microsoft 365 users and reclaim their licences

Microsoft 365 · Microsoft Graph PowerShell · 6 min read

Every tenant collects accounts nobody uses: leavers who were never fully offboarded, test accounts, shared mailboxes someone licensed by mistake. Each one can be holding a paid licence. This guide shows how to find them with Microsoft Graph PowerShell in a few minutes.

What you need

Install-Module Microsoft.Graph -Scope CurrentUser

Step 1: Connect to Microsoft Graph

Connect-MgGraph -Scopes "User.Read.All","AuditLog.Read.All","Organization.Read.All"

The first time, you'll be asked to consent to these permissions. They're all read-only.

Step 2: Pull users with their sign-in activity

signInActivity isn't returned by default, so ask for it explicitly with -Property:

$cutoff = (Get-Date).ToUniversalTime().AddDays(-90)

$users = Get-MgUser -All -Property DisplayName,UserPrincipalName,AccountEnabled,
    CreatedDateTime,SignInActivity,AssignedLicenses

Step 3: Filter to the inactive ones

Check both interactive and non-interactive sign-ins. A user who only syncs mail on their phone shows up as non-interactive, and you don't want to flag them.

$inactive = $users | Where-Object {
    $_.AccountEnabled -and
    $_.CreatedDateTime -lt $cutoff -and
    (
      (-not $_.SignInActivity.LastSignInDateTime -or
            $_.SignInActivity.LastSignInDateTime -lt $cutoff) -and
      (-not $_.SignInActivity.LastNonInteractiveSignInDateTime -or
            $_.SignInActivity.LastNonInteractiveSignInDateTime -lt $cutoff)
    )
}

The CreatedDateTime check skips brand-new accounts that simply haven't signed in yet.

Step 4: Show licence names and export

Licences come back as SKU IDs. Map them to readable names with Get-MgSubscribedSku:

$sku = @{}
Get-MgSubscribedSku -All | ForEach-Object { $sku[$_.SkuId] = $_.SkuPartNumber }

$inactive | Select-Object DisplayName, UserPrincipalName,
    @{n='LastSignIn'; e={ $_.SignInActivity.LastSignInDateTime }},
    @{n='Licences';   e={ ($_.AssignedLicenses | ForEach-Object { $sku[$_.SkuId] }) -join '; ' }} |
  Export-Csv .\InactiveUsers.csv -NoTypeInformation

Before you remove anything: send the list to the user's manager or HR first. Some accounts are inactive on purpose: people on long leave, service accounts and break-glass admin accounts.

Step 5: Decide what to do with each account

  1. Leavers: block sign-in, convert the mailbox to a shared mailbox if it's needed, then remove the licence.
  2. Test accounts: delete them, or remove their licences.
  3. Long leave: leave them alone and note the return date.

Skip the copy-paste

The free M365 Inactive User Report does all of this in one command and prints a licence total.

Get the free script