90 days
standard inactivity threshold for flagging a user account for review and disable in most compliance frameworks including CIS and NIST AC-2
30-90 days
recommended disable-before-delete monitoring window to catch hidden service or scheduled task dependencies before permanent account removal
> 5 years
typical age of non-expiring service account passwords found during AD audits, representing persistent credential exposure with no forced rotation
4625
Windows Security Event ID to monitor after disabling accounts, indicating a service or task is still attempting to authenticate with the disabled credential

SponsoredRetool

Retool's new app builder is where AI-generated code ships safely

Building apps with AI is easy. Getting them to production safely is another story.

Start building for free today

Every Active Directory environment accumulates stale accounts over time. Users leave the organization and their accounts are not fully deprovisioned. Service accounts are created for applications that are later decommissioned. Computer objects persist for servers that have been physically removed. The result is an expanding set of valid AD objects with valid credentials that provide no business value but represent real attack surface.

This is one of the most consistent findings in Active Directory security assessments: not because organizations do not know about stale accounts, but because the cleanup process is genuinely risky without a structured approach. Deleting an account that appears inactive but is actually used by a scheduled task or Windows service causes production outages. This guide covers the identification queries, the disable-before-delete safety sequence, and the ongoing automation that prevents account sprawl from rebuilding after a cleanup effort.

Identifying stale accounts: PowerShell queries

Finding stale accounts accurately requires querying the right AD attributes and understanding their replication behavior across domain controllers. The three most important attributes are lastLogonDate (replicated, may lag up to 14 days), lastLogon (non-replicated, requires checking every DC), and lastLogonTimestamp (replicated but intentionally updated only every 14 days to reduce replication traffic). For a practical baseline audit, lastLogonDate is sufficient for identifying accounts inactive for 90 or more days. The queries below cover the three highest-risk stale account categories: inactive user accounts, service accounts with non-expiring passwords (a Kerberoasting target when they also have SPNs), and computer accounts for systems that have been decommissioned. Each query includes filtering parameters to exclude known exception categories such as shared kiosk accounts, break-glass admin accounts, and seasonal employee OUs.

Stale user accounts: inactive for 90+ days

Complete query with exclusions for known exception categories: $threshold = (Get-Date).AddDays(-90); Get-ADUser -Filter {LastLogonDate -lt $threshold -and Enabled -eq $true} -Properties LastLogonDate, Department, Manager, PasswordNeverExpires, Description -SearchBase 'DC=corp,DC=example,DC=com' | Where-Object {$_.DistinguishedName -notmatch 'OU=ServiceAccounts|OU=SharedAccounts|OU=BreakGlass'} | Select-Object SamAccountName, Name, LastLogonDate, Department, Manager, PasswordNeverExpires, Description | Sort-Object LastLogonDate | Export-Csv -Path 'stale-users.csv' -NoTypeInformation. The Manager field helps identify account owners for confirmation before disabling. Review the CSV before taking action.

Service accounts with non-expiring passwords

Get-ADUser -Filter {PasswordNeverExpires -eq $true -and Enabled -eq $true} -Properties PasswordNeverExpires, PasswordLastSet, LastLogonDate, Description, ServicePrincipalNames | Select-Object SamAccountName, PasswordLastSet, LastLogonDate, Description, @{N='HasSPN';E={$_.ServicePrincipalNames.Count -gt 0}} | Sort-Object PasswordLastSet | Export-Csv service-accounts-non-expiring.csv. Sort by PasswordLastSet to find accounts whose passwords have not been changed in years. HasSPN=True indicates the account has a Service Principal Name registered, making it a Kerberoasting target. Prioritize these accounts for password rotation or migration to gMSA.

Stale computer accounts: offline for 180+ days

$computerThreshold = (Get-Date).AddDays(-180); Get-ADComputer -Filter {LastLogonDate -lt $computerThreshold -and Enabled -eq $true} -Properties LastLogonDate, OperatingSystem, OperatingSystemVersion, Description, IPv4Address | Select-Object Name, LastLogonDate, OperatingSystem, OperatingSystemVersion, Description, IPv4Address | Sort-Object LastLogonDate | Export-Csv stale-computers.csv. Cross-reference OperatingSystem to identify Windows Server 2008 and 2012 objects that are clearly decommissioned (those OS versions are end-of-life). Description field often contains server purpose notes from provisioning.

Privileged group membership audit

Stale accounts with privileged group membership are highest priority: (Get-ADGroupMember -Identity 'Domain Admins' -Recursive) + (Get-ADGroupMember -Identity 'Enterprise Admins' -Recursive) + (Get-ADGroupMember -Identity 'Schema Admins' -Recursive) | Get-ADUser -Properties LastLogonDate, Enabled | Where-Object {$_.LastLogonDate -lt (Get-Date).AddDays(-30) -or $_.Enabled -eq $false} | Select-Object SamAccountName, LastLogonDate, Enabled. Any disabled account or account with 30+ day inactivity in these groups is an immediate finding. Domain Admins should have fewer than 5 members in most environments, and each member should have active, documented justification.

Safe remediation sequence

The disable-before-delete sequence exists because accounts that appear inactive in lastLogonDate logs can still be in active use by services and scheduled tasks that authenticate non-interactively, without generating interactive logon events that update the lastLogonDate attribute. Disabling an account immediately reveals these hidden dependencies through Event ID 4625 authentication failures, while preserving the account object so that re-enabling it restores the dependency without rebuilding credentials. The three-phase process described here gives you a 30-to-90-day monitoring window to catch all authentication failures before permanent deletion. During that window, Windows Security Event Log records, Task Scheduler execution logs, and IIS application pool error logs are the primary evidence sources for discovering dependencies. Move disabled accounts to a dedicated OU immediately on disable to remove them from group policy scope and prevent GPO-applied access controls from continuing to function.

Phase 1: Disable and move (week 1)

For each account confirmed stale after cross-referencing with HR and application owners: disable the account and immediately move it to a dedicated disabled OU. PowerShell batch disable: Import-Csv stale-users.csv | ForEach-Object { Disable-ADAccount -Identity $_.SamAccountName; Move-ADObject -Identity (Get-ADUser $_.SamAccountName).DistinguishedName -TargetPath 'OU=DisabledPendingDeletion,DC=corp,DC=example,DC=com'; Set-ADUser $_.SamAccountName -Description ('Disabled $(Get-Date -Format yyyy-MM-dd): stale account, pending deletion') }. Moving to a separate OU removes the account from all group-policy-applied OUs and can remove group memberships if GPO-based access controls apply.

Phase 2: Monitor for authentication failures (days 1-90)

Create a PowerShell script that queries Security Event Logs across all DCs for Event ID 4625 (failed logon) with reason 'Account is disabled' (logon failure reason 0xC0000072 or 0xC000006E) for your disabled accounts. Run this daily and alert on any match: Get-WinEvent -ComputerName $dcList -FilterHashtable @{LogName='Security'; Id=4625} | Where-Object {$_.Properties[5].Value -in $disabledAccounts -and $_.Properties[9].Value -eq 'Account is disabled'}. Authentication failures after disabling indicate the account has an active dependency (service, scheduled task, application) that must be remediated before deletion.

Phase 3: Delete after 90 days with no authentication failures

After 90 days with no authentication failure events referencing a disabled account, it is safe to delete: Remove-ADUser -Identity username -Confirm:$false. For computer accounts: Remove-ADComputer -Identity computername -Confirm:$false. Before deleting, document the account's attributes (SID, group memberships, service principal names) in a decommission ticket for audit purposes. Some compliance frameworks (SOX, PCI DSS) require evidence that access was properly removed, and the ticket serves as that evidence. The SID is needed to resolve any file system permissions that referenced the account after deletion.

Free daily briefing

Briefings like this, every morning before 9am.

Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.

The bottom line

Active Directory stale account cleanup is a risk reduction activity with a clear, replicable process: find accounts using the PowerShell queries above, disable before deleting using the three-phase sequence, and monitor for authentication failures to catch hidden dependencies before they cause outages. After the initial cleanup, implement the ongoing automation to catch new stale accounts within 30-90 days of becoming inactive, tied into your HR offboarding workflow to prevent future accumulation. The time investment pays down one of the most consistently exploited attack surfaces in enterprise environments.

Frequently asked questions

How do I find all inactive user accounts in Active Directory?

Use PowerShell's Search-ADAccount or Get-ADUser with lastLogonDate filtering. For accounts inactive for 90+ days: Search-ADAccount -AccountInactive -TimeSpan 90:00:00:00 -UsersOnly | Where-Object {$_.Enabled -eq $true} | Select-Object Name, SamAccountName, LastLogonDate, DistinguishedName | Export-Csv inactive-users.csv. Note: lastLogonDate is replicated across DCs but can lag by up to 14 days; use lastLogon (non-replicated) for precision but check all DCs, or use lastLogonTimestamp (replicated, but updated only every 14 days by design). Filter out accounts that are legitimately inactive (seasonal employees, shared kiosk accounts) using an OU-based exclusion list.

How do I find stale computer accounts in Active Directory?

Computer accounts for decommissioned systems show lastLogonDate older than 90-180 days. PowerShell: Get-ADComputer -Filter {LastLogonDate -lt (Get-Date).AddDays(-180)} -Properties LastLogonDate, OperatingSystem, Description | Where-Object {$_.Enabled -eq $true} | Select-Object Name, LastLogonDate, OperatingSystem, Description | Export-Csv stale-computers.csv. Before disabling, cross-reference against your CMDB or asset inventory to confirm the machine is decommissioned rather than powered down but still valid. Server computers that are powered off for maintenance may appear stale in AD but should not be deleted.

What is the safe sequence to remove stale accounts without breaking production?

Follow the disable-before-delete sequence: Step 1: Disable the account (Set-ADUser -Identity username -Enabled $false). Step 2: Move the account to a dedicated 'Disabled Accounts' OU to remove it from all security groups and GPO scope while preserving the object. Step 3: Monitor for authentication failures referencing the disabled account in the Windows Security Event Log (Event ID 4625 with failure reason 'Account is disabled'). Step 4: Wait 30-90 days with no authentication failures. Step 5: Remove the account permanently (Remove-ADUser -Identity username -Confirm:$false). This sequence catches accounts that appear inactive in logs but have background services or scheduled tasks running under them.

How do I handle orphaned service accounts safely?

Service accounts are high-risk when orphaned: they often have non-expiring passwords, elevated permissions, and no human owner who would notice a password reset. Identify orphaned service accounts by: finding accounts with passwordNeverExpires = $true that have not logged on in 90+ days, and accounts in service account OUs whose description field references a decommissioned application. For each: contact the application team to confirm the service account is unused, review the account's group memberships and service configurations (Task Scheduler, Windows Services, IIS app pools) to confirm no running services authenticate as this account, disable the account, and after 30 days with no failures, remove it. Consider migrating all new service accounts to Group Managed Service Accounts (gMSA) to eliminate password management risk.

What is the risk of stale accounts that are never cleaned up?

Stale accounts with valid credentials represent persistent attack paths: attackers who compromise credentials via phishing, credential stuffing, or database breaches gain access to a valid account that no human actively monitors for suspicious activity. Password spray attacks specifically target accounts that have not been flagged for lockout because the password is set but never changed. Service accounts with 10-year-old passwords set before MFA requirements existed bypass MFA enforcement if the protocol (NTLM, Kerberos) does not support it. Computer accounts for decommissioned servers can be exploited for Silver Ticket attacks if the computer's Kerberos keys are known. Each stale object is an attack surface that provides no business value.

How do I build automated detection for new stale accounts going forward?

Create a scheduled PowerShell script that runs weekly and reports accounts newly crossing the inactivity threshold. Schedule the script via Windows Task Scheduler or as a Lambda function if you use Microsoft Entra ID Sync. Pipe the output to a Microsoft Teams or Slack channel via webhook, or to a ServiceNow/Jira ticket for the account owner's manager. Also implement a joiner-mover-leaver process integration: when HR terminates an employee in your HR system (Workday, SAP, BambooHR), trigger an automatic account disable via Microsoft Entra ID Lifecycle Workflows or a custom SCIM-based provisioning flow, eliminating the dependency on manual IT action.

Can I automate the cleanup process entirely without manual review?

Partial automation is safe; full automation is risky without careful scoping. Safe to automate: moving accounts that have been disabled for 90+ days with no authentication failures to a 'pending deletion' OU, and generating deletion approval tickets in your ITSM system. Not safe to automate without scoping: automatically disabling accounts based solely on lastLogonDate without excluding exceptions (shared accounts, break-glass accounts, automation accounts that authenticate non-interactively). The risk is breaking a business-critical process that authenticates as an AD account you thought was inactive. Start with human-in-the-loop for the disable step, automate the 90-day monitoring and deletion approval workflow after confidence is established.

Sources & references

  1. Microsoft AD Account Management Best Practices
  2. PowerShell Get-ADUser Inactive Accounts
  3. CIS Microsoft Windows Server 2022 Benchmark
  4. NIST SP 800-53 AC-2: Account Management

Free resources

25
Free download

Critical CVE Reference Card 2025–2026

25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.

No spam. Unsubscribe anytime.

Free download

Ransomware Incident Response Playbook

Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.

No spam. Unsubscribe anytime.

Free newsletter

Get threat intel before your inbox does.

50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.

Unsubscribe anytime. We never sell your data.

Eric Bang
Author

Founder & Cybersecurity Evangelist, Decryption Digest

Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.

Black Hat Giveaway

Win a $2,495 Black Hat pass.

Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.

Joins Decryption Digest daily briefing. Unsubscribe anytime.

Giveaway: Black Hat USA 2026 Full-Access Pass ($2,495 value)

Details →
Daily Briefing

Subscribe to enter the giveaway

Every subscriber is automatically entered. You also get daily threat intel every morning: zero-days, ransomware, and nation-state campaigns. Free. No spam.

Already subscribed? You're already entered.

Giveaway

Win a $2,495 Black Hat USA 2026 pass.