A recent project of mine required querying Active Directory for all the groups in which a given user is a member, or if there isn’t a domain then searching the local machine. It also had to return all groups defined in a given domain. In the course of putting the code together, I created a library for future projects that needed to perform such queries. I recently made a major improvement to the code that retrieves a user’s AD groups and thought it would be good to share what I have so far.
It’s based on some bits of code I found on the web. Initially GetADGroupMembership would only return the first-level Active Directory groups the user was in, but that’s no good if the user is in a group that is in another group that is in yet another group that’s the one you really care about. So I added some recursion to bring up the entire group chain. It seems to perform well enough with our domain, which admittedly doesn’t have a great deal of nesting.
Imports System.DirectoryServices
Public Class LDAP
Public Shared Function GetADGroupMembership(ByVal sPath As String, ByVal sUserID As String) As List(Of String)
Dim lstResults As List(Of String)
Dim lstGroups As List(Of String)
Dim lstSubGroups As List(Of String)
Dim sFilter As String
GetADGroupMembership = Nothing
sFilter = "(&(objectClass=user)(samAccountName=" & sUserID & "))"
lstResults = RunSearch(sPath, sFilter, "MemberOf")
lstGroups = New List(Of String)
If lstResults IsNot Nothing Then
For Each result As String In lstResults
lstGroups.Add(result)
lstSubGroups = GetADGroupMembershipByGroup(sPath, result)
For Each sSubGroup As String In lstSubGroups
If Not lstGroups.Contains(sSubGroup) Then
lstGroups.Add(sSubGroup)
End If
Next sSubGroup
Next result
End If
Return lstGroups
End Function
Public Shared Function GetLocalGroupMembership(ByVal sPath As String, ByVal sUserName As String, ByVal sPassword As String) As List(Of String)
Dim de As DirectoryEntry = Nothing
Dim colGroups As Object
Dim lstGroups As List(Of String)
GetLocalGroupMembership = Nothing
Try
de = New DirectoryEntry(sPath, sUserName, sPassword, AuthenticationTypes.Secure)
colGroups = de.Invoke("Groups")
lstGroups = New List(Of String)
For Each o As Object In colGroups
lstGroups.Add(o.Name)
Next o
Return lstGroups
Catch
Throw
Finally
If de IsNot Nothing Then
de.Dispose()
End If
End Try
End Function
Public Shared Function GetADGroups(ByVal sPath As String) As List(Of String)
Dim lstGroups As List(Of String)
Dim sFilter As String
GetADGroups = Nothing
sFilter = "(&(objectClass=group))"
lstGroups = RunSearch(sPath, sFilter)
lstGroups.Sort()
Return lstGroups
End Function
Private Shared Function GetADGroupMembershipByGroup(ByVal sPath As String, ByVal sGroupName As String) As List(Of String)
Dim lstResults As List(Of String)
Dim lstGroups As List(Of String)
Dim lstSubGroups As List(Of String)
Dim sFilter As String
GetADGroupMembershipByGroup = Nothing
sFilter = "(&(objectCategory=group)(cn=" & sGroupName & "))"
lstResults = RunSearch(sPath, sFilter, "MemberOf")
lstGroups = New List(Of String)
If lstResults IsNot Nothing Then
For Each result As String In lstResults
lstGroups.Add(result)
'Retrieve all groups that the current group is a member of
lstSubGroups = GetADGroupMembershipByGroup(sPath, result)
For Each sSubGroup As String In lstSubGroups
If Not lstGroups.Contains(sSubGroup) Then
lstGroups.Add(sSubGroup)
End If
Next sSubGroup
Next result
End If
Return lstGroups
End Function
Private Shared Function RunSearch(ByVal sPath As String, ByVal sFilter As String, Optional ByVal sProperty As String = "") As List(Of String)
Dim lstResults As List(Of String)
Dim de As System.DirectoryServices.DirectoryEntry = Nothing
Dim deSearcher As System.DirectoryServices.DirectorySearcher = Nothing
Dim results As System.DirectoryServices.SearchResultCollection
Dim res As System.DirectoryServices.SearchResult
RunSearch = Nothing
Try
de = New System.DirectoryServices.DirectoryEntry(sPath)
deSearcher = New System.DirectoryServices.DirectorySearcher(de)
deSearcher.Filter = sFilter
deSearcher.SearchScope = SearchScope.Subtree
results = deSearcher.FindAll
lstResults = New List(Of String)
For Each res In results
If sProperty = "" Then
'If no specific property is being sought, simply return the common name
lstResults.Add(TrimToName(res.GetDirectoryEntry.Name))
Else
For Each o As Object In res.Properties(sProperty)
lstResults.Add(TrimToName(o))
Next o
End If
Next res
Return lstResults
Catch
Throw
Finally
If deSearcher IsNot Nothing Then
deSearcher.Dispose()
End If
If de IsNot Nothing Then
de.Dispose()
End If
End Try
End Function
Private Shared Function TrimToName(ByVal path As String) As String
Dim parts() As String
parts = path.Split(",")
Return parts(0).Replace("CN=", String.Empty)
End Function
End Class