Tools · Active Directory defense · 12 / 23
Find and clean up stale user and computer accounts in Active Directory
Use lastLogonTimestamp and Search-ADAccount to find inactive accounts, understand the replication lag, and disable before you delete.
Why stale accounts are risky
Accounts that nobody uses still authenticate. A former employee’s account, a contractor account that was never disabled, or a computer account for a laptop that was recycled can all be used by someone who learns or guesses the credentials, and nobody will notice the logon because nobody expects one.
Stale accounts also clutter reviews, so real problems are harder to see.
Which timestamp to trust
lastLogon is per domain controller and not replicated, so you would need to query every DC. lastLogonTimestamp is replicated but only updated when the previous value is older than about 9 to 14 days. For finding accounts inactive for 90 days, lastLogonTimestamp is accurate enough and easy. PowerShell exposes it as LastLogonDate.
Search-ADAccount -AccountInactive -TimeSpan 90 uses it, and -UsersOnly or -ComputersOnly filters the type. Also check pwdLastSet, because an account whose password has not changed in years is risky even if it logs on.
Search-ADAccount -AccountInactive -TimeSpan 90.00:00:00 -UsersOnly | Where Enabled | Select Name, LastLogonDate, DistinguishedName Search-ADAccount -AccountInactive -TimeSpan 90.00:00:00 -ComputersOnly | Where Enabled | Select Name, LastLogonDate Get-ADUser -Filter 'Enabled -eq $true' -Properties PasswordLastSet | Where PasswordLastSet -lt (Get-Date).AddYears(-2) | Select Name, PasswordLastSet
Lab: a safe cleanup workflow
Create a Disabled Accounts OU in the lab. Run the inactive search, then for each result disable the account, add a description with the date and reason, and move it to that OU. After a holding period such as 30 to 90 days with no complaints, delete it. This order gives you a recovery path if an account was in fact used by a quarterly job.
Enable the AD Recycle Bin before deleting anything, so deleted objects can be restored with their attributes and group memberships.
$ou='OU=Disabled Accounts,DC=lab,DC=internal'
Search-ADAccount -AccountInactive -TimeSpan 90.00:00:00 -UsersOnly | Where Enabled | ForEach-Object {
Disable-ADAccount $_
Set-ADUser $_ -Description "Disabled $(Get-Date -f yyyy-MM-dd): inactive 90d"
Move-ADObject $_.DistinguishedName -TargetPath $ou }Fix the process, not only the data
Stale accounts come from leaver processes that do not reach IT. Connect account disablement to the HR system so accounts are disabled on the last day, and set expiration dates on contractor accounts at creation.
Exclude service accounts carefully: some log on rarely but are essential. Tag them with an owner so the review can confirm them instead of guessing.
Diagram
Disable, hold, delete
- Find
Search-ADAccount -AccountInactive with a 90-day window.
- Disable
Disable, describe, and move to a holding OU.
- Hold
Wait 30 to 90 days for any complaint.
- Delete
Delete with the Recycle Bin enabled.
Check yourself
1. Why is lastLogon hard to use across a domain?
2. What is the safest first action for an inactive account?
3. Which feature lets you restore a deleted object with its attributes?