Previous Section Table of Contents Next Section

Finding Inactive Users

This is a script I like to run from time to time, just to find out how many user accounts haven't logged on for a while. Often, they're accounts of employees who have left, but another administrator (certainly not me!) forgot to remove the accounts. Because the accounts represent a potential security threat, I like to disable them until I can figure out if they're still needed for something.

NOTE

This script works reliably only in Active Directory domains that use Windows Server 2003 domain controllers. Unfortunately, the attribute in Active Directory that stores the last logon date is not reliably updated and replicated in NT or Windows 2000 domains. The only way to use this script in older domains is to run it independently against each domain controller and then compare the results, because each domain controller maintains an independent list of last logon times.


graphics/arrow.gif Finding Inactive Users

Listing 30.2 demonstrates how to use ADSI to locate users who haven't logged on in a while.

Listing 30.2. FindOldUsers.vbs. This script checks the LastLogin date to see when users last logged into the domain.

Dim dDate

Dim oUser

Dim oObject

Dim oGroup

Dim iFlags

Dim iDiff

Dim iResult

Const UF_ACCOUNTDISABLE = &H0002



'Set this to TRUE to enable Logging only mode - 

'no changes will be made

CONST LogOnly = TRUE



'Point to oObject containing users to check

Set oGroup = _

 GetObject("WinNT://MYDOMAINCONTROLLER/Domain Users")

On error resume next

For each oObject in oGroup.Members



 'Find all User Objects Within Domain Users group

 '(ignore machine accounts)

 If (oObject.Class = "User") And _

  (InStr(oObject.Name, "$") = 0) Then

  Set oUser = GetObject(oObject.ADsPath)

 End If



 dDate = oUser.get("LastLogin")

 dDate = Left(dDate,8)

 dDate = CDate(dDate)



'find difference in weeks between then and now

 iDiff = DateDiff("ww", dDate, Now)



 'if 6 weeks or more then disable the account

 If iDiff >= 6 Then

  iFlags = oUser.Get("UserFlags")

 End If



 If (iFlags AND UF_ACCOUNTDISABLE) = 0 Then



  ' Only disable accounts if LogOnly set to FALSE

  If LogOnly = False Then

   oUser.Put "UserFlags", iFlags OR UF_ACCOUNTDISABLE

   oUser.SetInfo

  End if



  sName = oUser.Name

  iResult = Log(sName,iDiff)

 End If

Next



Set oGroup = Nothing

MsgBox "All Done!"



Function Log(sUser,sDate)



 'Constant for Log file path

 CONST StrLogFile = "C:\UserMgr1.txt"

 

 Set oFS = CreateObject("Scripting.FileSystemObject")

 Set oTS = oFS.OpenTextFile(strLogFile, 8, True)

 oTS.WriteLine("Account:" & vbTab & sUser & vbTab & _

  "Inactive for:" & vbTab & sDate & vbTab & "Weeks" & _

  vbTab & "Disabled on:" & vbTab & Date & vbTab & "at:" & _

  vbTab & Time)

  oTS.Close

  Set oFS = Nothing

  Set oTS = Nothing



End Function

You need to set the domain controller name to one that's within your environment. You can also customize the script to specify the number of weeks that can go by before you consider a user inactive. Finally, as-is, the script only tells you which accounts it would like to disable; you need to make one minor modification, which I discuss below, to have it make the change.

graphics/arrow.gif Finding Inactive Users-Explained

I start the script by defining a bunch of variables and a couple of constants. Constants, you may recall, are simply friendly names for difficult-to-remember values. In this case, I define one constant to be the value that a user account's flags take on when the account is disabled. I use another constant to tell the script whether to simply log its recommendations or disable old accounts; edit the script and change the constant to False if you want the script to disable accounts for you. See Chapter 5 for more coverage of variables and constants.


Dim dDate

Dim oUser

Dim oObject

Dim oGroup

Dim iFlags

Dim iDiff

Dim iResult

Const UF_ACCOUNTDISABLE = &H0002



'Set this to TRUE to enable Logging only mode - 

'no changes will be made

CONST LogOnly = TRUE

The script now needs to connect to the domain using ADSI and retrieve the Domain Users group. Look to Chapter 15 for more on connecting to domains.


'Point to oObject containing users to check

Set oGroup = GetObject("WinNT://MYDOMAINCONTROLLER/Domain Users")

Now, I use a For Each…Next loop to go through each user in the domain, one at a time.


On error resume next

For Each oObject in oGroup.Members

Even in NT, groups can technically contain computers as well as users. To make sure I'm only dealing with users, I add an If…Then to test the current account's object class.


'Find all User Objects Within Domain Users group

'(ignore machine accounts)

If (oObject.Class = "User") And _

 (InStr(oObject.Name, "$") = 0) Then

 Set oUser = GetObject(oObject.ADsPath)

End If

I want the script to pull in the LastLogin date.

NOTE

Microsoft almost always uses the term logon rather than login, because login was what you did in a Novell NetWare environment. But the folks who wrote the underlying code never got the message, and so it's LastLogin.


After getting the date, I'm going to grab just the leftmost eight characters. That's because LastLogin also includes time information, which I don't need. I end by converting the information to an actual, formatted date.


dDate = oUser.get("LastLogin")

dDate = Left(dDate,8)

dDate = CDate(dDate)

I use the DateDiff() function to find the difference between the last login date and today. The "ww" tells DateDiff() that I want the difference expressed in weeks, instead of days or some other interval.


'find difference in weeks between then and now

iDiff = DateDiff("ww", dDate, Now)

If the difference is six weeks or more, I retrieve the user's existing UserFlags property, which includes whether the account is disabled.


'if 6 weeks or more then disable the account

If iDiff >= 6 Then

 iFlags = oUser.Get("UserFlags")

End If

If the user account isn't already disabled, I disable it-if that constant is set to False.


If (iFlags AND UF_ACCOUNTDISABLE) = 0 Then



 ' Only disable accounts if LogOnly set to FALSE

 If LogOnly = False Then

  oUser.Put "UserFlags", iFlags OR UF_ACCOUNTDISABLE

  oUser.SetInfo

 End if

Whether I disable the account or not, I use a function named Log to add this user account to the log file.


  sName = oUser.Name

  iResult = Log(sName,iDiff)

 End If

Next

At this point, I've run through all of the accounts and I can display a message indicating that the script is finished.


Set oGroup = Nothing

MsgBox "All Done!"

The last thing in the script is the Log function. It accepts two parameters: the user's name and a date. This information is saved to a text file, and the name of that file is defined in a constant. Chapter 5 covers custom functions and subroutines.


Function Log(sUser,sDate)



 'Constant for Log file path

 CONST StrLogFile = "C:\UserMgr1.txt"

You might notice that the function opens the text file each time the function is called. That's because I also close the file each time the function is finished. It may seem inefficient, but this ensures that the file is safely closed if the script crashes in the middle for some reason. Note that the "8" used in the OpenTextFile method opens the file for appending. See Chapter 12 for more on reading and writing to text files.


Set oFS = CreateObject("Scripting.FileSystemObject")

Set oTS = oFS.OpenTextFile(strLogFile, 8, True)

All that's left now is to write the information into the log file, close the file, and release the objects I've created.


 oTS.WriteLine("Account:" & vbTab & sUser & vbTab & _

  "Inactive for:" & vbTab & sDate & vbTab & "Weeks" & _

  vbTab & "Disabled on:" & vbTab & Date & vbTab & "at:" & _

  vbTab & Time)

  oTS.Close

  Set oFS = Nothing

  Set oTS = Nothing



End Function

This is a great script to run on a regular basis, and you can even use Task Scheduler to automate it. Just make sure it's running under an administrator's account if you want it to actually disable the inactive user accounts.

    Previous Section Table of Contents Next Section