Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Pruning old users from AD

Status
Not open for further replies.

Leozack

MIS
Oct 25, 2002
867
GB
Hi all. I've been looking into a task I have to carry out on a large AD network whereby they want to do things to users who haven't been seen on the network for X time (eg 6 months). Initially they want to mark the account desc with a prefixed [D] and disable the account - also stuff like hiding the mailbox from the exchange mail list. A week later they want to move the account to a disabled OU and do other stuff like remove them from groups etc.

I've found a tool called oldcmp which I think was just for computers. I also found a script for ADLastSeenUsers which I modified to be users or computers and to sort alphabetically in a text file. I haven't found much else to be helpful.

My main problem is I'm not sure I can trust all the users marked as NEVER last seen, or the last seen dates. I'm also unsure how to do what needs doing rather than just writing the name/lastseentime to a textfile which is what I do currently.

Mainly I'm thinking people must have to do stuff like this all the time so there should be a tool out there for it or good tutorials to follow for DIYing the scripts etc.

Does anyone have any ideas? TIA

_________________________________
Leozack
Code:
MakeUniverse($infinity,1,42);
 
Here is the last logon script I mentioned
Code:
' LastLogon.vbs
' VBScript program to determine when each user in the domain last logged
' on.
'
' Edited to sort alphabetically and output text file by GM@Mouchel June 11
'
' ----------------------------------------------------------------------
' Copyright (c) 2002-2010 Richard L. Mueller
' Hilltop Lab web site - [URL unfurl="true"]http://www.rlmueller.net[/URL]
' Version 1.0 - December 7, 2002
' Version 1.1 - January 17, 2003 - Account for null value for lastLogon.
' Version 1.2 - January 23, 2003 - Account for DC not available.
' Version 1.3 - February 3, 2003 - Retrieve users but not contacts.
' Version 1.4 - February 19, 2003 - Standardize Hungarian notation.
' Version 1.5 - March 11, 2003 - Remove SearchScope property.
' Version 1.6 - May 9, 2003 - Account for error in IADsLargeInteger
'                             property methods HighPart and LowPart.
' Version 1.7 - January 25, 2004 - Modify error trapping.
' Version 1.8 - July 6, 2007 - Modify how IADsLargeInteger interface
'                              is invoked.
' Version 1.9 - December 29, 2009 - Output "Never" if no date.
' Version 1.10 - November 6, 2010 - No need to set objects to Nothing.
'
' Because the lastLogon attribute is not replicated, every Domain
' Controller in the domain must be queried to find the latest lastLogon
' date for each user. The lastest date found is kept in a dictionary
' object. The program first uses ADO to search the domain for all Domain
' Controllers. The AdsPath of each Domain Controller is saved in an
' array. Then, for each Domain Controller, ADO is used to search the
' copy of Active Directory on that Domain Controller for all user
' objects and return the lastLogon attribute. The lastLogon attribute is
' a 64-bit number representing the number of 100 nanosecond intervals
' since 12:00 am January 1, 1601. This value is converted to a date. The
' last logon date is in UTC (Coordinated Univeral Time). It must be
' adjusted by the Time Zone bias in the machine registry to convert to
' local time.
'
' You have a royalty-free right to use, modify, reproduce, and
' distribute this script file in any way you find useful, provided that
' you agree that the copyright owner above has no warranty, obligations,
' or liability for such use.

Option Explicit

Dim objRootDSE, strConfig, adoConnection, adoCommand, strQuery
Dim adoRecordset, objDC
Dim strDNSDomain, objShell, lngBiasKey, lngBias, k, arrstrDCs()
Dim strDN, dtmDate, objDate, objList, strUser
Dim strBase, strFilter, strAttributes, lngHigh, lngLow
'-----------------------------------------v
'Open up the path to save the information into a text file
Dim myFSO, WriteStuff
'-----------------------------------------^
' Use a dictionary object to track latest lastLogon for each user.
Set objList = CreateObject("Scripting.Dictionary")
objList.CompareMode = vbTextCompare

' Obtain local Time Zone bias from machine registry.
' This bias changes with Daylight Savings Time.
Set objShell = CreateObject("Wscript.Shell")
lngBiasKey = objShell.RegRead("HKLM\System\CurrentControlSet\Control\" _
    & "TimeZoneInformation\ActiveTimeBias")
If (UCase(TypeName(lngBiasKey)) = "LONG") Then
    lngBias = lngBiasKey
ElseIf (UCase(TypeName(lngBiasKey)) = "VARIANT()") Then
    lngBias = 0
    For k = 0 To UBound(lngBiasKey)
        lngBias = lngBias + (lngBiasKey(k) * 256^k)
    Next
End If

' Determine configuration context and DNS domain from RootDSE object.
Set objRootDSE = GetObject("LDAP://RootDSE")
strConfig = objRootDSE.Get("configurationNamingContext")
strDNSDomain = objRootDSE.Get("defaultNamingContext")

' Use ADO to search Active Directory for ObjectClass nTDSDSA.
' This will identify all Domain Controllers.
Set adoCommand = CreateObject("ADODB.Command")
Set adoConnection = CreateObject("ADODB.Connection")
adoConnection.Provider = "ADsDSOObject"
adoConnection.Open "Active Directory Provider"
adoCommand.ActiveConnection = adoConnection

strBase = "<LDAP://" & strConfig & ">"
strFilter = "(objectClass=nTDSDSA)"
strAttributes = "AdsPath"
strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";subtree"

adoCommand.CommandText = strQuery
adoCommand.Properties("Page Size") = 100
adoCommand.Properties("Timeout") = 60
adoCommand.Properties("Cache Results") = False

Set adoRecordset = adoCommand.Execute

' Enumerate parent objects of class nTDSDSA. Save Domain Controller
' AdsPaths in dynamic array arrstrDCs.
k = 0
Do Until adoRecordset.EOF
    Set objDC = _
        GetObject(GetObject(adoRecordset.Fields("AdsPath").Value).Parent)
    ReDim Preserve arrstrDCs(k)
    arrstrDCs(k) = objDC.DNSHostName
    k = k + 1
    adoRecordset.MoveNext
Loop
adoRecordset.Close

' Retrieve lastLogon attribute for each user on each Domain Controller.
For k = 0 To Ubound(arrstrDCs)
    strBase = "<LDAP://" & arrstrDCs(k) & "/" & strDNSDomain & ">"
'-----------------------------------------------------v
    strFilter = "(&(objectCategory=person)(objectClass=user))"
    ' Above line checks users - below line checks pcs
    'strFilter = "(objectCategory=computer)"
'-----------------------------------------------------^
    strAttributes = "distinguishedName,lastLogon"
    strQuery = strBase & ";" & strFilter & ";" & strAttributes _
        & ";subtree"
    adoCommand.CommandText = strQuery
    On Error Resume Next
    Set adoRecordset = adoCommand.Execute
    If (Err.Number <> 0) Then
        On Error GoTo 0
        Wscript.Echo "Domain Controller not available: " & arrstrDCs(k)
    Else
        On Error GoTo 0
        Do Until adoRecordset.EOF
            strDN = adoRecordset.Fields("distinguishedName").Value
            On Error Resume Next
            Set objDate = adoRecordset.Fields("lastLogon").Value
            If (Err.Number <> 0) Then
                On Error GoTo 0
                dtmDate = #1/1/1601#
            Else
                On Error GoTo 0
                lngHigh = objDate.HighPart
                lngLow = objDate.LowPart
                If (lngLow < 0) Then
                    lngHigh = lngHigh + 1
                End If
                If (lngHigh = 0) And (lngLow = 0) Then
                    dtmDate = #1/1/1601#
                Else
                    dtmDate = #1/1/1601# + (((lngHigh * (2 ^ 32)) _
                        + lngLow)/600000000 - lngBias)/1440
                End If
            End If
            If (objList.Exists(strDN) = True) Then
                If (dtmDate > objList(strDN)) Then
                    objList.Item(strDN) = dtmDate
                End If
            Else
                objList.Add strDN, dtmDate
            End If
            adoRecordset.MoveNext
        Loop
        adoRecordset.Close
    End If
Next

'----------------------------------------------------------v
Set myFSO = CreateObject("Scripting.FileSystemObject")
Set WriteStuff = myFSO.OpenTextFile("ADLastSawUsers.txt", 8, True)
dim objListSorted
Set objListSorted = objList
'SortDictionary objListSorted,dictKey
' Above line sorts by dates (badly, sorts as a string not a date)
' Below line sorts by name
SortDictionary objListSorted,dictKey
dim theName
'----------------------------------------------------------^

' Output latest lastLogon date for each user.
For Each strUser In objListSorted.Keys
    If (objListSorted.Item(strUser) = CStr(#1/1/1601#)) Then
'----------------------------------------------------------v
        'Wscript.Echo strUser & ";Never"
        'WriteStuff.WriteLine(strUser & ";Never")
        theName = Left(Mid(strUser,4),InStr(Mid(strUser,4),",")-1)
        If (Len(theName) < 7) Then
		theName = theName & "," & vbtab & vbtab & vbtab
	ElseIf (Len(theName) < 15) Then
		theName = theName & "," & vbtab & vbtab
        Else
		theName = theName & "," & vbtab
        End If
        WriteStuff.WriteLine(theName & "Never")
    Else
        'Wscript.Echo strUser & ";" & objList.Item(strUser)
        'WriteStuff.WriteLine(strUser & ";" & objListSorted.Item(strUser))
        theName = Left(Mid(strUser,4),InStr(Mid(strUser,4),",")-1)
        If (Len(theName) < 7) Then
		theName = theName & "," & vbtab & vbtab & vbtab
	ElseIf (Len(theName) < 15) Then
		theName = theName & "," & vbtab & vbtab
        Else
		theName = theName & "," & vbtab
        End If
        WriteStuff.WriteLine(theName & objListSorted.Item(strUser))
'----------------------------------------------------------^
    End If
Next

'----------------------------------------------------------v
WriteStuff.Close
SET WriteStuff = NOTHING
SET myFSO = NOTHING
'----------------------------------------------------------^

' Clean up.
adoConnection.Close



'----------------------------------------------------------v
Const dictKey  = 1
Const dictItem = 2

Function SortDictionary(objDict,intSort)
  ' declare our variables
  Dim strDict()
  Dim objKey
  Dim strKey,strItem
  Dim X,Y,Z

  ' get the dictionary count
  Z = objDict.Count

  ' we need more than one item to warrant sorting
  If Z > 1 Then
    ' create an array to store dictionary information
    ReDim strDict(Z,2)
    X = 0
    ' populate the string array
    For Each objKey In objDict
        strDict(X,dictKey)  = CStr(objKey)
        strDict(X,dictItem) = CStr(objDict(objKey))
        X = X + 1
    Next

    ' perform a shell sort of the string array
    For X = 0 to (Z - 2)
      For Y = X to (Z - 1)
        If StrComp(strDict(X,intSort),strDict(Y,intSort),vbTextCompare) > 0 Then
            strKey  = strDict(X,dictKey)
            strItem = strDict(X,dictItem)
            strDict(X,dictKey)  = strDict(Y,dictKey)
            strDict(X,dictItem) = strDict(Y,dictItem)
            strDict(Y,dictKey)  = strKey
            strDict(Y,dictItem) = strItem
        End If
      Next
    Next

    ' erase the contents of the dictionary object
    objDict.RemoveAll

    ' repopulate the dictionary with the sorted information
    For X = 0 to (Z - 1)
      objDict.Add strDict(X,dictKey), strDict(X,dictItem)
    Next

  End If

End Function
'----------------------------------------------------------^

_________________________________
Leozack
Code:
MakeUniverse($infinity,1,42);
 
Yup that's the oldcmp tool I mentioned above. Not sure what the best application of it is though - I ran it set to just make a report but I'll go back and have another look at it. I was hoping someone already performed all these tasks and knew how best to work with it (or another tool/script).

_________________________________
Leozack
Code:
MakeUniverse($infinity,1,42);
 
Having played with oldcmp again I try things like
oldcmp -report -users -age 180 -sort llts
but half the entries have all 0's for llts column. I'm unsure whether lastLogonTimestamp or pwdLastSet are what I'm after, either. I also have no way ot guage the accuracy of the returned results. I'd imagine there are accounts there for services and stuff which need to remain the same whilst normal users are handled - but perhaps this could be filtered via OU targetting or something?

_________________________________
Leozack
Code:
MakeUniverse($infinity,1,42);
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top