To find inactive users in Microsoft Entra ID, read each enabled account's last successful sign-in and keep those with none for 90 days or more. The date needs Microsoft Entra ID P1 or P2 and can take up to 24 hours to update. A blank date does not always mean the person never signed in.
| Method | Best fit |
|---|---|
| PowerShell | A CSV of enabled member accounts with no successful sign-in for the days you choose. |
| Admin centre | A quick look: add a last sign-in column to the Users list and filter it. |
| Altivane | Recommended for recurring reviews, with stale and never signed in accounts 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. The tenant needs Microsoft Entra ID P1 or P2. The PowerShell example requests User. and AuditLog.Read.All; your organisation may require administrator consent.
Microsoft calls 90 to 180 days reasonable for many organisations. The script uses 90 and Altivane starts at 180. The examples read and export enabled member accounts, leave guests out and change nothing.
Option 1
Find inactive users with PowerShell
Install the modules once, then connect and export the enabled member accounts with no successful sign-in for 90 days or more. Change $days to widen or narrow the window.
# Install once, if the modules are not already installed.
Install-Module Microsoft.Graph.Authentication, Microsoft.Graph.Users -Scope CurrentUser
$ErrorActionPreference = 'Stop'
$days = 90
$cutoff = (Get-Date).ToUniversalTime().AddDays(-$days)
Connect-MgGraph -Scopes 'User.Read.All', 'AuditLog.Read.All' -NoWelcome
try {
$properties = 'displayName,userPrincipalName,userType,createdDateTime,signInActivity'
$users = Get-MgUser -All -Filter 'accountEnabled eq true' -Property $properties
$members = $users | Where-Object { $_.UserType -eq 'Member' }
$report = @(foreach ($user in $members) {
$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 }
[pscustomobject]@{
DisplayName = $user.DisplayName
UserPrincipalName = $user.UserPrincipalName
LastSuccessfulSignIn = $last
LastSignInAttempt = $user.SignInActivity.LastSignInDateTime
Created = $user.CreatedDateTime
}
})
if ($report.Count -eq 0) {
Write-Output "No enabled member account has been inactive for $days days or more."
} else {
$report | Sort-Object LastSuccessfulSignIn |
Export-Csv -Path './inactive-users.csv' -NoTypeInformation -Encoding utf8
Write-Output "$($report.Count) enabled member accounts inactive for $days days or more."
}
} finally {
Disconnect-MgGraph | Out-Null
}
An account with no sign-in on record counts from its creation date, so a new account is not listed until it is $days old. The script reads the sign-in property for every enabled account because Microsoft says a filter on signInActivity cannot be combined with another filterable property. LastSignInAttempt includes failed attempts. Remove the Where-Object line to include guests.
Check a single account
Reconnect with Connect-MgGraph, then read 1 person's 3 sign-in dates.
# 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,
LastNonInteractiveSignInDateTime
These are the last successful sign-in, the last interactive attempt and the last attempt by a client acting for the person.
Option 2
Find inactive users in the admin centre
- Sign in to the Microsoft Entra admin centre as at least a Reports Reader.
- Go to Entra ID > Users.
- Select Manage view, then Edit columns.
- Select + Add column, choose Last interactive sign-in time and select Save.
- Select Add filter, choose the new column, set the operator to <= and pick a date 90 days back to list people whose last sign-in is before it.
- To check 1 person, open them and read the Sign-ins tile under My Feed.
This suits a quick look at 1 tenant. The column is named for interactive sign-ins, so an account used only by background clients can look idle.
Option 3 ยท Recommended for recurring reviews
Report on inactive users with Altivane
- Open Altivane and choose the tenant.
- Build the tenant's documents with the Identity scorecard report in full, so accounts are listed as well as counted, and set Stale users and New account grace in days.
- Open the tenant's Excel workbook and read Stale User Accounts, longest inactive first, and Users Never Signed In.
- Read User Account Summary for the counts and the scorecard for how each list rates against your thresholds.
Altivane reads the same sign-in activity, so the same licence applies; where it cannot be read, the tables say Not collected rather than showing an empty list. It counts the last successful sign-in, and for an account that last signed in before Microsoft began recording that on 1 December 2023 it uses the older sign-in dates. The scorecard marks each list Warning or Critical at the share of enabled members you set, 5% and 15% to start. The build also writes the tenant's Word report.
Contact usCommon questions
Why is the sign-in date blank for an account that is used?
Microsoft says the property is not returned for an account that never signed in or last signed in before April 2020. The last successful sign-in is also recorded only from 1 December 2023, and a new sign-in can take up to 24 hours to show.
Do failed sign-ins count as activity?
No. lastSignInDateTime counts failed attempts, so an account someone only tried to use can look active. The script and Altivane use lastSuccessfulSignInDateTime.
What should I do with the accounts on the list?
Ask whether the person still works there and the account is still needed. Then see which hold licences in how to export licensed users, and which lack an MFA method in how to find users without MFA.
Microsoft references
Technical references checked on 5 October 2026. The example scripts have been reviewed and syntax checked; they have not been run against a live tenant.