Use PowerShell to search for accounts in Active Directory that have gone stale!



Dear Microsoft and Active Directory Friends,


In a company/organization, employees come and go. This is completely normal and nothing out of the ordinary. But often, from a technical point of view, the Active Directory is somewhat forgotten and account cleanup is rarely or never done. This means there are outdated or no longer current accounts (ghost accounts). This is not a great situation from either a security or administrative perspective. In this article I would like to show you a possible way. But what exactly does "stale" accounts mean? Let me explain my definition to you:


1. Haven't logged in for X days
2. Hasn't logged in
3. Created at least X days ago


We can start first with the CMDLET "Search-ADAccount". This gives once a first result. The #tags are comments.

#Using Search-ADAccount
Search-ADAccount -AccountInactive -TimeSpan '90.00:00:00' -UsersOnly



How many accounts would there be?

(Search-ADAccount -AccountInactive -TimeSpan '90.00:00:00' -UsersOnly).count



But there are a lot of them. Now let's work with filters. We explicitly examine the "LastLogonTimeStamp" property. For an account that has never been logged in, no value is displayed.


#Using a filter
Get-ADUser "Freida Lazarus" -Properties LastLogonTimeStamp | Select-Object Name,LastLogonTimeStamp



An account that has been used for a login will show a value. This number represents a date, this is in FileTime format.



We can generate such value. For example, one hour in the past.

$LogonDate = (Get-Date).AddHours(-1).ToFileTime()



So, for example, we can check who has not logged in during the last hour. Of course we would not work with only one hour. But I'm going to keep working with this one hour so I can illustrate it to you better.


#If it is older than $LogonDate
$LogonDate = (Get-Date).AddHours(-1).ToFileTime()
Get-ADUser -Filter {LastLogonTimeStamp -lt $LogonDate}




Or in numbers it would be so many accounts.



Now we search for all accounts that do not have this value.


#If it doesn't have value
Get-ADUser -Filter {LastLogonTimeStamp -notlike "*"} -Properties LastLogonTimeStamp | Select-Object Name,LastLogonTimeStamp



But what if the user has not yet logged in because the account has just been created? Let's take a closer look at this as well.


#And if the account was created before $createdDate
$createdDate = (Get-Date).AddDays(-14)
Get-ADUser -Filter {Created -lt $createdDate} -Properties Created |
Select-Object Name,Created



Bringing all this together, we can determine what exactly "stale" means to us.


#Add them all together:
$filter = {
((LastLogonTimeStamp -lt $logonDate) -or (LastLogonTimeStamp -notlike "*"))
-and (Created -lt $createdDate)
Get-ADuser -Filter $filter | Select-Object SamAccountName



We can create a function from it.


Function Get-ADStaleUsers {
Param (
[datetime]$NoLogonSince = (Get-Date).AddDays(-90),
[datetime]$CreatedBefore = (Get-Date).AddDays(-14)
$NoLogonString = $NoLogonSince.ToFileTime()
$filter = {
((LastLogonTimeStamp -lt $NoLogonString) -or (LastLogonTimeStamp -notlike "*"))
-and (Created -lt $createdBefore)
Write-Host $filter
Get-ADuser -Filter $filter


# Usage



This way we can work with our own values. Moreover, we can delete the accounts directly afterwards or at least check with -WhatIf what exactly would be done!


Get-ADStaleUsers -NoLogonSince (Get-Date).AddDays(-30) -CreatedBefore (Get-Date).AddDays(-7) | Remove-ADUser -WhatIf



In the search result from above two Built-In accounts (e.g.: krbtgt) are displayed among others (this is how it turned out in the search). Such Built-In accounts should never be deactivated or even worse deleted, this has very negative effects on the function of the Active Directory. You should never delete user accounts directly. Instead, the accounts should be disabled for some time first. If the accounts were used for other purposes (unknown to you). You should also avoid setting user accounts as service accounts. This is not according to Microsoft "Best Practice". For service accounts, use the so-called group managed service accounts.


I realize that this was not necessarily spectacular. It was simply important for me to share my experience with you. Nevertheless, I hope that this article was helpful. Thank you for taking the time to read the article.

Best regards, Tom Wechsler


P.S. All scripts (#PowerShell, Azure CLI, #Terraform, #ARM) that I use can be found on github!

2 Replies

For anyone reviewing this and with a need to audit an AD environment not only for stale accounts, but also for stale passwords, stale computers, unsupported operating systems, and other such reports - please review my own project at , if it may provide any benefit to you or your organization. It is built upon many of the same principles and PowerShell cmdlets that Tom just covered here, but extends this into a suite of production-ready reports. (I don't mean to spam this article with my own solution, but only mean to share as I'm trying to help everyone I can get ahead of the potential security incidents otherwise waiting to happen within many environments due to such stale or orphaned accounts.)


Nice script, thanks !
I would add two exceptions to your script, one for "krbtgt", another for default AD administrator account ("administrator" in En-US) - if people start to disable/delete those because they think they are stale, it will backfire.