C# Active Directory:DirectoryEntry成员列表GroupPrincipal.GetMembers()

C# Active Directory:DirectoryEntry成员列表GroupPrincipal.GetMembers(),c#,active-directory,ldap,C#,Active Directory,Ldap,我有一个小组,我们叫它GotRocks。我试图获得它的所有成员,但我得到的结果在DirectoryEntry和AccountManagement之间有着天壤之别。以下是按成员检索方法列出的计数: Method 1: DirectoryEntry.PropertyName.member = 350 Method 2: AccountManagement.GroupPrincipal.GetMembers(false) = 6500 Method 2: AccountManagement.Group

我有一个小组,我们叫它GotRocks。我试图获得它的所有成员,但我得到的结果在DirectoryEntry和AccountManagement之间有着天壤之别。以下是按成员检索方法列出的计数:

Method 1: DirectoryEntry.PropertyName.member = 350
Method 2: AccountManagement.GroupPrincipal.GetMembers(false) = 6500
Method 2: AccountManagement.GroupPrincipal.GetMembers(true) = 6500
作为一种理智的检查,我进入ADUC并从该组中提取成员列表,默认情况下该列表限制为2000人。这里重要的是,ADUC似乎验证了AccountManagement的结果。我也检查了Children属性,但它是空白的。此外,DirectoryEntry中列出的成员都不是SchemaName组的成员—他们都是用户

我不认为这是一个代码问题,但可能是对DirectoryEntry和GetMembers方法如何检索组成员缺乏了解。有人能解释为什么DirectoryEntry成员列表会产生与GetMembers递归函数不同的结果吗?是否有某种方法或属性需要注意?注意:我已经构建了一个按成员查询DirectoryEntry的函数;range={0}-{1},其中循环获取1500个块中的成员。我在这里完全不知所措

DirectoryEntry返回的结果太少这一事实是有问题的,因为我想使用DirectoryEntry来说明一个简单的事实,即走这条路线至少比AccountManagement快两个数量级,即秒表时间分别为1100毫秒和250000毫秒

更新1:方法:

方法1:DirectoryEntry

private List<string> GetGroupMemberList(string strPropertyValue, string strActiveDirectoryHost, int intActiveDirectoryPageSize)
{
    // Variable declaration(s).
    List<string> listGroupMemberDn = new List<string>();
    string strPath = strActiveDirectoryHost + "/<GUID=" + strPropertyValue + ">";
    string strMemberPropertyRange = null;
    DirectoryEntry directoryEntryGroup = null;
    DirectorySearcher directorySearcher = null;
    SearchResultCollection searchResultCollection = null;
    // https://msdn.microsoft.com/en-us/library/windows/desktop/ms676302(v=vs.85).aspx
    const int intIncrement = 1500;

    // Load the DirectoryEntry.
    try
    {
        directoryEntryGroup = new DirectoryEntry(strPath, null, null, AuthenticationTypes.Secure);

        directoryEntryGroup.RefreshCache();
    }
    catch (Exception)
    { }

    try
    {
        if (directoryEntryGroup.Properties["member"].Count > 0)
        {
            int intStart = 0;

            while (true)
            {
                int intEnd = intStart + intIncrement - 1;

                // Define the PropertiesToLoad attribute, which contains a range flag that LDAP uses to get a list of members in a pre-specified chunk/block of members that is defined by each loop iteration.
                strMemberPropertyRange = string.Format("member;range={0}-{1}", intStart, intEnd);

                directorySearcher = new DirectorySearcher(directoryEntryGroup)
                {
                    Filter = "(|(objectCategory=person)(objectCategory=computer)(objectCategory=group))", // User, Contact, Group, Computer objects

                    SearchScope = SearchScope.Base,

                    PageSize = intActiveDirectoryPageSize,

                    PropertiesToLoad = { strMemberPropertyRange }
                };

                try
                {
                    searchResultCollection = directorySearcher.FindAll();

                    foreach (SearchResult searchResult in searchResultCollection)
                    {
                        var membersProperties = searchResult.Properties;

                        // Find the property that starts with the PropertyName of "member;" and get all of its member values.
                        var membersPropertyNames = membersProperties.PropertyNames.OfType<string>().Where(n => n.StartsWith("member;"));

                        // For each record in the memberPropertyNames, get the PropertyName and add to the lest.
                        foreach (var propertyName in membersPropertyNames)
                        {
                            var members = membersProperties[propertyName];

                            foreach (string memberDn in members)
                            {                                   
                                listGroupMemberDn.Add(memberDn);
                            }
                        }
                    }
                }
                catch (DirectoryServicesCOMException)
                {
                    // When the start of the range exceeds the number of available results, an exception is thrown and we exit the loop.
                    break;
                }

                intStart += intIncrement;
            }
        }

        return listGroupMemberDn;
    }
    finally
    {
        listGroupMemberDn = null;
        strPath = null;
        strMemberPropertyRange = null;
        directoryEntryGroup.Close();
        if(directoryEntryGroup != null) directoryEntryGroup.Dispose();                
        if (directorySearcher != null) directorySearcher.Dispose();
        if(searchResultCollection != null) searchResultCollection.Dispose();
    }
}
更新4:Program.cs

最后一次代码块更新2就是答案

用于读取成员属性的代码比需要的代码复杂。这可能是它返回歪斜结果的原因之一,但我没有仔细看,因为您根本不需要使用DirectorySearcher。我只是重写了它

以下是它的最简单形式:

private static List<string> GetGroupMemberList(string groupGuid, string domainDns) {
    var members = new List<string>();

    var group = new DirectoryEntry($"LDAP://{domainDns}/<GUID={groupGuid}>", null, null, AuthenticationTypes.Secure);

    while (true) {
        var memberDns = group.Properties["member"];
        foreach (var member in memberDns) {
            members.Add(member.ToString());
        }

        if (memberDns.Count == 0) break;

        try {
            group.RefreshCache(new[] {$"member;range={members.Count}-*", "member"});
        } catch (System.Runtime.InteropServices.COMException e) {
            if (e.ErrorCode == unchecked((int) 0x80072020)) { //no more results
                break;
            }
            throw;
        }
    }
    return members;
}
这不是递归的。要使其递归,您必须从每个成员创建一个新的DirectoryEntry,并测试它是否是一个组,然后获取该组的成员

我打开了代码,这是递归版本。它的速度很慢,因为它必须绑定到每个成员以查看它是否是一个组

这不是防弹的。仍然有一些情况下,您可能会得到奇怪的结果,比如您在组中的受信任外部域上有用户

private static List<string> GetGroupMemberList(string groupGuid, string domainDns, bool recurse = false) {
    var members = new List<string>();

    var group = new DirectoryEntry($"LDAP://{domainDns}/<GUID={groupGuid}>", null, null, AuthenticationTypes.Secure);

    while (true) {
        var memberDns = group.Properties["member"];
        foreach (var member in memberDns) {
            if (recurse) {
                var memberDe = new DirectoryEntry($"LDAP://{member}");
                if (memberDe.Properties["objectClass"].Contains("group")) {
                    members.AddRange(GetGroupMemberList(
                        new Guid((byte[]) memberDe.Properties["objectGuid"].Value).ToString(), domainDns,
                        true));
                } else {
                    members.Add(member.ToString());
                }
            } else {
                members.Add(member.ToString());
            }
        }

        if (memberDns.Count == 0) break;

        try {
            group.RefreshCache(new[] {$"member;range={members.Count}-*", "member"});
        } catch (System.Runtime.InteropServices.COMException e) {
            if (e.ErrorCode == unchecked((int) 0x80072020)) { //no more results
                break;
            }
            throw;
        }
    }
    return members;
}
当然,这是在编译guid列表,而不是DNs

更新2:这是一个版本,它还拉取将组作为主要组但未列在组的成员属性中的用户。GetMembers似乎就是这么做的。将用户创建的组作为主组是很奇怪的,但从技术上讲这是可能的。部分内容摘自以下答案:

最后一次代码块更新2就是答案

用于读取成员属性的代码比需要的代码复杂。这可能是它返回歪斜结果的原因之一,但我没有仔细看,因为您根本不需要使用DirectorySearcher。我只是重写了它

以下是它的最简单形式:

private static List<string> GetGroupMemberList(string groupGuid, string domainDns) {
    var members = new List<string>();

    var group = new DirectoryEntry($"LDAP://{domainDns}/<GUID={groupGuid}>", null, null, AuthenticationTypes.Secure);

    while (true) {
        var memberDns = group.Properties["member"];
        foreach (var member in memberDns) {
            members.Add(member.ToString());
        }

        if (memberDns.Count == 0) break;

        try {
            group.RefreshCache(new[] {$"member;range={members.Count}-*", "member"});
        } catch (System.Runtime.InteropServices.COMException e) {
            if (e.ErrorCode == unchecked((int) 0x80072020)) { //no more results
                break;
            }
            throw;
        }
    }
    return members;
}
这不是递归的。要使其递归,您必须从每个成员创建一个新的DirectoryEntry,并测试它是否是一个组,然后获取该组的成员

我打开了代码,这是递归版本。它的速度很慢,因为它必须绑定到每个成员以查看它是否是一个组

这不是防弹的。仍然有一些情况下,您可能会得到奇怪的结果,比如您在组中的受信任外部域上有用户

private static List<string> GetGroupMemberList(string groupGuid, string domainDns, bool recurse = false) {
    var members = new List<string>();

    var group = new DirectoryEntry($"LDAP://{domainDns}/<GUID={groupGuid}>", null, null, AuthenticationTypes.Secure);

    while (true) {
        var memberDns = group.Properties["member"];
        foreach (var member in memberDns) {
            if (recurse) {
                var memberDe = new DirectoryEntry($"LDAP://{member}");
                if (memberDe.Properties["objectClass"].Contains("group")) {
                    members.AddRange(GetGroupMemberList(
                        new Guid((byte[]) memberDe.Properties["objectGuid"].Value).ToString(), domainDns,
                        true));
                } else {
                    members.Add(member.ToString());
                }
            } else {
                members.Add(member.ToString());
            }
        }

        if (memberDns.Count == 0) break;

        try {
            group.RefreshCache(new[] {$"member;range={members.Count}-*", "member"});
        } catch (System.Runtime.InteropServices.COMException e) {
            if (e.ErrorCode == unchecked((int) 0x80072020)) { //no more results
                break;
            }
            throw;
        }
    }
    return members;
}
当然,这是在编译guid列表,而不是DNs

更新2:这是一个版本,它还拉取将组作为主要组但未列在组的成员属性中的用户。GetMembers似乎就是这么做的。将用户创建的组作为主组是很奇怪的,但从技术上讲这是可能的。部分内容摘自以下答案:


GetMembers方法只是从AD对象的member属性中读取数据,因此获取不同的值是没有意义的。你能展示你如何使用这两种方法的所有代码吗?@Gabriel。我已经更新了这个问题,包含了这两种方法的代码。谢谢你看这个。我测试了你的代码。我在我的广告环境中的一个组中尝试了它,其中有几个嵌套组。正如预期的那样,我为DirectoryEntry方法和GetMembersfalse获得了7个成员,而GetMemberStruery使VisualStudio在所有异常上都中断了304个成员。这里有很多try/catch块,因此可能会发生一些异常,但它只是默默地继续。我遵循了您提供的链接,并启用了所有异常的中断。我还删除了所有隐藏的文件;清洗溶液;并重新生成,但未成功。GetMembers方法仅从成员中读取

属性,因此获取不同的值是没有意义的。你能展示你如何使用这两种方法的所有代码吗?@Gabriel。我已经更新了这个问题,包含了这两种方法的代码。谢谢你看这个。我测试了你的代码。我在我的广告环境中的一个组中尝试了它,其中有几个嵌套组。正如预期的那样,我为DirectoryEntry方法和GetMembersfalse获得了7个成员,而GetMemberStruery使VisualStudio在所有异常上都中断了304个成员。这里有很多try/catch块,因此可能会发生一些异常,但它只是默默地继续。我遵循了您提供的链接,并启用了所有异常的中断。我还删除了所有隐藏的文件;清洗溶液;我更新了我的答案,并进行了修改,以使您的示例至少在我的环境中无异常运行。看看这有什么不同。我不知道会怎样,但你永远不知道。不,不管是什么类型的团体。你能展示一下你调用方法的代码吗?我在答案的末尾添加了一个新版本的方法。尝试一下,看看它是否会给你同样的结果,就像真正聪明的开发人员为你编写代码一样。然后,您经常回到代码,以激发编写自己代码的更好方法+++++++++11111111@snowYetis从这个答案开始,我在其他答案中重复使用了这个代码。最后我终于建立了一个网站,我可以把我学到的一些东西放在那里,这样我就不必重新打字了:我有更多文章的想法。我只是想了解一下。我更新了我的答案,并做了一些修改,以使您的示例在没有异常的情况下运行,至少在我的环境中是这样。看看这有什么不同。我不知道会怎样,但你永远不知道。不,不管是什么类型的团体。你能展示一下你调用方法的代码吗?我在答案的末尾添加了一个新版本的方法。尝试一下,看看它是否会给你同样的结果,就像真正聪明的开发人员为你编写代码一样。然后,您经常回到代码,以激发编写自己代码的更好方法+++++++++11111111@snowYetis从这个答案开始,我在其他答案中重复使用了这个代码。最后我终于建立了一个网站,我可以把我学到的一些东西放在那里,这样我就不必重新打字了:我有更多文章的想法。我只需要抽出时间去做。
private static List<string> GetGroupMemberList(string groupGuid, string domainDns) {
    var members = new List<string>();

    var group = new DirectoryEntry($"LDAP://{domainDns}/<GUID={groupGuid}>", null, null, AuthenticationTypes.Secure);

    while (true) {
        var memberDns = group.Properties["member"];
        foreach (var member in memberDns) {
            members.Add(member.ToString());
        }

        if (memberDns.Count == 0) break;

        try {
            group.RefreshCache(new[] {$"member;range={members.Count}-*", "member"});
        } catch (System.Runtime.InteropServices.COMException e) {
            if (e.ErrorCode == unchecked((int) 0x80072020)) { //no more results
                break;
            }
            throw;
        }
    }
    return members;
}
var members = GetGroupMemberList("00000000-0000-0000-0000-000000000000", "domain.com");
private static List<string> GetGroupMemberList(string groupGuid, string domainDns, bool recurse = false) {
    var members = new List<string>();

    var group = new DirectoryEntry($"LDAP://{domainDns}/<GUID={groupGuid}>", null, null, AuthenticationTypes.Secure);

    while (true) {
        var memberDns = group.Properties["member"];
        foreach (var member in memberDns) {
            if (recurse) {
                var memberDe = new DirectoryEntry($"LDAP://{member}");
                if (memberDe.Properties["objectClass"].Contains("group")) {
                    members.AddRange(GetGroupMemberList(
                        new Guid((byte[]) memberDe.Properties["objectGuid"].Value).ToString(), domainDns,
                        true));
                } else {
                    members.Add(member.ToString());
                }
            } else {
                members.Add(member.ToString());
            }
        }

        if (memberDns.Count == 0) break;

        try {
            group.RefreshCache(new[] {$"member;range={members.Count}-*", "member"});
        } catch (System.Runtime.InteropServices.COMException e) {
            if (e.ErrorCode == unchecked((int) 0x80072020)) { //no more results
                break;
            }
            throw;
        }
    }
    return members;
}
        //listPrincipalNoNull = listPrincipalSearchResult.Where(item => item.Name != null).ToList();
        if (groupPrincipal != null) {
            foreach (Principal principal in listPrincipalSearchResult) {
                listGroupMemberGuid.Add(((DirectoryEntry)principal.GetUnderlyingObject()).Guid);
            }
        }
private List<string> GetGroupMemberList(string strPropertyValue, string strActiveDirectoryHost, int intActiveDirectoryPageSize)
{
    // Variable declaration(s).
    List<string> listGroupMemberDn = new List<string>();
    string strPath = strActiveDirectoryHost + "/<GUID=" + strPropertyValue + ">";
    const int intIncrement = 1500; // https://msdn.microsoft.com/en-us/library/windows/desktop/ms676302(v=vs.85).aspx

    var members = new List<string>();

    // The count result returns 350.
    var group = new DirectoryEntry(strPath, null, null, AuthenticationTypes.Secure);
    //var group = new DirectoryEntry($"LDAP://{"EnterYourDomainHere"}/<GUID={strPropertyValue}>", null, null, AuthenticationTypes.Secure);

    while (true)
    {
        var memberDns = group.Properties["member"];
        foreach (var member in memberDns)
        {
            members.Add(member.ToString());
        }

        if (memberDns.Count < intIncrement) break;

        group.RefreshCache(new[] { $"member;range={members.Count}-*" });
    }

    //Find users that have this group as a primary group
    var secId = new SecurityIdentifier(group.Properties["objectSid"][0] as byte[], 0);

    /* Find The RID (sure exists a best method)
     */
    var reg = new Regex(@"^S.*-(\d+)$");
    var match = reg.Match(secId.Value);
    var rid = match.Groups[1].Value;

    /* Directory Search for users that has a particular primary group
     */
    var dsLookForUsers =
        new DirectorySearcher {
            Filter = string.Format("(primaryGroupID={0})", rid),
            SearchScope = SearchScope.Subtree,
            PageSize = 1000,
            SearchRoot = new DirectoryEntry(strActiveDirectoryHost)
    };
    dsLookForUsers.PropertiesToLoad.Add("distinguishedName");

    var srcUsers = dsLookForUsers.FindAll();

    foreach (SearchResult user in srcUsers)
    {
        members.Add(user.Properties["distinguishedName"][0].ToString());
    }
    return members;
}