Computers & ProgrammingComputers & NetworkingWindows Server

Locating Inactive Computer Accounts in AD

When it comes to computer accounts, unfortunately, they seem to linger around even when computers are removed from the domain and retired. Over time, the number of computer accounts in an Active Directory (AD) domain will get larger and larger. It is very difficult to use the out-of-box tools available when you install AD to manage these inactive accounts.

There are no built-in processes monitoring these stale objects and removing them when they are no longer being used. So the question you may be asking is how I effectively remove these inactive objects. First, we need to understand what categorizes an “inactive computer” object. Well, it turns out that these Windows computers will automatically reset their “computer password” at least once every thirty days.

While this number can be modified, it is generally not recommended. Since we know that the computer’s password age attribute is automatically modified at least once every thirty days, all we have to do is query Active Directory and apply some logic in code. There are various ways to extract and handle the data from AD, but I think using VBScript for this solution works quite nicely.

Here is an example of code that will query your domain, at any level (OU) of the structure, and allows you to choose the maximum age, and whether or not to run in DELETE or LIST mode. Choosing 60 or 90 days for a maximum age is pretty safe as it accounts for staff that may be on vacation or traveling for an extended period of time.

'***This script can be used to locate inactive computer accounts in an Active Directory domain*** 

'***The script can be run in 2 modes, DELETE and LIST***
'***DELETE mode will delete computer objects!!! Use Caution***
'***LIST mode will output to a file. However, it is easy to modify the script to***
'***write to a database.*** 

'***Set the scope of the script by using the correct starting point in the structure***
'***Change the cnPath to your needs. It can be set to the domain object level or OU***
cnPath = "OU=ouName,DC=domain,DC=com" 

'***Computers in the domain by default update their passwords once every thirty days***
'***When you run the program, this number should be greater than 30 **
'***targetting systems that may still be active.***
limit = inputbox("Enter maximum password age")
DeleteMode = Msgbox("Do you wish to run in DELETE mode?", vbYesNo)

'***Setup Log file***
Set fso = CreateObject("Scripting.FileSystemObject") 

'***The 8 in this line will append to an existing file, replace with a 2 to override***
set txtStream = fso.OpenTextFile("System.txt", 8, True)
txtStream.WriteLine "*** Ran on " & Date & " ***"

'***Setup ADSI connection and populate ADSI Collection***
Set objADOconnADSI = CreateObject("ADODB.Connection")
objADOconnADSI.Open "Provider=ADsDSOObject;"
Set objCommandADSI = CreateObject("ADODB.Command")
objCommandADSI.ActiveConnection = objADOconnADSI 

'***There is a 1000 object default if these next 2 lines are omitted***
objCommandADSI.Properties("Size Limit")= 10000
objCommandADSI.Properties("Page Size")= 10000
objCommandADSI.Properties("Sort on") = "sAMAccountName"
objCommandADSI.CommandText = "<LDAP://" & cnPath & ">;(objectClass=computer);sAMAccountName,pwdLastSet,name,distinguishedname;subtree"
Set objRSADSI = objCommandADSI.Execute 

'***Loop through record set and compare password age***
do while NOT objRSADSI.EOF
if not isnull(objRSADSI.Fields("distinguishedname")) and objRSADSI.Fields("distinguishedname") <> "" then
objDate = objRSADSI.Fields("PwdLastSet")

'***Go to the function to translate the PwdLastSet value from AD for the machine account.***
dtmPwdLastSet = Integer8Date(objDate, lngBias) 

'***calculate the current age of the password.***
DiffADate = DateDiff("d", dtmPwdLastSet, Now)

'***Is the password older than the specified age?***
if DiffADate > int(limit) then

'***Is the script running in DELETE mode?***
if DeleteMode = vbYes then

'***Ask if this machine account should be deleted from AD.***
'*** Uncomment next line and comment the following line to not be prompted for deletion of each object.***
'intReturn = vbYes
intReturn = Msgbox("Are you sure you want to DELETE this computer account?", vbYesNo, "Delete " & objRSADSI.Fields("name")) 

'***If YES, then write to log file and then DELETE it else just log it.***
If intReturn = vbYes Then
Set objComputer = GetObject("LDAP://" & objRSADSI.Fields("distinguishedname"))
strComputer = objComputer.CN

txtStream.WriteLine objRSADSI.Fields("name") & "," & dtmPwdLastSet & "," & DiffADate & ",DELETED"
objComputer.DeleteObject (0)
else
txtStream.WriteLine objRSADSI.Fields("name") & "," & dtmPwdLastSet & "," & DiffADate & ",NOT DELETED"
End If
else

'***If running in LIST mode then just write entry to log file and move on to next record.***
txtStream.WriteLine objRSADSI.Fields("name") & "," & dtmPwdLastSet & "," & DiffADate & ",LIST ONLY"
end if
end if
end if
objRSADSI.MoveNext
loop
wscript.echo "Process Completed."
 
'*** Function to convert Integer8 (64-bit) value to a date, adjusted for local time zone.***
Function Integer8Date(objDate, lngBias)
Dim lngAdjust, lngDate, lngHigh, lngLow
lngAdjust = lngBias
lngHigh = objDate.HighPart
lngLow = objdate.LowPart

'***Account for IADslargeInteger property methods.***
If lngLow < 0 Then
lngHigh = lngHigh + 1
End If
If (lngHigh = 0) And (lngLow = 0) Then
lngAdjust = 0
End If
lngDate = #1/1/1601# + (((lngHigh * (2 ^ 32)) _
+ lngLow) / 600000000 - lngAdjust) / 1440
Integer8Date = CDate(lngDate)
End Function 

'***EndofScript***

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top