How best to recursively query AD Group membership in ASP.NET (using vb)

11,591

Solution 1

A Complete example

This code will list all users in a group AND sub groups by enumerating a given group name. also, if a users account is enabled / disabled.

To use, just call ListADGroupMembers("Some_Group_Name"). This will populate the users full name and mobile number into an array, which you can then loop through.

Its pretty simple to follow, just read it through.

Public ADUSers(,) As String
 Public n As Integer = 0

 Public Sub ListADGroupMembers(ByVal GN As String)

    Dim DirectoryRoot As New DirectoryEntry("LDAP://RootDSE")
    Dim DNC = DirectoryRoot.Properties("DefaultNamingContext")(0).ToString()
    Dim GroupName As String = GN '"G_All_IT_Users"
    Dim GroupMembers As System.Collections.Specialized.StringCollection = GetGroupMembers(DNC, GroupName)
    'Dim GroupMembersMobile As System.Collections.Specialized.StringCollection = GetGroupMembers(DNC, GroupName)
    '  For Each Member As String In GroupMembers
    '    ListBox1.Items.Add(Member)
    'Next Member

End Sub

Public Function GetGroupMembers(ByVal strDomain As String, ByVal strGroup As String) As System.Collections.Specialized.StringCollection

    Dim GroupMembers As New System.Collections.Specialized.StringCollection()

    Try
        Dim DirectoryRoot As New DirectoryEntry("LDAP://" & strDomain)
        Dim DirectorySearch As New DirectorySearcher(DirectoryRoot, "(CN=" & strGroup & ")")
        Dim DirectorySearchCollection As SearchResultCollection = DirectorySearch.FindAll()
        For Each DirectorySearchResult As SearchResult In DirectorySearchCollection
            Dim ResultPropertyCollection As ResultPropertyCollection = DirectorySearchResult.Properties
            Dim GroupMemberDN As String
            For Each GroupMemberDN In ResultPropertyCollection("member")
                Dim DirectoryMember As New DirectoryEntry("LDAP://" & GroupMemberDN)
                Dim DirectoryMemberProperties As System.DirectoryServices.PropertyCollection = DirectoryMember.Properties
                Dim DirectoryItem As Object = DirectoryMemberProperties("sAMAccountName").Value
                Dim DirectoryPhone As Object = DirectoryMemberProperties("mobile").Value
                Dim uac As Object = DirectoryMemberProperties("userAccountControl").Value

                If DirectoryMember.SchemaClassName = "group" Then
                    ' this is a group.                        
                    ListADGroupMembers(DirectoryItem)
                End If

                If DirectoryMember.SchemaClassName = "user" Then
                    ' this is a user.
                    If Nothing IsNot DirectoryItem Then
                        If AccEnabled(uac) = 1 Then ' check the ad account is enabled
                            GroupMembers.Add(DirectoryItem.ToString())
                            ListBox1.Items.Add(DirectoryItem.ToString() & " " & DirectoryPhone)

                            ADUSers(0, n) = DirectoryItem.ToString()
                            ADUSers(1, n) = DirectoryPhone
                            n += 1
                            ReDim Preserve ADUSers(1, n)
                        End If
                    End If
                End If

            Next GroupMemberDN

        Next DirectorySearchResult
    Catch ex As Exception
        MsgBox(ex.Message)
    End Try

    Return GroupMembers



End Function




  ' check account is active or not.
Function AccEnabled(ByVal uac As String) As String

    Dim aret As Integer = 0
    Select Case uac
        Case 512 'Enabled 
            aret = 1
        Case 514 ': ACCOUNTDISABLE()
            aret = 0
        Case 528 ': Enabled(-LOCKOUT)
            aret = 1
        Case 530 ': ACCOUNTDISABLE(-LOCKOUT)
            aret = 0
        Case 544 ': Enabled(-PASSWD_NOTREQD)
            aret = 1
        Case 546 ': ACCOUNTDISABLE(-PASSWD_NOTREQD)
            aret = 0
        Case 560 ': Enabled(-PASSWD_NOTREQD - LOCKOUT)
            aret = 1
        Case 640 ': Enabled(-ENCRYPTED_TEXT_PWD_ALLOWED)
            aret = 1
        Case 2048 ' : INTERDOMAIN_TRUST_ACCOUNT()
            aret = 1
        Case 2080 ': INTERDOMAIN_TRUST_ACCOUNT(-PASSWD_NOTREQD)
            aret = 1
        Case 4096 ': WORKSTATION_TRUST_ACCOUNT()
            aret = 1
        Case 8192 ': SERVER_TRUST_ACCOUNT()
            aret = 1
        Case 66048 ': Enabled(-DONT_EXPIRE_PASSWORD)
            aret = 1
        Case 66050 ': ACCOUNTDISABLE(-DONT_EXPIRE_PASSWORD)
            aret = 0
        Case 66064 ': Enabled(-DONT_EXPIRE_PASSWORD - LOCKOUT)
            aret = 1
        Case 66066 ': ACCOUNTDISABLE(-DONT_EXPIRE_PASSWORD - LOCKOUT)
            aret = 0
        Case 66080 ': Enabled(-DONT_EXPIRE_PASSWORD - PASSWD_NOTREQD)
            aret = 1
        Case 66082 ': ACCOUNTDISABLE(-DONT_EXPIRE_PASSWORD - PASSWD_NOTREQD)
            aret = 0
        Case 66176 ': Enabled(-DONT_EXPIRE_PASSWORD - ENCRYPTED_TEXT_PWD_ALLOWED)
            aret = 1
        Case 131584 ': Enabled(-MNS_LOGON_ACCOUNT)
            aret = 1
        Case 131586 ': ACCOUNTDISABLE(-MNS_LOGON_ACCOUNT)
            aret = 0
        Case 131600 ': Enabled(-MNS_LOGON_ACCOUNT - LOCKOUT)
            aret = 1
        Case 197120 ': Enabled(-MNS_LOGON_ACCOUNT - DONT_EXPIRE_PASSWORD)
            aret = 1
        Case 532480 'SERVER_TRUST_ACCOUNT - TRUSTED_FOR_DELEGATION (Domain Controller) 
            aret = 1
        Case 1049088 ': Enabled(-NOT_DELEGATED)
            aret = 1
        Case 1049090 ': ACCOUNTDISABLE(-NOT_DELEGATED)
            aret = 0
        Case 2097664 ': Enabled(-USE_DES_KEY_ONLY)
            aret = 1
        Case 2687488 ': Enabled(-DONT_EXPIRE_PASSWORD - TRUSTED_FOR_DELEGATION - USE_DES_KEY_ONLY)
            aret = 1
        Case 4194816 ': Enabled(-DONT_REQ_PREAUTH)
            aret = 1
        Case Else
            aret = 0
    End Select

    AccEnabled = aret

End Function

Solution 2

Try this:

' set up domain context
Dim ctx As New PrincipalContext(ContextType.Domain)

' find a user
Dim user As UserPrincipal = UserPrincipal.FindByIdentity(ctx, "SomeUserName")

If user IsNot Nothing Then
    Dim groupMemberships = user.GetAuthorizationGroups()

            ' do something with group....
    For Each gp As GroupPrincipal In groupMemberships
    Next
End If

You should check out the System.DirectoryServices.AccountManagement (S.DS.AM) namespace. Read all about it here:

The new S.DS.AM makes it really easy to play around with users and groups in AD!

The method .GetAuthorizationGroups() will do a recursive search - so you should get all groups a user is member of - directly or indirectly.

Share:
11,591

Related videos on Youtube

Octavient
Author by

Octavient

Updated on September 15, 2022

Comments

  • Octavient
    Octavient over 1 year

    I'm trying to find the simplest way to query Active Directory, in one of two ways:

    1. Given an AD username, find all the groups (INCLUDING nested groups) that the user is a member of.

    2. Given an AD group name, find all the users (including those users in nested groups) that are part of the group.

    My app is in VB.NET on the v4.0 framework. I've reviewed suggestions from many different Google search results, some of which utilize LDAP and System.DirectoryServices.DirectorySearcher (which I'm thinking might be the best route).

    But I'm spinning my wheels and am looking for code samples.

    Thank you.

    UPDATE:

    I've got these pieces in place:

    <add assembly="System.DirectoryServices, Version=3.5.0.0, etc."/>

    <add namespace="System.DirectoryServices.AccountManagement" /> or Imports System.DirectoryServices.AccountManagement

    and on this line of code:

    Dim ctx As New PrincipalContext(ContextType.Domain)

    I still get this error: Type 'PrincipalContext' is not defined

    When you mention the "using statement," I assume you meant that I need to reference this namespace. Or did you mean I should do something like this?

    Using ctx As New PrincipalContext(ContextType.Domain)

  • Octavient
    Octavient over 11 years
    Cool--I'll try this out. But don't I have to define the LDAP path? Or does this assume that I'm using <authentication mode="Windows" />...?
  • Octavient
    Octavient over 11 years
    I'm getting this error: BC30002: Type 'PrincipalContext' is not defined. I've included this in web.config: <add namespace="System.DirectoryServices.AccountManagement" /> According to MSDN, the PrincipalContext Class is part of this AccountManagement class. Are there other namespaces/classes I need to include?
  • Octavient
    Octavient over 11 years
    Did you see my recent comment re: PrincipalContext error? Any ideas?
  • marc_s
    marc_s over 11 years
    @Octavient: have you included the using statement in your code-behind file where you're using PrincipalContext, too? Just adding the assembly isn't enough ...
  • Octavient
    Octavient over 11 years
    I've added an update to my original post, with more specific detail. Hoping you're willing to help me see this through...?
  • marc_s
    marc_s over 11 years
    @Octavient: yes, but I don't know what's going on - from all I see, I don't understand why it would complain about PrincipalContext being not defined ... I'm at a loss (and also not very fluent in VB.NET)
  • Octavient
    Octavient over 11 years
    ROCK ON. Thank you so much--your code helped me out tremendously. Well done.