Microsoft 365 licensing

How to find inactive licensed users in Microsoft 365

Find licensed accounts with no recent sign-in with Microsoft Graph PowerShell, export the Active users usage report from the admin centre, or run Altivane's Licensing report for a rated list.

By the Published

To find inactive licensed users in Microsoft 365, read each enabled member's licences and last successful sign-in, and keep those that hold a licence but haven't signed in for your cutoff, such as 90 days. The sign-in date needs Microsoft Entra ID P1 or P2, and an account that has never signed in counts from the day it was created.

Choose the method that fits the job
MethodBest fit
PowerShellA CSV of licensed members with no successful sign-in for the days you choose, with their products.
Admin centreA view of who uses each licensed product, from the usage reports.
AltivaneRecommended for regular licence reviews, with the count rated against a threshold.

Before you start

Use an account with a role that can read sign-in activity: Reports Reader is the least privileged role Microsoft lists, and it can open the usage reports too. The tenant needs Microsoft Entra ID P1 or P2 for the sign-in date. The PowerShell example requests User.Read.All, AuditLog.Read.All and LicenseAssignment.Read.All; your organisation may require administrator consent.

Know how fresh the date is. The last successful sign-in is recorded from 1 December 2023 and isn't filled in for earlier ones, and it can take up to 24 hours to update. Microsoft calls 90 to 180 days a reasonable window for many organisations: the script uses 90 and Altivane starts at 180. The examples leave out guests and disabled accounts. They read and export, and change no account or licence.

Option 1

Find inactive licensed users with PowerShell

Install the modules once, then connect and export the licensed member accounts with no successful sign-in for 90 days or more. Change $days to widen or narrow the window.

PowerShell
# Install once, if the modules are not already installed.
Install-Module Microsoft.Graph.Authentication, Microsoft.Graph.Users -Scope CurrentUser
Install-Module Microsoft.Graph.Identity.DirectoryManagement -Scope CurrentUser
PowerShell
$ErrorActionPreference = 'Stop'
$days = 90
$cutoff = (Get-Date).ToUniversalTime().AddDays(-$days)
$scopes = 'User.Read.All', 'AuditLog.Read.All', 'LicenseAssignment.Read.All'
Connect-MgGraph -Scopes $scopes -NoWelcome
try {
    $names = @{}
    foreach ($sku in Get-MgSubscribedSku -All) {
        $names[$sku.SkuId.ToString()] = $sku.SkuPartNumber
    }
    $properties = 'displayName,userPrincipalName,userType,createdDateTime,' +
        'assignedLicenses,signInActivity'
    $users = Get-MgUser -All -Filter 'accountEnabled eq true' -Property $properties
    $report = @(foreach ($user in $users) {
        if ($user.UserType -ne 'Member' -or $user.AssignedLicenses.Count -eq 0) { continue }
        $last = $user.SignInActivity.LastSuccessfulSignInDateTime
        # With no sign-in on record, count from the day the account was created.
        $since = if ($last) { $last } else { $user.CreatedDateTime }
        if ($since -and $since.ToUniversalTime() -gt $cutoff) { continue }
        $held = @($user.AssignedLicenses | ForEach-Object {
            $id = $_.SkuId.ToString()
            if ($names.ContainsKey($id)) { $names[$id] } else { $id }
        })
        [pscustomobject]@{
            DisplayName          = $user.DisplayName
            UserPrincipalName    = $user.UserPrincipalName
            LastSuccessfulSignIn = $last
            Created              = $user.CreatedDateTime
            Licences             = $held -join '; '
        }
    })
    if ($report.Count -eq 0) {
        Write-Output "No licensed member has been inactive for $days days or more."
    } else {
        $report | Sort-Object LastSuccessfulSignIn |
            Export-Csv -Path './inactive-licensed-users.csv' -NoTypeInformation -Encoding utf8
        Write-Output "$($report.Count) licensed members inactive for $days days or more."
    }
} finally {
    Disconnect-MgGraph | Out-Null
}

The CSV holds each account's name, sign-in name, last successful sign-in, creation date and the part numbers of its licences, the longest idle first; a blank sign-in means none on record. The script reads every enabled account's sign-in activity, because Microsoft says a filter on signInActivity can't be combined with another filterable property. Remove the UserType test to include guests.

Check a single account

Reconnect with Connect-MgGraph, then read 1 person's sign-in dates and licences.

PowerShell
# Run while connected to Microsoft Graph. Replace the example account.
$filter = "userPrincipalName eq 'person@example.com'"
$user = Get-MgUser -Filter $filter -Property 'displayName,signInActivity'
$user.SignInActivity | Select-Object LastSuccessfulSignInDateTime, LastSignInDateTime
Get-MgUserLicenseDetail -UserId 'person@example.com' | Select-Object SkuPartNumber

LastSignInDateTime counts failed interactive attempts as well, so it can be later than the last successful sign-in.

Option 2

Review licensed product use in the admin centre

  1. Sign in to the Microsoft 365 admin centre for the correct tenant.
  2. Go to Reports > Usage. If you don't see Reports, select Show all first.
  3. Under Reports, select Microsoft 365 apps, then the Active users tab.
  4. In the table, read each person's last active date for Exchange, OneDrive, SharePoint, Viva Engage and Teams beside the licences they hold for each. Use Choose columns to add the columns you need.
  5. Select Export for a CSV of every user to sort by last activity.

The report measures product use, not sign-ins, and its data usually runs 24 to 48 hours behind. Names are hidden in usage reports by default, so the list may show codes in place of people until the tenant's report settings show them. It suits a review of who uses what; for sign-in dates, use PowerShell or Altivane.

Common questions

Why is a shared mailbox's account on the list?

A shared mailbox has a user account that nobody signs in to, so a licensed one shows as never signed in. It may need its licence: a shared mailbox over 50 GB, with an archive or on litigation hold does.

Why does a recent sign-in not show yet?

The last sign-in date can take up to 24 hours to update. An account whose last successful sign-in came before 1 December 2023 has no date in that property, so check its older sign-in dates before treating it as never used.

What about disabled accounts that still hold licences?

The list keeps enabled accounts only. See how to find disabled users with licences in Microsoft 365.

Microsoft references

Technical references checked on 7 November 2026. The example scripts have been reviewed and syntax checked; they have not been run against a live tenant.

Talk to us about Altivane

Questions, or pricing? Get in touch.

Contact us