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.
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.
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.
|