Active Directory Cleanser

Cull unused or deleted accounts using the LDIFDE tool and some scripting trickery.

Bill: I'm trying to clean up stale and unused AD user accounts. For accounts that logged in at least once, I can use the USRSTAT resource kit tool to show the last logon date and delete accounts unused for a certain period of time. For accounts that never logged in, I have no way of knowing if it's a new or old account. Is there a way or tool to get the creation date for an account so I can then determine whether or not this account can be cleaned?
—Zev

Zev: You can get the creation date for each account from Active Directory. Every AD object has a WhenCreated and WhenChanged attribute. You can dump these attributes into a flat file using the LDIFDE utility, or you can dump them into a comma-delimited file using CSVDE (both utilities come with Windows 2000).

Here's the syntax to dump the two attributes for the user objects in an OU called Phoenix in a domain called Company.com to the console for viewing (the entire entry should typed as a single line):

ldifde -d ou=phoenix,dc=company,dc=com -l whencreated, whenchanged -p onelevel -r "(ObjectCategory=user)"
-f con

Get Help from Bill

Got a Windows or Exchange question or need troubleshooting help? Or maybe you want a better explanation than provided in the manuals? Describe your dilemma in an e-mail to Bill at mailto:[email protected]; the best questions get answered in this column.

When you send your questions, please include your full first and last name, location, certifications (if any) with your message. (If you prefer to remain anonymous, specify this in your message but submit the requested information for verification purposes.)

If you wanted to save the dump to a file, change the -f switch from con to a file name.

The last logon timestamp uses this format: YYYYMMDDHHMMSS, with the hour shown in Universal Coordinated Time. A time stamp of 20040115182937.0Z corresponds to Jan 15 2004 18:29:37 UCT.

USRSTAT is slow, and the report you get has to be merged with the LDIFDE dump. So, I put together a script that searches for user objects at each domain controller, then lists the local logon time and the creation time. The user logon timestamp requires conversion from a long integer. I borrowed the conversion code comes from Richard L. Mueller (www.rlmueller.net/Programs). Richard's full script also takes the local time zone from the Registry and converts the time from UCT to local time. Nifty.

'Establish ADO Constants
ADS_CHASE_REFERRALS_NEVER = &00
ADS_CHASE_REFERRALS_SUBORDINATE = &20
ADS_CHASE_REFERRALS_EXTERNAL = &40
ADS_CHASE_REFERRALS_ALWAYS = &60
ADS_SCOPE_BASE = 0
ADS_SCOPE_ONELEVEL = 1
ADS_SCOPE_SUBTREE = 2

'Get Distinguished Name for local domain
Set RootDSE = GetObject("LDAP://RootDSE")
domainDN = RootDSE.Get("DefaultNamingContext")

'Initialize ADO connection
Set connection = CreateObject("ADODB.Connection")
connection.Provider = "ADsDSOObject"
connection.open
Set command = CreateObject("ADODB.Command")
Set command.ActiveConnection = connection
Command.Properties("Page Size") = 1000
Command.Properties("Timeout") = 30
Command.Properties("searchscope") = ADS_SCOPE_SUBTREE
Command.Properties("Chase referrals") =   ADS_CHASE_REFERRALS_NEVER
Command.Properties("Cache Results") = False

'Get list of domain controllers for the domain
Set dcList = GetObject("LDAP://ou=domain controllers," &_   domainDN)

'Walk each domain controller for logons
For Each dc In dcList
WScript.Echo String(40,"=")
WScript.Echo "Logon dates at " & dc.DNSHostName

command.CommandText = "SELECT name,lastlogon,whencreated,whenchanged FROM " &_
  "'LDAP://" & dc.DNSHostName & "/" & domainDN &"' WHERE   objectcategory = 'user'"

Set rs = command.Execute
Do Until rs.EOF
  adoLastLogon = rs.fields("lastlogon")
  On Error Resume Next
  Err.Clear
  Set longDate = adoLastLogon
  If Err.Number <> 0 Then
    Err.Clear
    logonDate = "No Local Logon"
  Else
    longDateHigh = longDate.HighPart
    longDateLow = longDate.LowPart
    If (longDateLow = 0) And (longDateHigh = 0) Then
      logonDate = "No Local Logon"
    Else
      If longDateLow < 0="" then="" longdatehigh="longDateHigh" +="">
      logonDate = #1/1/1601# + (((longDateHigh * (2 ^ 32))
        + longDateLow)/600000000/1440)
    End If
  End If

  WScript.Echo "User Name: " & rs.fields("name")
  WScript.Echo " Last logon: " & logonDate
  WScript.Echo " Object Created: " & rs.fields("WhenCreated")
  WScript.Echo " Object Modified: " & rs.fields("WhenChanged")

  rs.MoveNext
Loop

WScript.Echo vbNL
Next

WScript.Quit()

As you mentioned, Zev, Windows Server 2003 has an additional attribute called LastLogonTimestamp that replicates to every domain controller once you shift to a Windows Server 2003 functional level. You can rewrite this script to search for the contents of LastLogonTimestamp on any domain controller.

Hope this helps.

About the Author

Contributing Editor Bill Boswell, MCSE, is the principal of Bill Boswell Consulting, Inc. He's the author of Inside Windows Server 2003 and Learning Exchange Server 2003 both from Addison Wesley. Bill is also Redmond magazine's "Windows Insider" columnist and a speaker at MCP Magazine's TechMentor Conferences.

Featured