windowsrecursionvbscriptactive-directorynested-groups

VBScript - Retrieving a user's nested groups and getting rid of repetitions


For my work, I have to write a script in VBScript that retrieves a list of ALL groups a user belongs to, including nested groups, and take out nested groups that would be repeated throughout the list (as well as indent nested groups, further indent nested groups of nested groups, etc.)

I found a script that fetches the entire list of groups a user belongs to by Monimoy Sanyal on gallery.technet.microsoft.com, and tried to adapt it to my needs. Here is the script as edited by me:

Option Explicit

Const ForReading = 1, ForWriting = 2, ForAppend = 8

Dim ObjUser, ObjRootDSE, ObjConn, ObjRS
Dim GroupCollection, ObjGroup
Dim StrUserName, StrDomName, StrSQL
Dim GroupsList
Dim WriteFile

GroupsList = ""

Set ObjRootDSE = GetObject("LDAP://RootDSE")
StrDomName = Trim(ObjRootDSE.Get("DefaultNamingContext"))
Set ObjRootDSE = Nothing

StrUserName = InputBox("Enter user login", "Info needed", "")
StrSQL = "Select ADsPath From 'LDAP://" & StrDomName & "' Where ObjectCategory = 'User' AND SAMAccountName = '" & StrUserName & "'"

Set ObjConn = CreateObject("ADODB.Connection")
ObjConn.Provider = "ADsDSOObject":  ObjConn.Open "Active Directory Provider"
Set ObjRS = CreateObject("ADODB.Recordset")
ObjRS.Open StrSQL, ObjConn
If Not ObjRS.EOF Then
    ObjRS.MoveLast: ObjRS.MoveFirst
    Set ObjUser = GetObject (Trim(ObjRS.Fields("ADsPath").Value))
    Set GroupCollection = ObjUser.Groups
    WScript.Echo "Looking for groups " & StrUserName & " is member of. This may take some time..."
    'Groups with direct membership, and calling recursive function for nested groups
    For Each ObjGroup In GroupCollection
        GroupsList = GroupsList + ObjGroup.CN + VbCrLf
        CheckForNestedGroup ObjGroup
    Next
    Set ObjGroup = Nothing: Set GroupCollection = Nothing:  Set ObjUser = Nothing
    'Writing list in a file named Groups <username>.txt
    Set WriteFile = WScript.CreateObject("WScript.Shell")
        Dim fso, f
        Set fso = CreateObject("Scripting.FileSystemObject")
        Set f = fso.OpenTextFile("Groups " & StrUserName & ".txt", ForWriting,true)
        f.write(GroupsList)
        f.Close
        WScript.Echo "You can find the list in the Groups " &StrUserName & ".txt file that has just been created."
Else
    WScript.Echo "Couldn't find user " & StrUserName & " in AD."
End If
ObjRS.Close:    Set ObjRS = Nothing
ObjConn.Close:  Set ObjConn = Nothing

'Recursive fucntion
Private Sub CheckForNestedGroup(ObjThisGroupNestingCheck)
    On Error Resume Next
    Dim AllMembersCollection, StrMember, StrADsPath, ObjThisIsNestedGroup
    AllMembersCollection = ObjThisGroupNestingCheck.GetEx("MemberOf")
    For Each StrMember in AllMembersCollection
        StrADsPath = "LDAP://" & StrMember
        Set ObjThisIsNestedGroup = GetObject(StrADsPath)
        'Not include a group in the list if it is already in the list (does not work for some reason?)
        If InStr(GroupsList, ObjThisIsNestedGroup.CN) = 0 Then
            GroupsList = GroupsList + vbTab + ObjThisIsNestedGroup.CN + VbCrLf
        End If
        'Recursion to look for nested groups and nested groups of nested groups and nested groups of nested groups of nested groups and...
        CheckForNestedGroup ObjThisIsNestedGroup
    Next
    Set ObjThisIsNestedGroup = Nothing: Set StrMember = Nothing:    Set AllMembersCollection = Nothing
End Sub

Rather than display a popup for EACH group found like the original script did, I store the entire list in a String (GroupsList = GroupsList + ObjGroup.CN + VbCrLf for direct groups, GroupsList = GroupsList + vbTab + ObjThisIsNestedGroup.CN + VbCrLf for nested groups in the recursive function,) and once the script is done looking for groups, it saves the String in a file. (f.write(GroupsList))

My problem is, despite the If "InStr(GroupsList, ObjThisIsNestedGroup.CN) = 0 in the recursive function, I still find myself with tons of repetitions throughout the results (our AD is kind of bloated with groups, it is a huge structure with many nested groups and nested groups in other nested groups, etc.) and the check doesn't seem to notice that ObjThisIsNestedGroup.CN is already found in GroupsList. And I have no idea how to implement the indentation properly.

Any ideas? I'm rather new at scripting, so forgive me if the answer is obvious.


Solution

  • I found the solution for both problems. Well, the first problem I'm not sure how I fixed since I only reverted the code after making a modification and then it was magically working. For the increasing indentation, I declared a global variable named RecurCount that I increment every time I call the recursive procedure, and decrease after the procedure. Then, within the procedure, I added a For i = 0 to RecurCount that adds a varying number of vbTabs depending on RecurCount.

    Here's the working procedure:

    Private Sub CheckForNestedGroup(ObjThisGroupNestingCheck)
        On Error Resume Next
        Dim AllMembersCollection, StrMember, StrADsPath, ObjThisIsNestedGroup, TabAdd, i 
        AllMembersCollection = ObjThisGroupNestingCheck.GetEx("MemberOf")
        For Each StrMember in AllMembersCollection
            If StrMember <> "" Then
                StrADsPath = "LDAP://" & StrMember
                Set ObjThisIsNestedGroup = GetObject(StrADsPath)
                'If InStr(GroupsList, ObjThisIsNestedGroup.CN) = 0 Then (Uncomment this If and indent lines below to remove groups already in the list)
                TabAdd = ""
                For i = 0 to Recurcount
                    TabAdd = TabAdd & vbTab
                Next
                GroupsList = GroupsList & TabAdd & " " & ObjThisIsNestedGroup.CN & VbCrLf
                'End If
                'Recursion to include nested groups of nested groups
                Recurcount = Recurcount + 1
                CheckForNestedGroup ObjThisIsNestedGroup
                Recurcount = Recurcount - 1
            End If
        Next
        Set ObjThisIsNestedGroup = Nothing: Set StrMember = Nothing:    Set AllMembersCollection = Nothing
    End Sub
    

    Don't forget to Dim Recurcount in the main script, and to make it 0 right before calling CheckForNestedGroup for the first time.