| 5/26/2004
-- My business partner, Derek Melber, was recently asked by one of our consulting
clients to create a script that would aid them in auditing their domain security.
He wanted to create a report that would display users' account status, including
lockout, disabled, last time their password was changed, and so forth. He also
wanted to display users' last logon date.
All that information is in the domain, but the last logon date in particular
can be difficult to figure out in pre-Win2003 domains. That's because, prior
to Win2003, the "lastLogon" attribute isn't replicated, meaning only
the last DC that a user authenticated to will have an accurate value. So to
display an accurate value, you'd have to query every domain controller in the
domain—perfect gruntwork for a script, but not the easiest thing to code.
I had some free time this past Saturday, though (I have no life), and decided
to take a whack at it, using Derek's original script as a starting point.
One thing I wanted to do was be able to check every user in the domain, which
is a less-than-straightforward task in AD, where users can be buried under a
tree of organizational units (OUs). Fortunately, AD is backward-compatible with
Windows NT domains, meaning AD can present its user list as a flat list, ignoring
OU membership. Windows NT also provides an easier-to-use LastLogin property
for users; AD's native lastLogon property (note the subtle name difference)
is a complex integer value that includes both a time and date, and it's a pain
to fuss with in VBScript.
On the other hand, NT doesn't make it very easy to enumerate a list of all
DCs in the domain. AD doesn't exactly make it easier, but it does have a helpful
Domain Controllers OU, which, nine times out of ten, contains every DC in the
domain. In fact, pretty much nobody recommends ever moving DCs out of the Domain
Controllers OU, so it's a safe bet that all the DCs' computer accounts can be
found there.
Given the relative strengths and weaknesses of NT and AD, then, I resolved
to use both. The result is the following script, which uses Active Directory
Services Interface (ADSI) and VBScript to query an AD domain both from an NT
standpoint (using the WinNT: moniker) and from a native AD standpoint (using
the LDAP: moniker). Check it out:
'get domain and container
Dim sTarget, sOU
sTarget = InputBox("Target what domain?", "Domain?", _
"techmentorevent")
sLDAP = InputBox("LDAP version
of domain name?", "LDAP Domain?", _
"dc=techmentorevents,dc=pri")
'iterate through all DCs?
Dim bCheckAll
bCheckAll = MsgBox("Check all DCs for last logon date?", _
4, "Check all DCs?")
If bCheckAll = 6 Then
bCheckAll = True
Else
bCheckAll = False
End If
'objects for output file
Dim sOutput, oFSO, oTS
sOutput = InputBox("Output path and filename?", "Filename?",
_
"c:\domainreport.csv")
Set oFSO = CreateObject("Scripting.FileSystemObject")
'see if file exists
If oFSO.FileExists(sOutput) Then
MsgBox("File exists. Please select another.")
WScript.Quit
End If
'create output file
Set oTS = oFSO.CreateTextFile(sOutput)
Const cComma = ","
'output header
oTS.WriteLine "User" & _
cComma & "Disabled" & cComma & "Locked"
& _
cComma & "IdleDays"
'get destination container
On Error Resume Next
Set oOU = GetObject("WinNT://" & sTarget)
If Err <> 0 Then
MsgBox "Error: " & vbCrLf & Err.Description
WScript.Quit
End If
On Error Goto 0
'filter for user objects
oOU.Filter = Array("User")
'get the Default Domain Controllers
container
Dim oDCs, oDC
Set oDCs = GetObject("LDAP://ou=Domain Controllers," & _
sLDAP)
'go through container objects
Dim oObject
For Each oObject In oOU
'get
attributes
Dim bDisabled, bLocked, iIdleDays
Dim dLastLogin, dLocalLastLogin
'reset last login date
dLastLogin = CDate("1/1/1900")
'is
user disabled?
If oObject.accountdisabled Then
bDisabled = True
Else
bDisabled = False
End If
'is
user locked out?
If oObject.isaccountlocked Then
bLocked = True
Else
bLocked = False
End If
'output basic info
oTS.Write oObject.name & cComma &
_
bDisabled & cComma &
_
bLocked & cComma
'check for last login?
If bCheckAll Then
'checking last login on all
DCs
For Each oDC In oDCs
'get last login
from this DC
Dim oLocalUser
Set oLocalUser =
GetObject("WinNT://" & _
Replace(oDC.sAMAccountName,"$","")
& "/" & _
oObject.name
& ",user")
On
Error Resume Next
dLocalLastLogin
= oLocalUser.LastLogin
If Err
<> 0 Then
dLocalLastLogin
= CDate("1/1/1900")
End
If
On Error
Goto 0
'is
that more recent?
If dLocalLastLogin
> dLastLogin Then
dLastLogin
= dLocalLastLogin
End
If
Next
'output last login
oTS.Write DateDiff("d",dLastLogin,Date)
& vbcrlf
Else
'not checking last login
oTS.Write "n/a"
& vbcrlf
End If
Next
'close text file and notify
oTS.Close
MsgBox "Finished. Output is at " & sOutput
When you run this script, you'll have to enter the domain name twice. First,
enter a normal NT domain name (the default is TECHMENTOREVENT). Next, enter
the LDAP-style domain name. The default is "dc=techmentorevents,dc=pri"
for my example domain, techmentorevents.pri. If your domain was braincore.net,
you'd enter "dc=braincore,dc=net" for the domain name. You can decide
whether or not to check the LastLogin property, because doing so will cause
the script to run for a lot longer since it has to query a bunch of computers.
The script will, by the way, bomb out if any of your DCs are unreachable. I
left it that way on purpose, but you can have it ignore missing DCs by using
the following code:
'get
last login from this DC
Dim oLocalUser
On Error Resume Next
Set oLocalUser = GetObject("WinNT://"
& _
Replace(oDC.sAMAccountName,"$","")
& "/" & _
oObject.name & ",user")
The boldfaced line of code tells VBScript to ignore any errors and keep right
on processing.
I hope this will be a useful script for you. Those of you lucky enough to work
in a native Windows Server 2003 domain don't need this trickery, because Win2003
repliates the lastLogin property to all DCs; just use the Saved Queries feature
in 2003's AD Users and Computers console to query for user accounts that haven't
logged in during a specified number of days. Note that it's impossible for me
to test this script in the myriad environments out there; it's possible your
environment may contain something that causes the script to bomb unexpectedly.
If it does, please feel free to send me a short e-mail describing the problem
and I'll do my best to help you figure it out. This script outputs a file that
can easily be imported into Excel so that you can sort by column or do whatever
other fancy stuff you like. 
|