HackBlue

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.

9 min read · practice only with authorization

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.

Snippet
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.

Snippet
$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

  1. Find

    Search-ADAccount -AccountInactive with a 90-day window.

  2. Disable

    Disable, describe, and move to a holding OU.

  3. Hold

    Wait 30 to 90 days for any complaint.

  4. 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?