Stale Active Directory accounts are enabled identities that nobody uses and nobody watches, which makes them ideal for an attacker who has guessed, bought or cracked a password. Most domains collect them steadily: leavers whose accounts were never disabled, test users, computers that were reimaged under a new name, and servers that were switched off but never removed from the directory. This runbook shows how to find stale users and computers with the attributes Active Directory already maintains, how to read those attributes correctly, which accounts to exclude, and how to disable, quarantine and delete safely with a tested way back. It is for AD administrators and identity teams running a one-off clean-up or a recurring hygiene process. For the cross-platform policy view, see Orphaned and Dormant Accounts.
This is an independent guide from NHI Mgmt Group, based on Microsoft's public documentation as of November 2026. It is not sponsored or reviewed by Microsoft. Confirm cmdlet behaviour against Microsoft's current documentation before changing production accounts.
Key takeaways
- Use lastLogonTimestamp for discovery. It is the replicated logon attribute, surfaced in PowerShell as
LastLogonDate, but it is only updated when the stored value is older thanmsDS-LogonTimeSyncInterval, so it can lag by up to about two weeks. - Corroborate with password age. Domain-joined computers change their machine password every 30 days by default, so a computer with an old
pwdLastSetas well as an old logon is very likely gone. - Exclude the accounts that never log on normally. krbtgt, trust accounts, cluster name objects, gMSAs and break-glass accounts need their own handling, not a bulk disable.
- Disable and move before you delete. A disabled account in a quarantine OU can be re-enabled in seconds; a deleted one depends on the AD Recycle Bin being enabled beforehand.
- Turn it into a schedule. A monthly query with a fixed threshold, an exception register and two metrics keeps the count low after the first clean-up.
When you need this
- Reducing valid account abuse: enabled but unused domain accounts are a direct route for ATT&CK T1078.002 Domain Accounts. The Colonial Pipeline ransomware attack began with a VPN account that was no longer in use but still worked.
- Audit findings: NIST SP 800-53 AC-2(3) requires disabling accounts after a defined inactivity period, and CIS Control 5.3 sets a 45-day expectation for dormant accounts.
- Posture tools: Microsoft Defender for Identity's "Remove stale Active Directory accounts" assessment flags user accounts with no logons in the past 90 days, and a separate "Stale service accounts" assessment covers service accounts.
- Before a migration, tiering or licence review: moving or synchronising stale accounts to Entra ID, or licensing them, wastes effort and widens exposure.
Before you start
Which attribute tells you what
| Attribute | PowerShell property | Replicated | Use it for | Limit |
|---|---|---|---|---|
lastLogonTimestamp | LastLogonDate | Yes | Domain-wide stale discovery | Updated only when older than the sync interval; first update after raising the functional level is 14 days minus a random percentage of 5 days |
lastLogon | lastLogon | No, per domain controller | Confirming a single candidate | Must query every DC and take the latest value |
pwdLastSet | PasswordLastSet | Yes | Computers: machine password rotation; users: password change | Users with "password never expires" may have old values and still be active |
whenCreated | whenCreated | Yes | Never-used accounts (no logon value, created long ago) | A new account has no logon value yet; give it a grace period |
userAccountControl | Enabled, PasswordNeverExpires | Yes | Filtering enabled accounts, spotting risky flags | Disabled accounts are already contained; delete them on schedule |
Microsoft's schema documentation states that at logon the domain controller reads lastLogonTimestamp and updates it only if it is older than the current time minus msDS-LogonTimeSyncInterval. That is why it is right for finding accounts unused for 60 or 90 days and wrong for questions such as "did this person log on yesterday".
Also note what does not update it. Authentication that never reaches a domain controller, such as cached logons on a laptop that is off the corporate network, or an account used only by an application that authenticates elsewhere, can make an active account look stale. Accounts synchronised to Entra ID may be used in the cloud only; check Entra sign-in activity before disabling a synchronised user.
Access, thresholds and safety nets
- Read access is enough for discovery. Run it from an admin workstation with the Active Directory PowerShell module.
- Check the AD Recycle Bin. It requires a Windows Server 2008 R2 or higher forest functional level, only protects objects deleted after it is enabled, and Microsoft states that enabling it is irreversible. With it, restored objects keep their group memberships; without it, a deleted account comes back stripped.
- Agree thresholds: a common pattern is 90 days for users and computers, with longer periods for known seasonal use. Record the threshold in policy, not in the script.
- Agree communication: HR leaver data for users, CMDB or endpoint management data for computers, and a named contact for each OU.
Step-by-step
Steps 1 to 4 are read-only. Steps 5 and 6 are reversible. Step 7 is destructive.
- Find inactive users.
Search-ADAccountsearches for accounts that have not logged on within a time span:
Microsoft notes thatSearch-ADAccount -AccountInactive -TimeSpan 90.00:00:00 -UsersOnly | Where-Object { $_.Enabled } | Select-Object SamAccountName, LastLogonDate, DistinguishedName-AccountInactiveworks only at Windows Server 2003 domain functional level or higher. If you want more properties, useGet-ADUserwith a date comparison:$cutoff = (Get-Date).AddDays(-90) Get-ADUser -Filter {Enabled -eq $true -and LastLogonDate -lt $cutoff} ` -Properties LastLogonDate, PasswordLastSet, whenCreated, PasswordNeverExpires, Description, Manager - Find users that have never logged on. Accounts with no
lastLogonTimestampdo not match a "less than" comparison. Query them separately and allow a grace period for new starters:Get-ADUser -Filter {Enabled -eq $true} -Properties lastLogonTimestamp, whenCreated | Where-Object { -not $_.lastLogonTimestamp -and $_.whenCreated -lt $cutoff } - Find inactive computers and corroborate. The Domain member: Maximum machine account password age policy defaults to 30 days, so a working domain member normally updates
pwdLastSetabout monthly. A computer with both an old logon and an old password is a strong candidate:
Compare the list with your endpoint management and CMDB records. A device that reports to endpoint management but looks stale in AD may be off network for long periods.Get-ADComputer -Filter {Enabled -eq $true -and LastLogonDate -lt $cutoff -and PasswordLastSet -lt $cutoff} ` -Properties LastLogonDate, PasswordLastSet, OperatingSystem, Description, ManagedBy - Remove exclusions and classify. Take these out of the bulk list and handle them individually:
- krbtgt and built-in accounts: never disable krbtgt. The built-in Administrator should be secured and monitored, not swept up.
- Domain controllers and trust accounts: domain controllers carry
SERVER_TRUST_ACCOUNT(0x2000); a stale DC object needs metadata clean-up, not a disable. Trust accounts (names ending in$under Users) belong to the trust. - Cluster name objects and virtual computer objects: owned by failover clusters; disabling them breaks the cluster.
- Managed service accounts and gMSAs: review through their own process (see the Group Managed Service Accounts (gMSA) Guide).
- Service accounts with SPNs: follow How to find and retire Active Directory service accounts with SPNs, which covers owner tracing and service dependencies.
- Break-glass and disaster recovery accounts: unused by design; keep them on the exception register with a test date.
- Disable and quarantine (reversible). Record the account's original OU, group memberships and description first, then:
UseDisable-ADAccount -Identity <sAMAccountName> Set-ADUser -Identity <sAMAccountName> -Description "Disabled 2026-11-11 stale review CHG12345" Move-ADObject -Identity "<distinguishedName>" -TargetPath "OU=Quarantine,DC=corp,DC=example"Set-ADComputerfor computers. Restrict the quarantine OU so only the identity team can re-enable objects, and block GPO inheritance there if your design depends on OU-linked policies. - Hold and watch. Keep disabled accounts for an agreed period, commonly 30 to 90 days. Monitor for logon failures against disabled accounts (event 4625 on domain controllers, or Kerberos pre-authentication failures, event 4771), which tell you something still uses the account. Investigate before re-enabling: an unknown process using a leaver's credentials is a security incident, not a support ticket.
- Delete (destructive). After the hold period, delete with the Recycle Bin enabled:
Computers with child objects (for example some cluster or Hyper-V objects) needRemove-ADUser -Identity <sAMAccountName> Remove-ADComputer -Identity <computerName>Remove-ADObject -Recursive; check what the children are before using it. Keep the pre-change record with the change ticket.
Verify it worked
- Directory state: rerunning steps 1 to 3 returns only exceptions on the register.
- Audit trail: domain controllers record event 4725 (user account disabled), 4726 (user account deleted) and 4743 (computer account deleted) for each change, with the operator account.
- No breakage: the service desk sees no rise in "account disabled" calls from active staff, and application owners report no failed jobs during the hold period.
- Posture tools: Defender for Identity's stale accounts assessment shows fewer exposed entities.
Rollback and common errors
| Situation | Fix |
|---|---|
| An active user was disabled | Enable-ADAccount, move back to the original OU, and add the cause (cloud-only use, long leave) to the exception logic |
| A disabled computer cannot reach the domain | Re-enable it. If it has been offline long enough for its secure channel to fail, it may need to be rejoined to the domain |
| A needed account was deleted, Recycle Bin enabled | Get-ADObject -Filter 'isDeleted -eq $true -and Name -like "*name*"' -IncludeDeletedObjects | Restore-ADObject; memberships return with it |
| A needed account was deleted, no Recycle Bin | Authoritative restore from backup or recreate the account; the new account has a new SID, so permissions and group memberships must be re-granted |
| Search results include accounts that are clearly active | Check whether logons happen against a domain controller at all, and whether the sync interval was changed; confirm with lastLogon on every DC |
| Accounts reappear in the next report | Joiner-mover-leaver process is not disabling accounts; fix the process, not just the report |
Choosing thresholds and approach
| Account type | Typical threshold | Corroboration | Notes |
|---|---|---|---|
| Standard users | 45 to 90 days | HR status, Entra sign-in activity | Disable immediately on leaver signal regardless of activity |
| Privileged users | Shorter, often 30 to 45 days | Privileged group membership, PAM records | Defender for Identity separately flags dormant entities in sensitive groups at 180 days; do not wait that long |
| Workstations | 90 days | pwdLastSet, endpoint management last check-in | Remote and seasonal devices need longer |
| Servers | 60 to 90 days, with owner sign-off | CMDB, monitoring, hypervisor inventory | Disaster recovery servers may be powered off by design |
| Service and test accounts | Owner-defined | SPNs, scheduled tasks, application logs | Route to the service account process |
Small domain: a single script run monthly, a spreadsheet exception register and manual review of each result is enough. Large or multi-domain forest: run per domain, feed results into your IGA or ticketing tool so each OU owner certifies their list, and automate the disable and move after a notification period. Automated deletion should still wait for a human check of anything marked unknown.
Where identity fits
Computer accounts are machine identities with passwords, group memberships and sometimes delegation rights; a stale server object can still hold permissions on file shares or certificate templates. User-type service accounts are non-human identities that often fail every heuristic above because they never "log on" interactively yet are in use. Treat both as identities with owners and lifecycles. The NHI Dormancy Detection guide covers activity signals for non-human accounts across platforms.
Detection and measurement
- Enabled accounts past threshold: users and computers counted separately; trend down after the first clean-up and stay near zero.
- Never-logged-on accounts older than the grace period: target zero.
- Median days from HR leaver date to AD disable: target same day.
- Logon attempts against disabled accounts: each one investigated and closed.
- Exceptions past review date: target zero.
- Re-enable rate after quarantine: low; a high rate means the thresholds or exclusions need work.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST SP 800-53 Rev. 5 | AC-2 Account Management; AC-2(3) Disable Accounts | Disable accounts after a defined inactivity period; manage the account lifecycle |
| NIST CSF 2.0 | PR.AA-01; ID.AM-01 | Identities managed; hardware inventory kept accurate (computer objects) |
| CIS Controls v8.1 | 5.1 Establish and Maintain an Inventory of Accounts; 5.3 Disable Dormant Accounts | Inventory and 45-day dormant account expectation |
| ISO/IEC 27001:2022 Annex A | A.5.16 Identity management; A.5.18 Access rights | Removal of identities and access no longer needed |
| MITRE ATT&CK | T1078.002 Domain Accounts | Fewer dormant accounts available for valid account abuse |
Common failure patterns
- Querying
lastLogonon one domain controller: it is not replicated, so accounts that authenticate against other DCs look stale. - Forgetting never-logged-on accounts: date comparisons skip empty values, and these are often the oldest, most forgotten accounts.
- Bulk-disabling cluster and trust objects: a stale-looking cluster name object or trust account taken down by a script causes outages that end the clean-up programme.
- Deleting without the Recycle Bin: one wrong deletion and the account returns with a new SID and none of its access.
- Ignoring cloud-only use: synchronised users who only use Microsoft 365 can look idle in AD.
- Cleaning the report, not the process: if leavers are not disabled at exit, the next report is as long as the first.
Practitioner checklist
- Confirm the AD Recycle Bin is enabled and the forest level supports it.
- Set and document thresholds per account type.
- Query inactive users, never-logged-on users and inactive computers using
LastLogonDate. - Corroborate computers with
PasswordLastSetand endpoint inventory, users with HR and Entra data. - Exclude krbtgt, DCs, trusts, cluster objects, gMSAs, SPN accounts and break-glass accounts.
- Record original OU and memberships, then disable and move to a restricted quarantine OU.
- Monitor logon failures against disabled accounts during the hold period.
- Delete after the hold and keep the record with the change.
- Schedule the queries monthly and track the metrics.
Standards and references
- Microsoft: Search-ADAccount
- Microsoft: Last-Logon-Timestamp attribute
- Microsoft: Domain member: Maximum machine account password age
- Microsoft: Active Directory Recycle Bin
- Microsoft: Defender for Identity accounts security posture assessments
- NIST: SP 800-53 Rev. 5
- CIS: Critical Security Controls v8.1
- MITRE ATT&CK: Valid Accounts: Domain Accounts
Related NHI Mgmt Group resources: Orphaned and Dormant Accounts · How to find and retire Active Directory service accounts with SPNs · NHI Dormancy Detection · Active Directory Security Checklist · How to Find unconstrained delegation in active directory