Users inactive 90 days
Users inactive 90 days are accounts in your Microsoft Entra tenant whose last recorded sign-in is older than 90 days; they are a security risk because nobody notices if one is misused. You can list them yourself with a read-only Microsoft Graph PowerShell query against the signInActivity property.
Why it matters
Microsoft says that in large environments user accounts are not always deleted when people leave, and that these obsolete accounts represent a security risk. An inactive account still holds its group memberships and app access, so it keeps whatever permissions it had.
The last sign-in time is the key signal. Microsoft notes that the hard part is defining what "for a while" means for your environment, because people can be away for legitimate reasons, and that in many organisations a reasonable window is between 90 and 180 days. This page uses 90 days as the threshold; change it to match your own policy.
Sources: How to manage inactive user accounts (Microsoft Learn); signInActivity resource type (Microsoft Learn).
Find it yourself
This script is read-only: it only reads user objects and their sign-in activity and writes a CSV file on your machine. It changes nothing in your tenant. Microsoft documents that reading lastSuccessfulSignInDateTime needs a Microsoft Entra ID P1 or P2 licence, the AuditLog.Read.All and User.Read.All Graph permissions, and at least the Reports Reader role.
# Read-only: lists enabled member users with no successful sign-in in the last 90 days
Connect-MgGraph -Scopes 'AuditLog.Read.All','User.Read.All'
$cutoff = (Get-Date).AddDays(-90).ToUniversalTime()
Get-MgUser -All -Filter "userType eq 'Member' and accountEnabled eq true" `
-Property Id,DisplayName,UserPrincipalName,CreatedDateTime,SignInActivity |
Where-Object {
$last = $_.SignInActivity.LastSuccessfulSignInDateTime
if (-not $last) { $last = $_.SignInActivity.LastSignInDateTime }
$last -and $last -lt $cutoff
} |
Select-Object DisplayName, UserPrincipalName,
@{n='LastSuccessfulSignIn';e={$_.SignInActivity.LastSuccessfulSignInDateTime}},
@{n='LastInteractiveSignIn';e={$_.SignInActivity.LastSignInDateTime}},
@{n='LastNonInteractiveSignIn';e={$_.SignInActivity.LastNonInteractiveSignInDateTime}} |
Export-Csv .\inactive-users.csv -NoTypeInformationAccounts with no sign-in timestamp at all are left out here on purpose; those are the "never signed in" case, covered on its own page.
Read the result
Each row is a member account that is still enabled but whose last recorded sign-in is more than 90 days old. Three timestamps are shown because they mean different things, per Microsoft Learn:
- LastSuccessfulSignIn: the most recent successful interactive or non-interactive sign-in. Use it to decide when the account was truly accessed. Microsoft did not backfill it; it became available on December 1, 2023.
- LastInteractiveSignIn: the last interactive attempt, successful or not, so a failed attempt can make an account look active.
- LastNonInteractiveSignIn: the last time a client signed in on the user's behalf. Some people use clients rather than signing in directly, so check this one before calling an account unused.
The sign-in value is stored on the user object and can take up to 24 hours to update, so a very recent sign-in may not show yet.
What to do next
Microsoft suggests asking three questions for each account: is the person still employed, do they still need access to what they can reach, and is the account needed for any other reason (for example a shared or service-style account)? Then act on the answers, starting with unused or over-privileged accounts.
Microsoft Learn points to options such as dynamic membership groups and Microsoft Entra ID Governance access reviews; some of these need extra licensing. Review the list with account owners or managers before you disable anything, and keep the CSV as a record of what you found.
See it continuously
ConfigCobra's Inventory & Insights includes a built-in risk view for users inactive 90 days, so you do not have to rerun a script. It is available on Microsoft 365, Azure, Google Cloud and AWS, with one inventory across clouds, findings linked to the inventory objects they concern, and CSV export. ConfigCobra stays read-only and never changes your tenant.
See it for your whole tenant, continuously
Inventory & Insights lists these risks with built-in views and CSV export, read-only.