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 ldapPath As String, ByVal userName 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 filter As String GetADGroupMembership = Nothing filter = "(&(objectClass=user)(samAccountName=" & userName & "))" lstResults = RunSearch(ldapPath, filter, "MemberOf") lstGroups = New List(Of String) If lstResults IsNot Nothing Then For Each result As String In lstResults lstGroups.Add(result) lstSubGroups = GetADGroupMembershipByGroup(ldapPath, result) For Each subGroup As String In lstSubGroups If Not lstGroups.Contains(subGroup) Then lstGroups.Add(subGroup) End If Next subGroup Next result End If Return lstGroups End Function Public Shared Function GetLocalGroupMembership(ByVal ldapPath As String, ByVal userName As String, ByVal password 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(ldapPath, userName, password, 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 ldapPath As String) As List(Of String) Dim lstGroups As List(Of String) Dim filter As String GetADGroups = Nothing filter = "(&(objectClass=group))" lstGroups = RunSearch(ldapPath, filter) lstGroups.Sort() Return lstGroups End Function Private Shared Function GetADGroupMembershipByGroup(ByVal ldapPath As String, ByVal groupName 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 filter As String GetADGroupMembershipByGroup = Nothing filter = "(&(objectCategory=group)(cn=" & groupName & "))" lstResults = RunSearch(ldapPath, filter, "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(ldapPath, result) For Each subGroup As String In lstSubGroups If Not lstGroups.Contains(subGroup) Then lstGroups.Add(subGroup) End If Next subGroup Next result End If Return lstGroups End Function Private Shared Function RunSearch(ByVal ldapPath As String, ByVal filter As String, Optional ByVal propertyName 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(ldapPath) deSearcher = New System.DirectoryServices.DirectorySearcher(de) deSearcher.Filter = filter deSearcher.SearchScope = SearchScope.Subtree results = deSearcher.FindAll lstResults = New List(Of String) For Each res In results If propertyName = "" 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(propertyName) 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
Update:
This library has grown and improved quite a bit over time, and I recently re-wrote it in C# and posted it on GitHub.