CertCities.com  -- The Ultimate Site for Certified IT Professionals
Post Your Mind in the CertCities.com Forums Share share | bookmark | e-mail
 Microsoft®
 Cisco®
 Security
 Oracle®
 A+/Network+™
 Linux/Unix
 More Certs
 Newsletters
 Salary Surveys
 Forums
 News
 Exam Reviews
 Tips
 Columns
 Features
 PopQuiz
 RSS Feeds
 Press Releases
 Contributors
 About Us
 Search
 

Advanced Search
 Free Newsletter
  Sign-up for the #1 Weekly IT
Certification News
and Advice.
Subscribe to CertCities.com Free Weekly E-mail Newsletter
CertCities.com

See What's New on
Redmondmag.com!

Cover Story: IE8: Behind the 8 Ball

Tech-Ed: Let's (Third) Party!

A Secure Leap into the Cloud

Windows Mobile's New Moves

SQL Speed Secrets


CertCities.com
Let us know what you
think! E-mail us at:
ccfeedback@certcities.com


 
 
...Home ... Editorial ... Columns ..Column Story Thursday: September 2, 2010


 Microsoft: Under the Hood  
Don      Jones
Don Jones


  • PRINTABLE FORMAT
  • E-MAIL STORY
  • POST YOUR COMMENTS
  • MORE COLUMNS
  •  Finding Idle Users
    A domain security script for pre-Windows Server 2003 domains.
    by Don Jones  
    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.


    Don Jones is the owner and operator of ScriptingAnswers.com, a speaker at national technical IT conferences, and the author of nearly twenty books on information technology. His latest book is "Managing Windows with VBScript and WMI" (Addison-Welsey) and he's completing "Windows Administrator's Automation Toolkit" (Microsoft Press). You can reach Don at his Web site or at don@scriptinganswers.com.

     


    More articles by Don Jones:
  • Away, Foul Wires!
  • GPO Policies and Preferences
  • Imaginary Servers
  • Becoming a Citrix Administrator: The New CCA Exam (#222)

  • -- advertisement --


    There are 7 CertCities.com user Comments for “Finding Idle Users”
    Page 1 of 1
    7/13/04: JohnRolstead from St. Louis, MO says:This code works ok if you are in a small environment, with only a few domain controllers. I support a Windows 2000 shop with 30 domain controllers in all parts of the world. Another alternative to Last Logon is Password Last Set. Our password policy is set to max 90 days, so if a password is not changing in 120 days or so, the user is probably no longer needed. pwdLastSet is replicated to all DCs, making query time about 1 minute. Here is the coder who wrote the perl script to query this value. It can work for users and computers by the way. NT changes password every 7 days, W2K and above every 30 days. # This Perl code finds the user accounts whose password is about to expire # --------------------------------------------------------------- # From the book "Active Directory Cookbook" by Robbie Allen # Publisher: O'Reilly and Associates # ISBN: 0-596-00466-4 # Book web site: http://rallenhome.com/books/adcookbook/code.html #
    12/9/04: Nate79 from Colorado says:Thanks for taking the time to post this script Don. It helped out with some stuff we had to do for Sarbanes-Oxley.
    2/15/05: Dipesh Malk from Delhi says:Every time i will try to execute this script. i will get an error message that: ERROR : PERMISSION DENIED 'GETOBJECT' CODE : 800A0046
    3/15/05: Joshua from Little Rock, Arkansas, USA says:I'm receiving the same error that Dipesh gets. What gives?
    9/13/06: Jones Fan says:Thanks Don! Works perfectly, as your scripts always do. Dipesh and Josh: Are you running with enterprise admin rights? Also, check to make sure this key is present in the registry: HKLM\SOFTWARE\Microsoft\Ole\EnableDCOM:R eg_SZ:Y Could also check this: Open Control Panel Open Administrative Tools Open the Component Services applet Select Component Services and open the Computers Folder Right Click My Computer and select Properties Select the Default Properties tab and Ensure Enable Distribted COM on this Computer is Checked. Apply, close all windows and reboot the PC
    1/26/07: Anonymous says:I am just wondering, is it possible to modify this scripts to find only the “OU USERS” (I mean with specified OU’s) with “last logon” ? Can you also please publish the new scripts in your website and send one email along the modified scripts? Thanks, SuvajitBasu
    7/21/10: Anonymous says:Thanks for the code, unfortunately I can't get it to run. Forgive me, I'm a n00b in Active Directory. I'm not sure what the LDAP prompt is referring to, and this is probably why I'm getting the 'a referral was returned from the server' error after running the script. A little help?
    Your comment about: “Finding Idle Users”
    Name: (optional)
    Location: (optional)
    E-mail Address: (optional)
    Comment:
       

    -- advertisement (story continued below) --

    top