none
Recursive retrieval of all AD group memberships of a user.

    General discussion

  • Recursive retrieval of all group memberships (security and distribution) of an AD user.

    That weird looking filter (1.2.840.113556.1.4.1941) is an OID called LDAP_MATCHING_RULE_IN_CHAIN

    It works, it's fast (compared to recursive enumerations), it gets both security and distribution groups, and it's almost as intuitive as the federal tax code.

    $userdn = '<user DN to search for>'
    $strFilter = "(member:1.2.840.113556.1.4.1941:=$userdn)"
    $objDomain = New-Object System.DirectoryServices.DirectoryEntry("LDAP://rootDSE")
    $objSearcher = New-Object System.DirectoryServices.DirectorySearcher
    $objSearcher.SearchRoot = "LDAP://$($objDomain.rootDomainNamingContext)"
    $objSearcher.PageSize = 1000
    $objSearcher.Filter = $strFilter
    $objSearcher.SearchScope = "Subtree"
    $colProplist = "name"
    foreach ($i in $colPropList){
       $objSearcher.PropertiesToLoad.Add($i) > $nul
       }
    $colResults = $objSearcher.FindAll()
    foreach ($objResult in $colResults)
        {
          $objItem = $objResult.Properties
          $objItem.name
          } 

     


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "

    Friday, October 12, 2012 12:36 AM
    Moderator

All replies

  • You should post this up in the Gallery.  We get lots of requests for this.


    Grant Ward, a.k.a. Bigteddy

    Friday, October 12, 2012 10:04 AM
  • I know.  I don't generally use ADSI methods for AD scripting, but I have followed some of the threads where examples have been posted, and I don't recall ever seeing this method used in any of the examples, so I put it up for comments.

     I think it should probably be wrapped up as a proper function for the Gallery, but I thought I'd post the basic code here first for reference.

      

    I found the documentation on it here:

     

    http://msdn.microsoft.com/en-us/library/windows/desktop/aa746475(v=vs.85).aspx

     

    They only give examples in C++, and that example doesn't work in Powershell. They have the user DN wrapped in parens, and you have to remove those to get it to work in Powershell.

     


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "

    Friday, October 12, 2012 11:04 AM
    Moderator
  • The 1941 matching filter was introduced with Windows Server 2003 R2. It is very powerful. I documented it years ago on this page:

    http://www.rlmueller.net/ADOSearchTips.htm

    I also is included in my example filters in this Wiki (also see Note 10):

    http://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters-en-us.aspx

    However, it is used infrequently. It can also be used with the memberOf attribute to find all members of a group, whether directly or due to group nesting. For example:

    $x = Get-ADUser -LDAPFilter "(memberOf:1.2.840.113556.1.4.1941:=cn=Test Group,ou=West,dc=MyDomain,dc=com)"
    ForEach ($Member In $x)
    {
        $Member.sAMAccountName
    }

    -----

    It can be used anywhere that accepts LDAP filters, such as with dsquery * -filter.


    Richard Mueller - MVP Directory Services

    Friday, October 12, 2012 6:25 PM
    Moderator
  • Nice.

    Any tips on displaying that WIKI page without the examples getting truncated?


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "

    Friday, October 12, 2012 6:37 PM
    Moderator
  • Rats. I worked hard to format the table so the examples were not truncated (at least for me). I guess it depends on browser and resolution. One criticism of the Wiki is that it doesn't use all of the screen width.

    I would try to fix the article, but I can't tell how much you see. Here is part of what I see:

    All of the word wrapping was done manually (in the HTML) by me to avoid truncating.


    Richard Mueller - MVP Directory Services

    Friday, October 12, 2012 7:08 PM
    Moderator
  • The maching I'm on at work is running IE8, and I've got this:


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "

    Friday, October 12, 2012 7:40 PM
    Moderator
  • It seems to get trimmed at the width of the sidebar menu at the top of the screen.

    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "

    Friday, October 12, 2012 7:42 PM
    Moderator
  • I asked in a forum about the Wiki article, but I think no one has a solution. I will probably move the line breaks to shorten the lines.

    In the meantime, I compared three PowerShell methods for determining nested group memberships:

    1. Using ADSISearcher and the LDAP_MATCHING_RULE_IN_CHAIN. This uses the LDAP syntax filter:
       (member:1.2.840.113556.1.4.1941:=$DN)
    where $DN is the distinguishedName of the user. This method:
        a. Reveals membership due to group nesting.
        b. Reveals both security and distribution groups.
        c. Does not reveal the "primary" group (or any groups the "primary" is a member of).
    This method requires one query of Active Directory. You can retrieve any attributes of the group you want without the need to bind to the individual group objects.

    2. Using the tokenGroups attribute, a collection of security group SID values. This method:
        a. Reveals membership due to group nesting.
        b. Reveals only security groups, not distribution groups.
        c. Reveals the "primary" group (and any groups the "primary" group is a member of).
    This method requires binding to one object in Active Directory to retrieve the tokenGroups attibute. However, the values retrieved are SID's. You must then bind to each corresponding group object, using the SID value, to retrieve the name.

    3. A recursive retrieval of the memberOf attribute. This method:
        a. Reveals membership due to group nesting.
        b. Reveals both security and distribution groups.
        c. Does not reveal the "primary" group (or any groups the "primary" is a member of).
    This method requires that you bind to each group object to retrieve the memberOf attribute. In addition, you must keep track of the groups in a hash table to avoid an infinite loop if the group nesting is circular.

    I tested all three methods on two users in my test domain. The first user has 14 group memberships (10 security, 1 distribution, 1 primary, and 2 groups the primary is a member of). Methods 1 and 3 above revealed 11 of these groups (missing the primary and the 2 groups the primary is a member of). Method 2 above revealed 13 of the groups (missing the 1 distribution group). It is interesting that none of the methods revealed all 14 groups. For this user method 1 above took twice as long as the other two methods.

    The second user I tested with is a member of 305 groups, all security. Methods 1 and 3 revealed 302 of the groups (missing the primary and the 2 groups the primary is a member of). Method 2 revealed all 305 groups. For this user method 1 was significantly faster. As before, method 3 was only slightly faster than method 2.

    These results make sense. The slowest steps in any script are those that retrieve information from a remote computer over the network. Method 1 requires one query of a Domain Controller. Methods 2 and 3 require the script to bind to every group object the user is a member of. The query itself is slower in method 1, so that method is slower if there are relatively few groups. But as the number of groups involved increases, the other two methods require more binding of objects in Active Directory, significantly slowing the script.

    My tests are not complete, as they take several hours. My scripts pause 15 minutes between trials to avoid caching (where the first trial will be much slower than the others). I will post raw results later.


    Richard Mueller - MVP Directory Services

    Saturday, October 13, 2012 4:12 PM
    Moderator
  • Interesting. 

     

    The situation I've got right now, is that we're fighting resolution time for a non-AD aware appliction that's using group memberships to determine permissions within the app. 

     

    They have an LDS system set up for testing, but run against AD in production.  It appears the second method works in production, but they cannot use it in their test environment. 

     

    I haven't researched it yet, but I suspect that LDS instance doesn't understand tokenGroups or SIDs.


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "

    Saturday, October 13, 2012 4:30 PM
    Moderator
  • This article:

    http://msdn.microsoft.com/en-us/library/cc223395(prot.20).aspx

    indicates tokenGroups is in the Schema for both AD DS and AD LDS. All objects are identified by objectSID. The process of calculating tokenGroups is necessary to create the logon token with the SID's of all security groups that apply to the user, for determining permissions. However, the SID's themselves seem to be different in AD LDS.


    Richard Mueller - MVP Directory Services

    Saturday, October 13, 2012 5:23 PM
    Moderator
  • This LDS instance is not replicated from AD.  It is manually populated with test users and groups by the application programmers that are using it.

    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "

    Saturday, October 13, 2012 5:36 PM
    Moderator
  • The groups in question may not be security groups.  They were set up explicitly for the application, so there wasn't any point in making them security groups - the application was not designed to work with AD security tokens. 

     

    It queries AD for group membership, so it doesn't matter what kind of group it is, and making them security groups would have added a lot of unnecessary token bloat.


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "

    Saturday, October 13, 2012 5:47 PM
    Moderator
  • Results of testing in my test domain:

    User with 14 groups:
    Method                      Groups   Ave. (10 runs) 1 standard deviation
    --------------------------- ------   -------------  --------------------
    LDAP_MATCHING_RULE_IN_CHAIN    11     0.1664 sec.   +/- 0.0325 sec.
    tokenGroups                    13     0.0895 sec.   +/- 0.0038 sec.
    Recursive memberOf             11     0.0774 sec.   +/- 0.0021 sec.

    User with 308 groups:
    Method                      Groups   Ave. (10 runs) 1 standard deviation
    --------------------------- ------   -------------  --------------------
    LDAP_MATCHING_RULE_IN_CHAIN   305     0.2664 sec.   +/- 0.0151 sec.
    tokenGroups                   306     1.2147 sec.   +/- 0.0271 sec.
    Recursive memberOf            305     1.2813 sec.   +/- 0.1190 sec.



    Richard Mueller - MVP Directory Services

    Saturday, October 13, 2012 10:23 PM
    Moderator
  • mjolinor, can you view this Wiki article again to see if the Examples table is still cut off for you?

    http://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters-en-us.aspx

    I worked on formatting the cells so the columns take up less space, but I cannot tell if that fixed things. I never could duplicate what you saw. No one in the Wiki Discussion forum had suggestions.


    Richard Mueller - MVP Directory Services

    Friday, October 26, 2012 9:36 PM
    Moderator
  • Still about the same:


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "

    Friday, October 26, 2012 9:43 PM
    Moderator