RE: Scavanging retired machine accounts

From: Ron Rosenkoetter (RonRosenkoetter_at_discussions.microsoft.com)
Date: 08/06/04


Date: Fri, 6 Aug 2004 14:37:02 -0700

Here's a script I wrote a while back that does exactly what you want.

On Error Resume Next

  
'Set Log File Name
    LogFileName = "c:\CheckComputerAccountsInAD.log"
  
'Set File Constants
  Const ForReading = 1
  Const ForWriting = 2
  Const ForAppending = 8
  
'Setting the Arguments
  Set objArgs = Wscript.Arguments
  If ObjArgs.Count > 0 Then
    Arg1 = objArgs(0)
  Else
    wscript.echo "Please specify all required arguments. Run this script
again "
    wscript.echo "with a /? as the first argument to see the help file"
    Wscript.Quit
  End If

'Checking if Help file is needed
  HELP = 0

  If Arg1 = "help" Then HELP = 1
  If Arg1 = "/?" Then HELP = 1
  If Arg1 = "?" Then HELP = 1

' Doubles as Help document and Purpose of script REMARKS
  If HELP = 1 then
 
    wscript.echo " ******************************"
    wscript.echo " * Script: CheckComputerAccountsInAD.vbs"
    wscript.echo " * Creation Date: 5-7-2003"
    wscript.echo " * Date Last Modified: 2-12-2004"
    wscript.echo " * Author: Ron Rosenkoetter"
    wscript.echo " * E-mail: Ronald.Rosenkoetter@gentiva.com"
    wscript.echo " *"
    wscript.echo " * Description: This script will query the specified"
    wscript.echo " * domain (will default to the current domain if none"
    wscript.echo " * is specified), looking for computer accounts that"
    wscript.echo " * haven't been modified in the specified number of
months"
    wscript.echo " * or weeks (will default to 3 months if none is
specified)"
    wscript.echo " * This script will either list, disable, or move these"
    wscript.echo " * accounts depending on which option is chosen"
    wscript.echo " *"
    wscript.echo " * Note: Computer accounts change their passwords every
30 days"
    wscript.echo " * After 60 days, the computer account will not be able "
    wscript.echo " * to authenticate on the domain until its password is
reset"
    wscript.echo " *"
    wscript.echo " * Limitations: "
    wscript.echo " * List: This script will only list disabled computers"
    wscript.echo " * if weeks are chosen as a timeframe. "
    wscript.echo " * Disable: This script will not disable a computer
account"
    wscript.echo " * based on weeks inactive. An account must be
inactive"
    wscript.echo " * for at least 2 months before this script will
disable it"
    wscript.echo " * Move: This script will move an account to the
DeletePending OU"
    wscript.echo " * (It will created it if it doesn't already exist)
based on the "
    wscript.echo " * number of weeks or months the computer has been
DISABLED."
    wscript.echo " * This script will not move a computer account that
isn't"
    wscript.echo " * already disabled."
    wscript.echo " *"
    wscript.echo " * Usage: CheckComputerAccountsInAD.vbs [options]"
    wscript.echo " *"
    wscript.echo " * /l: List"
    wscript.echo " * /ds: Disable"
    wscript.echo " * /move: Move (This script will only move disabled
computers)"
    wscript.echo " * [one of these is required]"
    wscript.echo " *"
    wscript.echo " * /d: Domain (ADsPath form)"
    wscript.echo " * [defaults to current domain]"
    wscript.echo " * /m: Number of Months of inactivity to check for"
    wscript.echo " * /w: Number of Weeks of inactivity to check for"
    wscript.echo " * [defaults to 3 months]"
    wscript.echo " *"
    wscript.echo " * Example: CheckComputerAccountsInAD.vbs /l:"
    wscript.echo " * This will list all computers in the current domain"
    wscript.echo " * that haven't been modified in 3 months"
    wscript.echo " *"
    wscript.echo " * Example: CheckComputerAccountsInAD.vbs /m:4 /ds:"
    wscript.echo " * This will disable all computers in the current domain"
    wscript.echo " * that haven't been modified in 4 months"
    wscript.echo " *"
    wscript.echo " * Example: CheckComputerAccountsInAD.vbs /w:2 /move:"
    wscript.echo " * This will move all computers in the current domain"
    wscript.echo " * that have been DISABLED for 2 weeks to the
DeletePending OU"
    wscript.echo " *"
    wscript.echo " ******************************"
  
  Wscript.Quit
  End If
  
'Make sure required options are specified
  If (NOT Wscript.Arguments.Named.Exists("l")) and (NOT
Wscript.Arguments.Named.Exists("ds")) _
    and (NOT Wscript.Arguments.Named.Exists("move")) Then
    wscript.echo "Please specify all required arguments. Run this script
again "
    wscript.echo "with a /? as the first argument to see the help file"
    Wscript.Quit
  End If
  
'Set Option Variables
  Domain = Wscript.Arguments.Named("d")
  MonthsSpecified = CInt(Wscript.Arguments.Named("m"))
  WeeksSpecified = CInt(Wscript.Arguments.Named("w"))
  
  

'Create the File System Object
  Set objFSO = CreateObject("Scripting.FileSystemObject")
  
'Open a log file for printing results
  Wscript.Echo "Opening Log File"
  Set LogFile = objFSO.OpenTextFile(LogFileName,ForWriting,True)
  If Err.Number <> 0 then
    Wscript.Echo "Unable to open the " & LogFileName
    Wscript.Quit
  End If
  
'Dimension Array Variables
  Dim arrUserList()

'Set Number of Months if not specified
  If (NOT Wscript.Arguments.Named.Exists("m")) AND (NOT
Wscript.Arguments.Named.Exists("w")) Then
    MonthsSpecified = 3
  End If

'Get Domain name if not specified
  If NOT Wscript.Arguments.Named.Exists("d") Then
    Set RootDSE = GetObject("LDAP://rootDSE")
    Domain = RootDSE.Get("DefaultNamingContext")
  End If
  
'Searching Active Directory
'Create the Connection object and open it
  Set objConnection = CreateObject("ADODB.Connection")
  objConnection.Open "Provider=ADsDSOObject;"

'Create the Command object and set its ActiveConnection to the Connection
object
  Set objCommand = CreateObject("ADODB.Command")
  objCommand.ActiveConnection = objConnection

'Set the CommandText property of the Command object. Start at the domain
level, and
'pull back a list of every user's account name and distinguished name
    objCommand.CommandText = _
       "<LDAP://" & Domain & ">;(objectCategory=Computer);" & _
         "Name,ADsPath,DistinguishedName,whenChanged;subtree"
  
'To search for more than 1000 records, add the following line. This will
return ALL objects
'in the search.
  objCommand.Properties("Page Size") = 1000
  
'Sort by name
  objCommand.Properties("Sort On") = "whenChanged"

'Execute the Command and place the results in the RecordSet object
  Message "Retrieving a list of all computer accounts in " & Domain
  Set objRecordSet = objCommand.Execute
  CheckForErrorQuit
  
  Message vbCrLf

'Check to see which action the script should perform (List, Disable, Delete)

'***************
'List PCs that match the criteria
  If (Wscript.Arguments.Named.Exists("l")) AND (NOT
Wscript.Arguments.Named.Exists("ds")) _
    AND (NOT Wscript.Arguments.Named.Exists("move")) Then

'Check to see if we're working with weeks or months
    If Wscript.Arguments.Named.Exists("w") Then
      ListComputers WeeksSpecified, "w", "weeks"
    Else
      ListComputers MonthsSpecified, "m", "months"
    End If
  
  End If

'Subrountine ListComputers
  Sub ListComputers (TimeSpecified, TimeCheck, TimePeriod)
     
'Loop through the RecordSet and compare the dates
    Do Until objRecordSet.EOF
        DateDifference =
DateDiff(TimeCheck,objRecordSet.Fields("whenChanged"), Now)
        
        
'If the computer account has not been modified in several months, then it has
'been off-line or disabled all that time (Computer accounts change their
'passwords every 30 days)
        If DateDifference >= TimeSpecified Then
        
'Object has not modified in the time period specified. Check to see if it's
disabled
'or just hasn't checked in"
          Set objComp = GetObject(objRecordSet.Fields("ADsPath"))
          If objComp.UserAccountControl AND 2 Then
            Message objRecordSet.Fields("name") & " has been disabled for
" & _
              DateDifference & " " & TimePeriod
            DisabledCount = DisabledCount + 1
          Else

'How long a computer has been off-line (not updated its password) is really
only
'relevant when talking about months. If we're using a number of weeks as a
'comparison, we're probably only interested in the disabled computer accounts
            If TimeCheck = "m" Then
              Message objRecordSet.Fields("name") & " has not updated " & _
                "its password in " & DateDifference & " " & TimePeriod
              OfflineCount = OfflineCount + 1
            End If
          End If
          
        End If
        objRecordSet.MoveNext
    Loop
    
    Message vbCrLf
    
    Message "Number of PCs that have been disabled for " & _
        TimeSpecified & " " & TimePeriod & ": " & DisabledCount
    If TimeCheck = "m" Then
      Message "Number of PCs that have been off-line for " & _
          TimeSpecified & " " & TimePeriod & ": " & OfflineCount
    End If
    
    objRecordSet.MoveFirst
    DisabledCount = 0
    OffLineCount = 0
  
  End Sub
'***************

'****************
'Disable PCs that match criteria
  If Wscript.Arguments.Named.Exists("ds") Then
   
'There is no point disabling PCs based on how many weeks it's been since the
'computer changed its password. This is something that should be set at the
'month time period (2 at the very least, 3 is better as a minimum)
    If Wscript.Arguments.Named.Exists("w") Then
      Message "Disabling Computers based on weeks is not supported"
      QuitProgram
    End If
    
    If MonthsSpecified < 2 Then
      Message "Disabling Computers for less than 2 months of inactivity is
not supported"
      QuitProgram
    End If

'Loop through the RecordSet and compare the dates
    Do Until objRecordSet.EOF
        NumberOfMonths = DateDiff("m",objRecordSet.Fields("whenChanged"), Now)
        
'If the computer account has not been modified in several months, then it has
'been off-line all the time (Computer accounts change their passwords every
30 days)
        If NumberOfMonths >= MonthsSpecified Then
          Set objComp = GetObject(objRecordSet.Fields("ADsPath"))
          
          Select Case DisableComputerAccount (objComp)
            Case 0
              Message "Disabled " & objRecordSet.Fields("DistinguishedName")
              Message " Password has not been updated in " & NumberofMonths
& _
                  " months" & vbCrlf
              Count = Count + 1
            Case 1
              Message "Unable to disable " &
objRecordSet.Fields("DistinguishedName") & " <------"
              Message " Password has not been updated in " & NumberofMonths
& _
                  " months" & vbCrlf
            Case 2
              Message objRecordSet.Fields("DistinguishedName") & " already
disabled"
          End Select
          
        End If
        objRecordSet.MoveNext
    Loop
    
    Message "Number of PCs that have been disabled: " & Count
    objRecordSet.MoveFirst
    Count = 0
  
  End If
'***************

'****************
'Move PCs that match criteria
  If Wscript.Arguments.Named.Exists("move") Then
  
'Check to see if we're working with weeks or months
    If Wscript.Arguments.Named.Exists("w") Then
      DeleteComputers WeeksSpecified, "w", "weeks"
    Else
      DeleteComputers MonthsSpecified, "m", "months"
    End If
    
  End If
  
'Subrountine DeleteComputers
  Sub DeleteComputers (TimeSpecified, TimeCheck, TimePeriod)
  
'Make sure that deletion is desired
    Wscript.Echo "Are you SURE you want to move all computers that have been
disabled"
    Wscript.Echo "for more than " & TimeSpecified & " " & TimePeriod & "?"
    Wscript.StdOut.Write "Enter Yes if you do, anything else to cancel: "
    Input = Wscript.StdIn.ReadLine
    If ucase(Input) = "YES" Then

'Create (if it doesn't already exist), a DeletePending OU
    Set objDomain = GetObject("LDAP://" & Domain)
    Select Case CreateOU(objDomain,"DeletePending", _
           "An OU that holds disabled computers before they are deleted")
      Case 0
        Message vbCrLf & "Created DeletePending OU"
      Case 2
        'Already exists, do nothing
      Case Else
        Message vbCrLf & "Unable to create DeletePending OU"
        QuitProgram
    End Select

'Loop through the RecordSet and compare the dates
      Do Until objRecordSet.EOF
        DateDifference =
DateDiff(TimeCheck,objRecordSet.Fields("whenChanged"), Now)
        
'If the computer account has not been modified in several months, then it has
'been off-line all the time (Computer accounts change their passwords every
30 days)
        If DateDifference >= TimeSpecified Then
          Set objComp = GetObject(objRecordSet.Fields("ADsPath"))

'Check to see if Computer account is disabled. This script will not delete any
'computer accounts that are enabled.
          If objComp.UserAccountControl AND 2 Then
          
            Select Case MoveAccountToOU("OU=DeletePending," & Domain,objComp)
              Case 0
                Message "Moved " & objRecordSet.Fields("DistinguishedName")
& vbCrLf
                Count = Count + 1
              Case Else
                Message "Unable to move " &
objRecordSet.Fields("DistinguishedName") & " <------" & _
                    vbCrLf
                
            End Select
          
          
          End If
              
        End If
        objRecordSet.MoveNext
      Loop
    
      Message "Number of PCs that have been moved to the DeletePending OU: "
& Count
      objRecordSet.MoveFirst
      Count = 0
    
    Else
      QuitProgram
    End If
  
  End Sub
'***************

 QuitProgram

'#############################
'Function DisableComputerAccount

'Variables
  'objComputerName - object referencing the ComputerName
  
'Returns
  '0 if account is disabled successfully
  '1 if function was unable to disable the account
  '2 if account was already disabled.

  Function DisableComputerAccount(objComputerName)
  
    On Error Resume Next

'Create a Shell object in order to retrieve the username of the user
'running this script.
    Set objWS = WScript.CreateObject("WScript.Shell")
  
'Access Environment variables using ExpandEnvironmentStrings()
    UserName = objWS.ExpandEnvironmentStrings("%USERNAME%")

'UserAccountControl returns a bitmask that references many user account
'settings
    UAC = objComputerName.userAccountControl

'2 (0010) is the bit that is set to indicate a disabled account
    If UAC AND 2 Then
      DisableComputerAccount = 2 'already disabled
    Else

'Set the (0010) bit in order to disable the account
      objComputerName.Put "userAccountControl", UAC OR 2
      objComputerName.Put "Description", "Disabled by " & UserName & " on "
& Date
      objComputerName.SetInfo
      
      If Err.Number = 0 Then
        DisableComputerAccount = 0
      Else
        DisableComputerAccount = 1
        Err.Clear
      End If
    
    End If
  
  End Function
      
'#############################

'#############################

'Function MoveAccountToOU

'Variables
  'OUDN = Distinugished Name Path (i.e. OU=OUName,DC=domain,DC=com)
  'objAccountName (User object or Computer object)
  
'Returns
  '0 if account is moved to the specified OU successfully
  '1 if function is unable to move the account to the specified OU
  
  Function MoveAccountToOU(OUDN,objAccountName)
    
    Set objOU = GetObject("LDAP://" & OUDN)
    objOU.MoveHere objAccountName.ADsPath, "cn=" & objAccountName.cn
    If Err.Number = 0 Then
      MoveAccountToOU = 0
    Else
      MoveAccountToOU = 1
      Err.Clear
    End If
 
       
  End Function
 
'#############################

'#############################

'Function CreateOU
 
'Variables
  'objParent - Parent Object (Domain or another OU)
  'OUName
  'OUDescription
    
'Returns
  '0 if the OU is created successfully
  '1 if function is unable to create the OU
  '2 if the OU already exists.
   
  Function CreateOU(objParent,OUName,OUDescription)
   
'Check to see if the OU already exists
    For Each OU in objParent
      If ucase(OU.name) = ucase("OU=" & OUName) Then
        CreateOU = 2
        NoMatch = 1
      End If
    Next
      
    If NoMatch <> 1 Then
     
      Set NewOU = objParent.Create("organizationalUnit", "OU=" & OUName)
      NewOU.Description = OUDescription
      NewOU.SetInfo
      If Err.Number = 0 Then
        CreateOU = 0
      Else
        CreateOU = 1
        Err.Clear
      End If
  
    End If
    
  End Function

'#############################

'#############################

'Function CheckForErrorQuit

  Function CheckForErrorQuit()
    If Err.Number <> 0 then
      Message "Error #" & Err.Number & " - " & Err.Description
      CheckForErrorQuit = Err.Description
      QuitProgram
    End If
  End Function
  
'#############################

'Function CheckForErrorClear

  Function CheckForErrorClear()
    If Err.Number <> 0 then
      Message "Error # " & Err.Number & " - " & Err.Description
      CheckForErrorClear = Err.Description
      Err.Clear
    End If
  End Function
  
'#############################

'Subroutine Message

  Sub Message (Text)
    Wscript.Echo Text
    LogFile.WriteLine Text
  End Sub
  
'#############################

'Subrountine QuitProgram

  Sub QuitProgram
    
    LogFile.Close
    
    Wscript.Echo " "
    Wscript.Echo "****************************************************"
    Wscript.Echo "* Use notepad " & LogFileName & " to see a log of these
results"
    Wscript.Echo "****************************************************"
  
    Wscript.Quit
    
  End Sub
  
'############################

"Rully Kusuma" wrote:

> Is there any scripts that would scavange retired machine accounts based on
> their last contact with active directory?
>
> Thank you in advance.
>
>
>



Relevant Pages

  • Re: Problems with Disabled Computer Accounts
    ... I had a similar situation and after rebooting I was unable to login.. ... Recently I discovered that after Disabling one of my computer accounts ... if I disable a Computer account I should not be ... Am I just under the wrong impression on how Disabling ...
    (microsoft.public.windows.server.active_directory)
  • Re: Problems with Disabled Computer Accounts
    ... This means working local only on the machine, without domain access. ... Recently I discovered that after Disabling one of my computer accounts ... if I disable a Computer account I should not be ... Am I just under the wrong impression on how Disabling ...
    (microsoft.public.windows.server.active_directory)
  • Re: Error Code!
    ... > When I need to force logoff for a user, after the disabling of the ... > account, nothing is work. ... If you have tried to script the logging off of the remote user, ...
    (microsoft.public.scripting.vbscript)
  • Re: Create account with an Expiration Date
    ... the account cannot be used. ... corresponds to the expiration date of the account. ... creating an account with your script and have it expire tomorrow. ... For the disabling of accounts though, I cant have this done manually ...
    (microsoft.public.windows.server.active_directory)
  • Re: Script to enable/disable domain account?
    ... included, script is at bottom): ... Recipe 6.12 Enabling and Disabling a User ... right-click on the user and select Enable Account to ... strDisableAccount = FALSE ...
    (microsoft.public.scripting.vbscript)