Contents for Ezine 86 – LastLogon Property
- This Week’s Secret
- Scenario: You want to Identify the Delete Old Accounts
- Beware lastLogon
- Example – To Disable, then Move Old Accounts into a Named OU
- Summary of Resetting the Password
If I had to give a man-of-the-match award to an element of this week’s script, it would be a tie between WScript.Echo and the construction ‘If…Then… End If’. My secret for success was to combine WScript.Echo commands with the ‘If’ construction. As a result I was able to troubleshoot the more powerful sections, for example the sub routine to move the computers or users.
While I have ‘Remmed out most of the WScript.Echo lines in the final script, I retained an unusually high number of ‘If’ statements. Incidentally, I don’t know how I would have managed without my OnScript VBScript editor, see here menu opposite.
This week’s script is a triumph for vision, believe and perseverance. In truth, I had forgotten how difficult it was to move an object from one OU to another using a script. Drag and drop is so easy in the MMC, but to mimic this action with a VBscript is very difficult.
If I have a lingering doubt about this week’s script, it is that although this is a beautiful script, even a work of art, it may not be suitable for teaching purposes.
SolarWinds have produced three Active Directory add-ons. These free utilities have been approved by Microsoft, and will help to manage your domain by:
- Seeking and zapping unwanted user accounts.
- Finding inactive computers.
- Bulk-importing new users. Give this AD utility a try, it’s free!
Five readers kindly wrote in suggesting that we disable the accounts before deleting them. I whole-heartedly agree. Last week I was so focussed on the lastLogon date that I forgot about ‘best practice’. In this instance, best practice is to disable the accounts, backup Active Directory, and only then delete the old accounts.
Now the above task is easy to plan, but hard to put into action, especially the section which moves the computer or user objects from one OU to another. I have extended my usual plea for you to break the script into sections by introducing two sub-routines. Sub Disable() will handle disabling the accounts, while the Sub MoveUser() moves the disabled user or computer accounts into another OU.
There are three goals for this script:
1) To identify all users or computers that are older than 60 days. (Number of days is controlled by intAging.)
2) To disable these old accounts.
3) To move any disabled accounts into a separate OU. I admit that in terms of logic, in a production network, this could mean moving accounts that are disabled but not older than 60 days. My defence is that such points keep you on your toes, and that you or I could amend the script to cater for this loophole. What I seek is to create scripts that teach you about scripting as well as solving specific problems.
O.K. I confess, the real reason that I have not perfected the script is time, plus a genuine wish not to add yet more complexity to the script.
This script needs an Active Directory domain, however please note that for safety, this script is designed for a domain with only one Domain Controller. Best would be to logon as administrator at a domain controller. My plan B would be to Remote Desktop to a domain controller.
- Review how the script extracts the lastLogon date – See last week’s ezine. If necessary, create an extra test route to check the lastLogon value against the value for a known date.
- Decide what you want to do with objects that match the test, disable them? Move them, or even both?
- Talking of objects, you have a decision to make. Decide whether to run the script for users, computers or both.
Instructions for Creating a VBScript to display lastLogon time.
- You have not just one variable to amend, but a whole section. Make sure that you set the SourceOU, and the DestinationOU. Pay special attention to the type of object ‘Computer’ or ‘Person’ (not user).
- Create a few new users and new computer in your SoureOU, as these account will never have logged on, they will have lastLogon value of 1/1/1601. As a result, the script will identify, disable and then move these accounts into strDestinationOU.
- Copy and paste the example script below into notepad or get a OnScript.
- One advantage of a good script editor such as OnScript is that you can see the line numbers.
- Save the file with a .vbs extension, for example: LastLogonMove.vbs
- Double click LastLogonMove.vbs and check the message box.
Guy Recommends: The Free IP Address Tracker (IPAT)
Calculating IP Address ranges is a black art, which many network managers solve by creating custom Excel spreadsheets. IPAT cracks this problem of allocating IP addresses in networks in two ways:
For Mr Organized there is a nifty subnet calculator, you enter the network address and the subnet mask, then IPAT works out the usable addresses and their ranges.
For Mr Lazy IPAT discovers and then displays the IP addresses of existing computers. Download the Free IP Address Tracker
This example disables all accounts older than 60 days in an OU specified by strSourceOU. It then moves these filtered accounts into a named OU. Note my script’s original version number was 5.1, I would not be surprised that in the light of feedback, I amended the script, if so check the version number of the example below.
‘ Example VBScript to display when an object last logged on
‘ Version 5.1 – September 2005
Dim objOU, objUser, objRootDSE, objLastLogon
Dim strContainer, strDNSDomain, strMove, strLDAP
Dim strLDAPQ, strQ, arrClass, strPerson, strDisable
Dim intLastLogonTime, intKeyDate, intAging, intDeleteDate
Dim objConnection, objCommand, objRecordSet, objNewOU, objMoveUser
Dim strUser, strSourceOU, strDestinationOU
Dim intCounter, intDisabled, intMoved
intDisabled = 0
intMoved = 0
‘ Note: Please change these variables to reflect your domain
intAging = 60 ‘ How old before delete? Number of days
strSourceOU = "OU=Accounts, "
strDestinationOU = "OU=PayRoll,"
strDisable = lcase("Computer") ‘Computer or Person (not User)
‘ Binding to Active Directory. Note strSourceOU
Set objRootDSE = GetObject("LDAP://RootDSE")
strDNSDomain = objRootDSE.Get("DefaultNamingContext")
strDNSDomain = strSourceOU & strDNSDomain
set objOU =GetObject("LDAP://" & strDNSDomain )
‘ Section to calculate variables for the MoveUser() Sub
‘ Getting the speech marks around the LDAP names is tricky.
strQ = "’" & strDisable & "’"
strLDAPQ = "’LDAP://" & strDNSDomain & "’"
‘ Extra comma is needed for strSourceOU but not strDestinationOU
‘ This comma drove me mad.
strSourceOU = "," & strSourceOU & objRootDSE.Get("DefaultNamingContext")
strDestinationOU = strDestinationOU & objRootDSE.Get("DefaultNamingContext")
For Each objUser In objOU
Set objLastLogon = objUser.Get("lastLogon")
intLastLogonTime = objLastLogon.HighPart * (2^32) + objLastLogon.LowPart
intLastLogonTime = intLastLogonTime / (60 * 10000000)
intLastLogonTime = intLastLogonTime / 1440
‘ Key date test statement. Are they older than intAging?
intDeleteDate = date()-intAging
intKeyDate ="#" & intDeleteDate & "#"
‘ Wscript.Echo "Date for deletion " & intKeyDate ‘ Check date calculation
if intKeyDate > intLastLogonTime Then
‘ Call the subroutines Disable(), then MoveUser()
if objUser.userAccountControl = 514 Then
if objUser.Name <> "" then
Wscript.Echo objUser.Name & " ‘s last logon time: " _
& intLastLogonTime + #1/1/1601#
WScript.Echo intDisabled & " Accounts disabled" _
& vbcr & intMoved & " Accounts moved "
‘ Sub Routines Section
‘ Note use of objCategetory not objClass. Heavy use of Split.
If objUser.userAccountControl <> 514 then
arrClass = split(objUser.objectCategory,",")
strPerson = split(arrClass(0),"=")
strPerson(1) = lcase(strPerson(1))
‘ Wscript.Echo "strLDAP " & strPerson(1)
‘ If we have the right type of object, then disable.
If strPerson(1) = strDisable then
objUser.Put "userAccountControl", 514
Wscript.Echo objUser.name & " is disabled " & strPerson(1)
intDisabled = intDisabled + 1
‘ MoveUser.vbs – A very difficult routine
‘ This is a tricky section. Makes a new link to Active Directory
Const ADS_SCOPE_SUBTREE = 2
Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCOmmand.ActiveConnection = objConnection
objCommand.CommandText = _
"Select Name, Location from " & strLDAPQ _
& "where objectCategory=" & strQ
objCommand.Properties("Page Size") = 1000
objCommand.Properties("Timeout") = 30
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
objCommand.Properties("Cache Results") = False
‘ Treats the User objects as a set of record cards
Set objRecordSet = objCommand.Execute
if objRecordSet.EOF <> True Then
‘ Get all the records
Do Until objRecordSet.EOF
strUser = objRecordSet.Fields("Name").Value
strUser = "CN="& strUser
‘ This script features lots of IFs. This look ensures only
‘ disabled accounts, which match the user name get moved.
if objUser.name = strUser then
if objUser.userAccountControl = 514 then
‘ Wscript.Echo "User Name: " & strUser & vbcr & objUser.name
Set objNewOU = GetObject("LDAP://" & strDestinationOU)
Set objMoveUser = objNewOU.MoveHere _
("LDAP://" & strUser & strSourceOU, strUser)
intMoved = intMoved +1
‘ End of disable then move accounts VBScript
Note 1: Firstly, seek information from Remarks in the actual script.
Note 2: Refresh your memory of the lastLogon attribute in Ezine 85.
Note 3: The secret of understanding this script is to break it down into four sections.
Note 3a: Amend your variables, beginning with intAging at line 20.
Note 3b: Check the logic of comparing lastLogon with intKeyDate lines 46-59
Note 3c: Realize that userAccountControl = 514 means disable the account.
Note 3d: Examine the MoveUser() sub Routing in great detail.
Note 4: Make a point of finding, and understanding each of the ‘If…Then…End If’ constructions.
Note 5: Make a special study of objectCategory line 71 because you can use the ‘Person’ value in other VBScripts. ObjectCategory is a different LDAP property from objectClass and I use it to distinguish between users and computers. You could substitute objectClass, but be aware that ‘User’ is a value of both Users and Computers. Strange but true. The benefit of objectClass is that you can filter with ‘Computer’ and ‘Person’. Remember that ‘Person’ is a value held only by User objects. (Check with ADSI Edit).
Note 6: I have been smitten with the Split() function, and I employ it here to extract the ‘Person’ or ‘Computer’ value from the objectCategory. It goes without saying that there are other ways of filtering the objects, but I am taken with Split, and that is what I used to select one type of Active Directory account.
Note 7: As I hinted earlier, I had forgotten that scripting the process to move users was so difficult. Regard Sub MoveUser() (line 87) as a ‘black’ box for shovelling disabled objects into a holding pen. In real life, the idea would be to examine them and check that they were the correct object before running another script to delete these old users or computers.
Note 8: This script also features a one-liner to get today’s date. Examine the simple but effective date function and see why the script always detects accounts that are older than 60 days from when you run the script. See line 47:
intDeleteDate = date()-intAging
This is an advanced script. It’s goals are to identify old Active Directory accounts, disable them and finally to move the User or Computer accounts to a different OU. The second OU acts as a holding pen until you are ready to delete these old objects.