Cleaning up Inactive Accounts
Lots of people don’t like cleaning up. As evidence, I cite my three sons. Cleaning up inactive accounts in Entra ID might sound about as exciting as picking up cigarette butts, but it’s important because every account represents a potential entry point for an attacker. Reducing your attack surface will always improve your security, so locking out or removing accounts can give you a significant security improvement.
If you’ve ever inherited a tenant with lots of these ghost accounts, you know the risks—unused accounts are an easy target for attackers, given that they often have stale passwords and forgotten privileges. If you don’t know whether you have inactive accounts in your tenant, that’s all the more reason to find out and take action.
Why Inactive Accounts Are a Problem
Accounts can become inactive for a variety of reasons. They may be forgotten accounts for devices or applications you’re not using, or they may belong to departed employees or contractors. They could be test accounts or accounts that were created for a short-term use that’s now past. Whatever the reason, these accounts often retain access to mailboxes, SharePoint sites, OneDrive files, or apps you’d rather not have strangers poking around in. Worse, these inactive accounts may have weak credentials or not be enabled for MFA. That would make them an open invitation for credential-stuffing attacks.
Entra ID doesn’t have any native mechanism to find or remove these accounts for you. You can always look, but Microsoft doesn’t sweep these under the rug for you. Entra ID will happily let dormant accounts linger forever unless you take action. Manual cleanup? Sure, if you enjoy clicking through the admin center like it’s a choose-your-own-adventure novel. But we’re smarter than that. With PowerShell, we can find these accounts, disable them, and set up a recurring sweep—all without breaking a sweat.
Before we get started, let me point out that this is meant to be a simple and practical example. If you really want to get down into the details of your user and guest accounts, Tony Redmond has a much more sophisticated reporting script that looks at the actions taken by accounts, not just their sign-in times. However, for a first cut at the problem, simply disabling any account that looks unused is a good start for tenants that aren’t getting periodic account maintenance.
Step 1: Prepare Your Tools
Before we can do much with user accounts, we’ll need the Microsoft Graph PowerShell module. If you don’t have it installed, you can install it with:
Install-Module -Name Microsoft.Graph -Scope CurrentUser
Then connect to Entra ID with the required permissions. Run:
Connect-MgGraph -Scopes "User.ReadWrite.All", "Directory.Read.All", "AuditLog.Read.All", "User.EnableDisableAccount.All”
This will trigger a login prompt; log in with an account with sufficient privileges to consent to your script, and be prepared for an MFA challenge (you are using MFA on all your privileged accounts, right?)
Step 2: Find the Ghosts
The trick here is figuring out which users have been inactive. Entra ID tracks the lastSignInDateTime property for each user, which tells us the last time they authenticated. We’ll use Get-MgUser to grab this data and filter out anyone who hasn’t logged in for, say, 60 days. Why 60? It’s a reasonable cutoff—long enough to account for vacationers or sabbaticals, short enough to keep the risk low. Adjust it if your org has different needs.
Here’s a small chunk of code to do just that:
$cutoffDate = (Get-Date).AddDays(-60) $inactiveUsers = Get-MgUser -All -Filter "usertype -eq 'member'" -Property DisplayName,UserPrincipalName,SignInActivity | Where-Object { $_.SignInActivity.LastSignInDateTime -lt $cutoffDate -or $_.SignInActivity.LastSignInDateTime -eq $null }
This grabs all users, pulls their display name, UPN, and sign-in activity, then filters for those whose last login is older than the cutoff date, or who’ve never logged in at all. In this example I included the -All switch to ensure we don’t miss anyone due to pagination; in larger environments, you may want to use pagination to avoid throttling and excessively long runtimes.
One thing you could do is pipe this to a table to get a quick view of who your inactive accounts belong to, like this:
$inactiveUsers | Select-Object DisplayName, UserPrincipalName, @{Name="LastSignIn";Expression={$_.SignInActivity.LastSignInDateTime}} | Format-Table
You might be surprised how many names pop up—old interns, departed staff, that one guy who swore he’d “get around to using Teams.” Now we’ve got our hit list.
Step 3: Disable the Accounts
Finding these accounts is only half the battle; now let’s neutralize the threat. I prefer to handle this by blocking their ability to sign in. You’ve no doubt seen some
Why not delete the accounts? Lots of reasons: mailbox and OneDrive retention, Office 365 license management, and auditing all might demand that you keep the underlying account object intact. Blocking sign-in makes it easy to restore access on the off-chance that someone yells, “Wait, I need that mailbox!”
Once you’ve gotten $inactiveUsers loaded with the set of users you want to disable, you just call Update-MgUser:
$params = @{ AccountEnabled = "false" } foreach ($user in $inactiveUsers) { Update-MgUser -UserId $user.Id -BodyParameter $params Write-Host "Disabled account: $($user.UserPrincipalName)" }
This loops through our list, sets AccountEnabled to $false, and confirms the action, and gives us a little confirmation. Run it, and those accounts are locked down faster than you can say “security breach.”
Step 4: Add Some Smarts
Disabling is great, but let’s not stop there. What if we want to skip certain accounts—like service accounts? For example, if you wanted to exclude accounts whose names started with “svc”, you could do something like the following:
$exceptions = @("svc-*") $inactiveUsers = $inactiveUsers | Where-Object { $_.UserPrincipalName -notlike $exceptions[0]}
Now those exceptions stay untouched. You could also log what you’re doing to a CSV for posterity, or do any other type of notification or tracking that you find necessary.
Step 5: Verify and Celebrate
Before you pop the champagne, check your work. In the Entra ID portal, spot-check a few of the newly disabled accounts under Users > All Users—look for the “Account enabled” column (and add it to the view if need be). Your newly modified accounts should show up with a status of “No.”
Wrapping Up
Inactive accounts aren’t glamorous, but they’re a real risk. With PowerShell, you’ve got a repeatable, hands-off way to keep your Microsoft 365 tenant lean and mean. This column was meant to be an illustrative example that you can customize for your own needs. For example, you could automate running the script at regular intervals, add reporting, filter out specific groups that you want to exempt from checking, and so on. This should get you started!