Home > .net, active directory, adsi, howto, powershell, scripting, server 2003, user management, windows, windows server > Find Disabled and Inactive User and Computer Accounts using Powershell – Part II

Find Disabled and Inactive User and Computer Accounts using Powershell – Part II

Part I demonstrated how to find aged or inactive accounts, and in Part II we will look at another lingering account type: disabled accounts.

Like inactive accounts, Directory Searchers also come in handy for disabled accounts. We can also, however, read an Active Directory account’s status directly from a hidden attribute on the ADSI object. Let’s start with the Directory Searcher method. This entry also draws from Bahram’s Blog. The code:

$adobjroot = [adsi]''
$objdisabsearcher = New-Object System.DirectoryServices.DirectorySearcher($adobjroot)
$objdisabsearcher.filter = "(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2))"
$resultdisabaccn = $objdisabsearcher.findall() | sort path

That was a lot easier! The $adobjroot line gives us an ADSI object for the root of our domain. The second line creates a new Directory Services searcher, and then we add our filter.

As with Part I, setting objectCategory to person and objectClass to user sets up our filter to search for user accounts; switch both of those to computer to search for computer accounts instead.

The userAccountControl portion is a bit of weird number, though, isn’t it?! After some digging, I was able to determine that the :1.2.840.113556.1.4.803: is the attribute ID for the Last-Logon-Timestamp Attribute (found on an MSDN article linked from Bahram’s Blog). Specifying that value in the directory searcher filter queries for the value of that specific attribute stored in the userAccountControl property, rather than userAccountControl as a whole. Stephen Looney actually corrected this for me, as I was somewhat off the mark in my deduction. The string is not a selection filter, as I had supposed, but more akin to a bitwise OR operator. See his comment below for clarification.

The last line of the code simply collects our searcher results in System.DirectoryServices.SearchResult collection

The alternative method to determine an account’s status is to check a hidden attribute on the ADSI object itself. For this you will need the Distinguished Name of a user or computer account:

$struserdn = "CN=Some User,OU=Users,OU=Corp,DC=yourdomain,DC=com"

Set up an ADSI object for that account:

$adobjuser = [ADSI]"LDAP://$struserdn"

And access the hidden method:

$adobjuser.PsBase.InvokeGet("AccountDisabled")

This will return $true if the account is disabled, and $false if the account is not disabled (i.e. it is enabled). It’s not as glamorous as the directory searcher method, but I think both have their place.

Also, feel free to play around with the lastLogonTimeStamp and UserAccountControl attributes in the directory searcher. For instance, to find only disabled accounts that have been inactive for a certain number of days, you could use an LDAP string like:

$objsearcher.filter = (&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2)(lastLogonTimeStamp<=" + $lltIntLimit + "))"

Or switch it up for only enabled accounts inactive for a certain period by flipping the userAccountControl portion around be negative:

$objsearcher.filter = (&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(lastLogonTimeStamp<=" + $lltIntLimit + "))"

I hope this has been helpful. As always, comments, corrections, additions, or questions are appreciated.

Bitcoin tip address for this post: 192yT6362K6BgLpm8B2xhcCVZtrnwQkjek

Advertisements
  1. justanothersysadmin
    2008-03-24 at 21:41

    Updated: Added explanation for userAccountControl:1.2.840.113556.1.4.803:=2 directory searcher filter.

  2. Stephen Looney
    2008-09-04 at 18:21

    Actually, :1.2.840.113556.1.4.803 is the LDAP Bitwise AND operator “LDAP_MATCHING_RULE_BIT_AND.” See here and here for more information. This operator is used because the userAccountControl is a Bitmask value. See here for more information.

  3. justanothersysadmin
    2008-09-04 at 18:37

    Hi Stephen,

    Thanks for that info! It’s not too often that I dig through some of those odd attributes, so it’s great that someone finally stumbled across this post and cleared things up a bit!

    Cheers,

    JaS

    P.S. I just got your note about the missing URL, and I have updated your comment to fill in that link. Thanks again.

  4. justanothersysadmin
    2008-09-04 at 18:44

    Updated: Incorporated Stephen Looney’s explanation of the LDAP_MATCHING_RULE_BIT_AND operator. Thanks Stephen!

    JaS

  5. Mahair Ashaboon
    2009-02-19 at 22:31

    How can I find all user accounts that have been disabled for over than a year?
    My management wants me to identify those accounts to delete them and to keep all other disabled accounts that have not been year old disabled.
    I have 2 AD forests the first forest has only one domain and the second forest has 6 domains.
    All DCs are running Windows Sever 2003 SP2 R2 Standard Edition.

  6. Dean Scully
    2010-07-14 at 16:40

    Your code above is missing a letter;

    $adobjuse.PsBase.InvokeGet(“AccountDisabled”)

    should be

    $adobjuser.PsBase.InvokeGet(“AccountDisabled”)

    note the missing ‘r’ in ‘$adobjuse.PsBase’

    • 2010-07-16 at 14:26

      Thanks for catching that! Code snippet updated to reflect the typo fix.

      JaSa

  7. JD
    2011-01-11 at 16:44

    a little trick to make the output more reader friendly. create an array and then foreach the name property of the findall output into that array. here is the code from your example above.

    $resultdisabaccn = @()
    $objdisabsearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]””)
    $objdisabsearcher.filter = “(&(objectCategory=person)(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=2))”
    $objdisabsearcher.findall() | % {$resultdisabaccn = $resultdisabaccn + [string]$_.properties.name}
    $resultdisabaccn | sort

  8. Ytsejamer1
    2011-06-20 at 06:10

    This was a great couple of articles. I’m a PS newb and would like some clarification. I want to search for users with mailboxes on a particular server. The reason is that as part of a coexistence, I’ve got disabled mailboxes on the legacy 2003 server. I need to find out which users with mailboxes on that server have been disabled for over a year.

    I think I can use part of this PS script along with a couple extra lines, but aren’t sure how best to do so.

    Thanks!

  9. Ytsejamer1
    2011-06-20 at 07:34

    Here’s what I managed to piece together from these articles and a few other online resources:

    # Set the AD Container and Number of Days
    $Subtree = “OU=Users – Disabled,DC=company,DC=com”
    $NbDays = “365”

    # Get the current date
    $currentDate = [System.DateTime]::Now

    # Convert the local time to UTC format because all dates are expressed in UTC (GMT) format in Active Directory
    $currentDateUtc = $currentDate.ToUniversalTime()

    # Set the LDAP URL to the container DN specified on the command line
    $LdapURL = “LDAP://” + $Subtree

    # Initialize a DirectorySearcher object
    $searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$LdapURL)

    # Set the attributes that you want to be returned from AD
    $searcher.PropertiesToLoad.Add(“displayName”) >$null
    $searcher.PropertiesToLoad.Add(“sAMAccountName”) >$null
    $searcher.PropertiesToLoad.Add(“lastLogonTimeStamp”) >$null
    $searcher.PropertiesToLoad.Add(“MsExchHomeServerName”) >$null

    # Calculate the time stamp in Large Integer/Interval format using the $NbDays specified on the command line
    $lastLogonTimeStampLimit = $currentDateUtc.AddDays(- $NbDays)
    $lastLogonIntervalLimit = $lastLogonTimeStampLimit.ToFileTime()
    Write-Host “Looking for all users that have not logged on since “$lastLogonTimeStampLimit” (“$lastLogonIntervalLimit”)”
    $searcher.Filter = “(&(&(objectCategory=person)(objectClass=user)(&(userAccountControl:1.2.840.113556.1.4.803:=514))(&(MsExchHomeServerName=/O=Advanced Systems/OU=Corporate Office/cn=Configuration/cn=Servers/cn=EXCHANGE01)(lastLogonTimeStamp<=$lastLogonIntervalLimit))))"
    # Run the LDAP Search request against AD
    $users = $searcher.FindAll()

    foreach ($user in $users)
    {
    # Read the user properties
    [string]$adsPath = $user.Properties.adspath
    [string]$displayName = $user.Properties.displayname
    [string]$samAccountName = $user.Properties.samaccountname
    [string]$lastLogonInterval = $user.Properties.lastlogontimestamp

    # Convert the date and time to the local time zone
    $lastLogon = [System.DateTime]::FromFileTime($lastLogonInterval)
    Write-Host "Inactive user "$displayName" ("$samAccountName") who last logged on "$lastLogon" ("$lastLogonInterval")"}

  1. 2012-09-27 at 20:26
  2. 2014-03-04 at 12:56

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: