Finding disabled user accounts in Active Directory Domain Services

I f  Get-ExecutionPolicy returns restricted you are3 not able to run powershell scripts :

PS C:\> c:\locatedisabledusers.ps1
File C:\locatedisabledusers.ps1 cannot be loaded because the execution of scripts is disabled on this system. Please se
e “get-help about_signing” for more details.
At line:1 char:27
+ c:\locatedisabledusers.ps1 <<<<
+ CategoryInfo : NotSpecified: (:) [], PSSecurityException
+ FullyQualifiedErrorId : RuntimeException

PS C:\>

You need to Set-ExecutionPolicy RemoteSigned or Set-ExecutionPolicy AllSigned
The script I wrote to find disabled user accounts in my domain uses the Win32_UserAccount WMI class. It is a very easy class to use in Windows PowerShell, and it works well for small domains. The script listed here also appeared in the Windows Server 2008 Security Resource Kit that was part of the Windows Server 2008 Resource Kit (I wrote all 150 Windows PowerShell scripts that appear in those volumes).

For other articles about searching Active Directory using Windows PowerShell, see this collection of Hey, Scripting Guy! Blog posts.


) #end param
# Begin Functions
function funHelp()
$descriptionText= `
NAME: LocateDisabledUsers.ps1
Locates disabled users a local or remote domain by
supplying the netbios name of the domain.
The script can query multiple domains by accepting
more than one value for the -domain parameter. The
script also supports using -whatif to prototype the
command prior to execution
-domain the domain or domains to query for locked
out users. Note: this is the netbios domain name.
Does not accept fully qualified domain name. For
example: nwtraders is correct, is
-query executes the query
-whatif prototypes the command.
-help prints help description and parameters file
-examples prints only help examples of syntax
-full prints complete help information
-min prints minimal help. Modifies -help
“@ #end descriptionText
$examplesText= `
Displays an error missing parameter, and calls help
LocateDisabledUsers.ps1 -query
Queries disabled user accounts. The domain queried is
the local logged on users domain from the machine
that launched the script
LocateDisabledUsers.ps1 -domain nwtraders, contoso -query
Queries disabled user accounts in the nwtraders domain and
in the contoso domain. The script is executed locally
LocateDisabledUsers.ps1 -query -domain nwtraders -whatif
Displays what if: Perform operation locate disabled
users from the nwtraders domain.The query will execute
from the localhost computer
LocateDisabledUsers.ps1 -help
Prints the help topic for the script
LocateDisabledUsers.ps1 -help -full
Prints full help topic for the script
LocateDisabledUsers.ps1 -help -examples
Prints only the examples for the script
LocateDisabledUsers.ps1 -examples
Prints only the examples for the script
“@ #end examplesText
$remarks = `

For more information, type: $($MyInvocation.ScriptName) -help -full
” #end remarks
if($examples) { $examplesText ; $remarks ; exit }
if($full) { $descriptionText; $examplesText ; exit }
if($min) { $descriptionText ; exit }
$descriptionText; $remarks
} #end funHelp function
function funline (
$char = “=”,
$sColor = “Yellow”,
$uColor = “darkYellow”,
$local:helpText = `
Funline accepts inputs: -strIN for input string and -char for seperator
-sColor for the string color, and -uColor for the underline color. Only
the -strIn is required. The others have the following default values:
-char: =, -sColor: Yellow, -uColor: darkYellow
funline -strIN “Hello world”
funline -strIn “Morgen welt” -char “-” -sColor “blue” -uColor “yellow”
funline -help
} #end funline help
$strLine= $char * $strIn.length
Write-Host -ForegroundColor $sColor $strIN
Write-Host -ForegroundColor $uColor $strLine
} #end funLine function
Function funWhatIf()
foreach($sDomain in $Domain)
“what if: Perform operation locate disabled users from the $sDomain domain”
} #end funWhatIf
Function funQuery()
Foreach($sDomain in $domain)
$strOutput = Get-WmiObject -Class win32_useraccount -filter `
“domain = “”$sDomain”” AND disabled = ‘true'”
$count = ($strOutput | Measure-Object).count
If($count -eq 0)
funline -scolor green -ucolor darkyellow -strIN `
“There are no disabled accounts in the $sDomain”
} #end if
funline -scolor red -ucolor darkyellow -strIN `
“$count disabled in the $sDomain domain — List follows:”
format-table -property name, sid -AutoSize -inputobject $strOutput
} #end else
} #end foreach
} #end funquery
# Entry Point
if($help) { funhelp }
if($examples) { funhelp }
if($full) { funhelp }
if($whatif) { funWhatIf }
if(!$query) { “missing parameter” ; funhelp }
if($query) { funQuery }

When the LocateDisabledUsers.ps1 script runs, the output appears that is shown in the following image.

Image of output of LocateDisabledUsers.ps1 script

Later in my Windows PowerShell 2.0 Best Practices book, I decided to update the script, and I used the [adsiSearcher] type accelerator. The output is not exactly the same because I do not return the user’s SID. In addition, I display both the disabled user accounts and the accounts that are not disabled, but the concept is basically the same. The FindDisabledUserAccounts.ps1 script is shown here.


#Requires -Version 2.0
$filter = “(&(objectClass=user)(objectCategory=person))”
$users = ([adsiSearcher]$Filter).findall()
foreach($suser in $users)
“Testing $($“”distinguishedname””))”
$user = [adsi]”LDAP://$($“”distinguishedname””))”
if($uac -band 0x2)
{ write-host -foregroundcolor red “`t account is disabled” }
{ write-host -foregroundcolor green “`t account is not disabled” }
} #foreach

When the FindDisabledUserAccounts.ps1 script runs, the output appears that is shown in the following image.

Image of output of FindDisabledUserAccounts.ps1 script

Now with access to the Active Directory module from Windows Server 2008 R2 and with my desktop running Windows 7 with the RSAT tools installed, I can use the Search-ADAccount cmdlet. I have not turned this into a script yet because it is basically a single command from a cmdlet.

For a good introduction to using the Active Directory Domain Services Windows 2008 R2 cmdlets, see Hey, Scripting Guy! What’s Up with Active Directory Domain Services Cmdlets?

At some point, I probably will add support for inputting alternative credentials and support for other domains. But for now, it is a single command:

Search-ADAccount -AccountDisabled -UsersOnly | Format-Table name, sid -AutoSize

When the Search-ADAccount command runs, the output appears that is shown in the following image.

Image of output of Search-ADAccount command

From a performance standpoint, which of the three different approaches is best? Let us begin with the WMI Win32_UserAccount script.

See this link for a collection of Hey, Scripting Guy! articles that talk about testing the performance of various Windows PowerShell scripts.

When I use the Measure-Command cmdlet, it takes 571 milliseconds to return. Keep in mind that my AD is small, and I have two domain controllers on my switched gigabyte network.

Windows PowerShell
Copyright (C) 2010 Microsoft Corporation. All rights reserved.
PS C:\> measure-command { C:\data\Windows2008ResKit\WS08SecurityResKit\LocateDisabled
Users.ps1 -domain nwtraders -q }
15 disabled in the nwtraders domain — List follows:
Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 571
Ticks : 5719349
TotalDays : 6.61961689814815E-06
TotalHours : 0.000158870805555556
TotalMinutes : 0.00953224833333333
TotalSeconds : 0.5719349
TotalMilliseconds : 571.9349
PS C:\>

I then rebooted my workstation and ran the ADSISearcher script. The reason for rebooting my workstation is to avoid any kind of false performance boosts from ADSI caching. When the script runs, it takes 690 milliseconds to complete. One of the things to keep in mind is that the FindDisabledUserAccounts.ps1 script writes to the Windows PowerShell console, which will increase the amount of time to run the script.

My workstation has solid-state hard drives, and therefore boots really quickly. In fact, if I turn on my workstation and my laptop at the same time, I can be logged into the domain and have Word started before I am even prompted to type in the Windows BitLocker Drive Encryption PIN for my laptop (a prompt that comes up before the Windows 7 even begins to load). To be perfectly honest, if I were to do the testing on my laptop, I probably would forego the additional reboots.

PS C:\> Measure-Command { C:\data\ScriptingGuys\2010\HSG_8_16_10\FindDisabledUserAcco
unts.ps1 }
account is disabled
account is disabled
account is not disabled
account is not disabled
account is disabled
account is disabled
account is disabled
account is disabled
account is disabled
account is disabled
account is disabled
account is disabled
account is disabled
account is disabled
account is disabled
account is disabled
account is disabled
account is not disabled
account is not disabled
account is not disabled
account is not disabled
account is not disabled
account is not disabled
account is not disabled
account is not disabled
Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 690
Ticks : 6904643
TotalDays : 7.9914849537037E-06
TotalHours : 0.000191795638888889
TotalMinutes : 0.0115077383333333
TotalSeconds : 0.6904643
TotalMilliseconds : 690.4643
PS C:\>

I reboot my workstation one more time, and after loading the Active Directory module, I run the Search-ADAccount cmdlet command and use theMeasure-Command cmdlet to time the operation. The results show that it took 81 milliseconds to complete, making it the fastest of the three commands:

Windows PowerShell
Copyright (C) 2010 Microsoft Corporation. All rights reserved.
PS C:\> Import-Module ac*
PS C:\> measure-command{Search-ADAccount -AccountDisabled -UsersOnly | Format-Table
name, sid -AutoSize }
Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 81
Ticks : 819660
TotalDays : 9.48680555555556E-07
TotalHours : 2.27683333333333E-05
TotalMinutes : 0.0013661
TotalSeconds : 0.081966
TotalMilliseconds : 81.966

What does all this tell me? For my environment, any of the three approaches work. And all are virtually the same speed. Keep in mind when using theMeasure-Command cmdlet that you should be really careful making judgments based upon milliseconds—it simply is not that accurate. If the results are several seconds apart, you have a guideline to go on. But milliseconds? Dude, the antivirus program can kick in and start a scan, or a process can decide to start. Anything can cause a time skew of a few hundred milliseconds.

In addition, you may have other requirements that will dictate which approach you use. The following table summarizes the three different techniques.

Approach Use
WMI Win32_UserAccount VBScript, Windows PowerShell 1.0, Windows PowerShell 2.0. If VBScript, then Windows 2000 and later. If Windows PowerShell, then Windows XP and later.
ADSISearcher Windows PowerShell 2.0, Windows XP, and later on client.
Search-ADAccount Windows PowerShell 2.0, Windows 7 with RSAT tools installed, Windows Server 2008 R2 domain controller.

EJ, that is all there is to using Windows PowerShell to search for disabled user accounts. User Management Week will continue tomorrow when we will talk about changing a user’s password.

We invite you follow us on Twitter and Facebook. If you have any questions, send email to us at, or post your questions on theOfficial Scripting Guys Forum.. See you tomorrow. Until then, peace.


Ed Wilson and Craig Liebendorfer, Scripting Guys