This new iteration is has a new function added to it called NSLookup(). This function does just what you'd imagine it does; runs nslookup against a computer name.
Before a machine account is added to the stale account text file the NSLookup() function performs the command with that machine's name. If a name is not returned in the results then the machine is added to the stale accounts.
Another new feature of the script creates a separate text file for machine names that do return a name in the results. The text file is called Problem Accounts.txt. The reason for this is any account that is not reporting login timestamps to the domain controller(s), but is recognized by nslookup could be experiencing problems and warrant further investigation.
The new script runs just as fast as the old version. By using nslookup instead of pinging the machine name the script does not have to wait for a response or timeout. I do recommend running this script with cscript now because of this. If you simply run it as a .vbs file you'll watch a ton of command windows open and close very rapidly. It's rather amusing, but it can be irritating.
Try opening it like that with Wireshark running and you're likely to cause someone to have a seizure.
I'm now going to begin working on version 3. Originally, I did not want to automate this process completely, but now that it is practically bulletproof I am going to automatically disable and move the stale accounts. So look forward to that.
As before, credit for the script that I built this script off of goes to Richard Mueller.
His original scripts and documentation can be found here (http://www.rlmueller.net/Last%20Logon.htm)
Download Link
His original scripts and documentation can be found here (http://www.rlmueller.net/Last%20Logon.htm)
Download Link
'------------------------------ 'Author: Christopher Maddalena w/ Richard Mueller 'Date: October 17, 2007; Revised: September 8, 2008 'Purpose: List stale user and computer account in AD in a text file. 'How: Dictionary objects are created to store the computer and user names for both disabled. ' and current accounts. These objects are segregated in this manner to allow for ' comparison. If a user/computer name exists on both lists then that account is active. ' This is done to remove the chance an active user or computer account are marked as stale ' because they have not been active on one particular domain controller. ' ADO and LDAP are used to query each domain controller sperately for the name and lastLogon ' attribute to ensure accuracy. The lastLogon attribute is compared to the current ' date and time set to the time zone bias pulled from he registry. ' Run this script using cscript: cscript ADCleanUpV2.vbs > output.txt '------------------------------ Option Explicit Call Main() WScript.Quit(0) On Error Resume Next Sub Main Dim intHigh, intLow, intSeconds, str64Bit, strOutput, arrDC() Dim strNumDays, strTodaysDate, strDeleteDate, strRealDeleteDate, strTimeZoneKey, strTimeZone, k, strConfig, strDNSDomain, strQuery, strDN Dim strDTMDate, strDTMDateValue, strDTMAdjusted, strStaleDays, strUser, strBase, strFilter, strAttributes, strComputer 'Declare and set objects Dim objFileSystem : Set objFileSystem = CreateObject("Scripting.FileSystemObject") Dim objShell : Set objShell = CreateObject("WScript.Shell") Dim objLogFile : Set objLogFile = objFileSystem.CreateTextFile("C:\Stale Accounts.txt", True) Dim objLogFile2 : Set objLogFile2 = objFileSystem.CreateTextFile("C:\Problem Accounts.txt", True) Dim objRootDSE : Set objRootDSE = GetObject("LDAP://RootDSE") 'Declare and set dictionary objects Dim objComputerList : Set objComputerList = CreateObject("Scripting.Dictionary") objComputerList.CompareMode = vbTextCompare Dim objUserLIst : Set objUserList = CreateObject("Scripting.Dictionary") objUserList.CompareMode = vbTextCompare Dim objCurrentUserList : Set objCurrentUserList = CreateObject("Scripting.Dictionary") objCurrentUserList.CompareMode = vbTextCompare Dim objCurrentComputerList : Set objCurrentComputerList = CreateObject("Scripting.Dictionary") objCurrentComputerList.CompareMode = vbTextCompare 'Set values for current date time and strNumDays = 30 'Number of days stale strStaleDays = strNumDays strStaleDays = 0 - strStaleDays strTodaysDate = CDate(Now()) strDeleteDate = CDate(Now()) - strNumDays strRealDeleteDate = "#" & strDeleteDate & "#" strDTMDateValue = DateAdd("d", strStaleDays, strTodaysDate) objLogFile.WriteLine(strDTMDateValue) 'Obtain local time zone bias strTimeZoneKey = objShell.RegRead("HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation\ActiveTimeBias") If (UCase(TypeName(strTimeZoneKey)) = "LONG") Then strTimeZone = strTimeZoneKey ElseIf (UCase(TypeName(strTimeZoneKey)) + "VARIANT()") Then strTimeZone = 0 For k = 0 To UBound(strTimeZoneKey) strTimeZone = strTimeZone + (strTimeZoneKey(k) * 256^k) Next End If 'Convert datetime value to UTC for query strDTMAdjusted = DateAdd("n", strTimeZone, strDTMDateValue) 'Find number of seconds since 1/1/1601 intSeconds = DateDiff("s", #1/1/1601#, strDTMAdjusted) 'Convert seconds to a string and convert to 100-nanosecond intervals str64Bit = CStr(intSeconds) & "0000000" 'Determine configuration context and DNS domain from RootDSE strConfig = objRootDSE.Get("configurationNamingContext") strDNSDomain = objRootDSE.Get("defaultNamingContext") 'Use ADO to search AD for nTDSDSA to identify domain controllers Dim objCommand : Set objCommand = CreateObject("ADODB.Command") Dim objConnection : Set objConnection = CreateObject("ADODB.Connection") objConnection.Provider = "ADsDSOObject" objConnection.Open "Active Directory Provider" objCommand.ActiveConnection = objConnection strBase = " strFilter = "(objectClass=nTDSDSA)" strAttributes = "AdsPath" strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";subtree" objCommand.CommandText = strQuery objCommand.Properties("Page Size") = 100 objCommand.Properties("Asynchronous") = True objCommand.Properties("Timeout") = 60 objCommand.Properties("Cache Results") = False Dim objRecordSet : Set objRecordSet = objCommand.Execute 'Enumerate through nTDSDSA and set domain controller AdsPaths into arrays k = 0 Do Until objRecordSet.EOF Dim objDC : Set objDC = GetObject(GetObject(objRecordSet.Fields("AdsPath").Value).Parent) ReDim Preserve arrDC(k) arrDC(k) = objDC.DNSHostName k = k + 1 objRecordSet.MoveNext Loop objRecordSet.Close 'Retrieve lastLogon attribute for each USER on each controller For k = 0 To UBound(arrDC) strBase = " strFilter = "(&(ObjectCategory=Person)(objectClass=User))" strAttributes = "name,lastLogon" strQuery = strBase & ";" & strFilter & ";" & strAttributes _ & ";subtree" objCommand.CommandText = strQuery On Error Resume Next Set objRecordSet = objCommand.Execute If (Err.number <> 0) Then On Error GoTo 0 objLogFile.WriteLine("Domain Controller not available: " & arrDC(k)) Else On Error GoTo 0 Do Until objRecordSet.EOF strDN = objRecordSet.Fields("name").Value On Error Resume Next Dim objDate : Set objDate = objRecordSet.Fields("lastLogon").Value If (Err.number <> 0) Then On Error GoTo 0 strDTMDate = #1/1/1601# Else On Error GoTo 0 intHigh = objDate.HighPart intLow = objDate.LowPart If (intLow < inthigh =" intHigh" inthigh =" 0)" intlow =" 0)" strdtmdate =" #1/1/1601#" strdtmdate =" #1/1/1601#"> strDTMDate Then If (objUserList.Exists(strDN) = True) Then If (strDTMDate > objUserList(strDN)) Then objUserList.Item(strDN) = strDTMDate End If Else objUserList.Add strDN, strDTMDate End If Else If (objCurrentUserList.Exists(strDN) = True) Then If (strDTMDate > objCurrentUserList(strDN)) Then objCurrentUserList.Item(strDN) = strDTMDate End If Else objCurrentUserList.Add strDN, strDTMDate End If End If If objUserList.Exists(strDN) AND objCurrentUserList.Exists(strDN) Then objUserList.Remove(strDN) End If objRecordSet.MoveNext Loop objRecordSet.Close End If Next 'Output users to log file objLogFile.WriteLine "" objLogFile.WriteLine("USERS + LAST LOGIN DATE AND TIME: ") objLogFile.WriteLine "" For Each strUser In objUserList.Keys objLogFile.WriteLine strUser & ";" & objUserList.Item(strUser) Next 'Retrieve lastLogon attribute for each COMPUTER on each controller For k = 0 To UBound(arrDC) strBase = " strFilter = "(&(objectClass=Computer)(lastLogon<=" & str64Bit & "))" strAttributes = "name,lastLogon" strQuery = strBase & ";" & strFilter & ";" & strAttributes _ & ";subtree" objCommand.CommandText = strQuery On Error Resume Next Set objRecordSet = objCommand.Execute If (Err.number <> 0) Then On Error GoTo 0 objLogFile.WriteLine("Domain Controller not available: " & arrDC(k)) Else On Error GoTo 0 Do Until objRecordSet.EOF strDN = objRecordSet.Fields("name").Value On Error Resume Next Set objDate = objRecordSet.Fields("lastLogon").Value If (Err.number <> 0) Then On Error GoTo 0 strDTMDate = #1/1/1601# Else On Error GoTo 0 intHigh = objDate.HighPart intLow = objDate.LowPart If (intLow < inthigh =" intHigh" inthigh =" 0)" intlow =" 0)" strdtmdate =" #1/1/1601#" strdtmdate =" #1/1/1601#"> strDTMDate Then If (objComputerList.Exists(strDN) = True) Then If (strDTMDate > objComputerList(strDN)) Then objComputerList.Item(strDN) = strDTMDate End If Else objComputerList.Add strDN, strDTMDate End If Else If objComputerList.Exists(strDN) AND objCurrentComputerList.Exists(strDN) Then objComputerList.Remove(strDN) End If End If objRecordSet.MoveNext Loop objRecordSet.Close End If Next objLogFile.WriteLine("") objLogFile.WriteLine("COMPUTERS + DATE OF LAST USER LOG IN: ") objLogFile.WriteLine "" objLogFile2.WriteLine "Possible Problematic Machine Accounts:" objLogFile2.WriteLine "These machines resolve to a name, but do not appear to be communicating with the domain." 'Close text file objLogFile.Close objLogFile2.Close For Each strComputer In objComputerList.Keys NSLookup(strComputer) Next 'Clean up objConnection.Close Set objRootDSE = Nothing Set objConnection = Nothing Set objCommand = Nothing Set objRecordset = Nothing Set objDC = Nothing Set objDate = Nothing Set objUserList = Nothing Set objComputerList = Nothing Set objShell = Nothing MsgBox("Query is complete.") End Sub Function NSLookup(strComputer) Dim strLine, strBool Dim objFileSystem : Set objFileSystem = CreateObject("Scripting.FileSystemObject") Dim objLogFile : Set objLogFile = objFileSystem.OpenTextFile("C:\Stale Accounts.txt", 8, True) Dim objLogFile2 : Set objLogFile2 = objFileSystem.OpenTextFile("C:\Problem Accounts.txt", 8, True) Dim objShell : Set objShell = CreateObject("WScript.Shell") Dim objWshScriptExec : Set objWshScriptExec = objShell.Exec("nslookup " & strComputer) Dim objStdOut : Set objStdOut = objWshScriptExec.StdOut strBool = "0" Do Until objStdOut.AtEndOfStream strLine = objStdOut.ReadLine If left(strLine,4) = "Name" Then objLogFile2.WriteLine strComputer objLogFile2.WriteLine "" strBool = "1" End If Loop If strBool = "0" Then objLogFile.WriteLine strComputer End if objLogFile.Close objLogFile2.Close End Function |
2 comments:
Nice article, thanks for sharing the helpful information about active directory cleanup. I found this Lepide active directory cleaner tool ( https://blog.netwrix.com/2018/02/15/the-ten-best-free-active-directory-management-tools/ ) which provides the facilitate to cleanup inactive or stale active directory accounts and get the real last log details of accounts. This tool helps to find out inactive accounts from active directory environment and generates the report which are based on inactive user/ computer accounts, never logged on users and real last logon details of accounts.
Post a Comment