IT Operations

Your Active Directory Has Ghost Accounts. Here Is How I Find and Remove Them.

By Felix Maru · July 25, 2026 · 7 min read

Six months into my role as ICT Officer at a large international NGO, I ran the first proper Active Directory audit the organization had ever done. Three hundred staff across 31 counties, and not a single access review on record. What I found took three full days to clean up: enabled accounts for people who had left over a year earlier, contractors whose terms had expired, and service accounts tied to systems that no longer existed. Roughly one in six enabled accounts in that directory had not authenticated in over 90 days.

That experience changed how I think about user account hygiene. The NGO had an offboarding checklist. HR filled it in. IT signed off. And still, those accounts sat open. The gap was not in the intention, it was in the verification. Nobody had ever gone back to confirm the directory matched reality.

Here is the audit I now run every quarter, and the four-step process that keeps the list short.

The Ghosts in Every Directory

A ghost account is any Active Directory account that remains enabled after it should have been disabled. Three categories show up in every audit, without exception:

The common thread is not negligence. It is the absence of a feedback loop. Offboarding says "disable the account." Nobody checks whether it happened. Stale accounts accumulate quietly, a few every month, until the directory no longer reflects who actually has access to anything.

Why This Is a Security Problem, Not Just a Housekeeping Task

License waste is real. An unused M365 seat still bills. But the larger issue is access risk, and it is more immediate than most IT teams treat it.

An enabled account with a valid password is a live credential. Former employees sometimes remember their passwords. Attackers who obtain credentials through phishing, credential stuffing, or data breaches try them against every service they can reach. If the account is still active and the password was weak or reused, the door opens without any warning.

The verification question is not "did this person leave?" It is "can this account still authenticate?" Those are different questions, and most orgs only ever ask the first one.

Compliance frameworks reinforce this. ISO 27001 and SOC 2 Type II both require periodic user access reviews. Quarterly is the standard most auditors expect. Annual is the floor. Never is an audit finding waiting to happen.

An enabled account for someone who left 14 months ago is not an oversight. It is an open door with a key nobody changed.

The Audit Command (Run This First)

Before you can clean up, you need the list. Open PowerShell as a domain admin and run this:

Search-ADAccount -AccountInactive -TimeSpan 90.00:00:00 -UsersOnly |
  Where-Object {$_.Enabled -eq $true} |
  Select-Object Name, SamAccountName, LastLogonDate, DistinguishedName |
  Export-Csv -Path "C:\stale_accounts_audit.csv" -NoTypeInformation

This finds every enabled user account that has not authenticated in 90 or more days and exports it as a CSV. Open the file and triage it before touching anything. A few things to look for:

On the first audit, expect the list to be longer than comfortable. That is normal. At the NGO, the first pass pulled over 40 accounts that should not have been active. At a top US-based company I work with, a similar first-run audit on a smaller directory found accounts for several contractors whose terms had ended months prior.

The Four-Step Cleanup Process

Run audit script Disable + move to staging OU Notify last manager 30-day grace period Delete + reclaim M365 license
Quarterly AD cleanup: automated steps in blue, the human review checkpoint in amber.

After triaging the export, I work through four steps in order. I do not skip any of them, including on the accounts I am confident about.

Step 1: Disable, never delete immediately. An account that looks stale might belong to someone on extended leave, a sabbatical, a parental leave longer than 90 days, or a system that runs a quarterly batch job. Disabling is reversible; deleting is not. The command:

Disable-ADAccount -Identity "samAccountName"

Step 2: Move to a staging OU. I keep a "Disabled Users" OU in AD for exactly this purpose. Moving the account out of its original OU (Sales, IT, Finance) signals clearly that it is no longer active, removes it from most group policy applications, and makes the cleanup visible in the directory structure. The command:

Move-ADObject -Identity "CN=Name,OU=Finance,DC=domain,DC=com" `
  -TargetPath "OU=Disabled Users,DC=domain,DC=com"

Step 3: Notify the account owner's last manager. Before permanent deletion, I send a brief message to the person's last reporting manager (pulled from the AD manager attribute). The message is direct: this account has been disabled and will be deleted in 30 days. Reply if it is still needed. Almost nobody replies. When someone does reply, it is almost always a service account that was missed during triage, and the conversation is useful.

Step 4: Delete after 30 days and revoke M365 licenses. Once the grace period passes with no response, delete the AD account and explicitly remove the Microsoft 365 license assignment. These are two separate actions. Disabling or even deleting the AD account does not automatically free the M365 seat.

The M365 License Piece

Disabling an account in Active Directory stops authentication for on-premises and synced services. It does not release the Microsoft 365 license. The license keeps billing until you remove it explicitly.

In the M365 Admin Center: go to Users, Active Users, select the user, open the Licenses and Apps tab, uncheck the license, and save. For a small batch, this takes a few minutes.

For larger cleanups or if you prefer the command line:

Update-MgUser -UserId "user@domain.com" -AssignedLicenses @{
  RemoveLicenses = @("paste-sku-id-here")
  AddLicenses = @()
}

You can get the SkuId for each license with Get-MgSubscribedSku | Select SkuPartNumber, SkuId.

At a typical mid-size organization, a first proper cleanup pass reclaims enough seats to produce a visible line item on the next M365 billing cycle. At the NGO, the first audit paid back its time investment purely on license recovery before we factored in the security benefit.

Make It Quarterly, Not Annual

The real leverage is not the one-time cleanup. It is the habit.

Once you run the first pass, the list stays manageable if you review it every quarter. Four times a year, the first week of each quarter: run the script, triage the output, process the disables, send manager notifications. When you do this consistently, you are dealing with accounts from the last three months, not accounts from the last 14 months.

The first run takes a day or two, depending on how long it has been since the last review. By the fourth quarter, the same task takes under two hours. That is the compounding benefit of a consistent routine over a periodic scramble.

I keep a recurring calendar event on the first Monday of each quarter with three things noted: the script path, the CSV export location, and a reminder to pull the M365 license reclaim report afterward. That is the entire system. It does not require a ticketing workflow, a dedicated tool, or a project kickoff. It requires one person, a domain admin session, and 90 minutes every three months.

A ghost account that existed for three months is a gap. One that existed for 14 months is a policy failure. The difference is whether someone checked.

If you want to talk through access hygiene, AD structure, or IT operations, reach me at felixmaru.com.

Share in 𝕏

Comments