Active Directory:DirectoryEntry成员列表<> GroupPrincipal.GetMembers()

时间:2018-03-12 03:32:16

标签: c# active-directory ldap

我有一个小组,我们称之为GotRocks。我试图获得所有成员,但我在DirectoryEntry和AccountManagement之间的计数结果却截然不同。以下是按成员检索方法的计数:

Method 1: DirectoryEntry.PropertyName.member = 350
Method 2: AccountManagement.GroupPrincipal.GetMembers(false) = 6500
Method 2: AccountManagement.GroupPrincipal.GetMembers(true) = 6500

作为一项健全性检查,我进入ADUC并从该组中删除了成员列表,默认情况下限制为2,000。这里重要的是ADUC似乎验证了AccountManagement的结果。我也检查了儿童财产,但它是空白的。此外,DirectoryEntry中列出的所有成员都不属于SchemaName组 - 它们都是用户。

我认为这不是代码问题,但可能缺乏对DirectoryEntry和GetMembers方法如何检索组成员的理解。任何人都可以解释为什么DirectoryEntry成员列表会产生与GetMembers递归函数不同的结果?我需要注意某种方法或属性吗?注意:我已经构建了一个函数,它将通过" member; range = {0} - {1}"来查询DirectoryEntry。循环以1,500块的形式获得成员。我在这里完全彻底失败。

DirectoryEntry返回如此少的结果这一事实是有问题的,因为我想使用DirectoryEntry这一简单事实,即这条路线至少比AccountManagement快两个数量级(即,秒表时间为1,100毫秒,相比之下) 250,000毫秒)。

更新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();
    }
}

方法2:AccountManagement(将bolRecursive切换为true或false)。

private List<Guid> GetGroupMemberList(string strPropertyValue, string strDomainController, bool bolRecursive)
{
    // Variable declaration(s).
    List<Guid> listGroupMemberGuid = null;
    GroupPrincipal groupPrincipal = null;
    PrincipalSearchResult<Principal> listPrincipalSearchResult = null;
    List<Principal> listPrincipalNoNull = null;
    PrincipalContext principalContext = null;
    ContextType contextType;
    IdentityType identityType;

    try
    {
        listGroupMemberGuid = new List<Guid>();

        contextType = ContextType.Domain;

        principalContext = new PrincipalContext(contextType, strDomainController);

        // Setup the IdentityType. Use IdentityType.Guid because GUID is unique and never changes for a given object. Make sure that is what strPropertyValue is receiving.
        // This is required, otherwise you will get a MultipleMatchesException error that says "Multiple principals contain a matching Identity."
        // This happens when you have two objects that AD thinks match whatever you're passing to UserPrincipal.FindByIdentity(principalContextDomain, strPropertyValue)
        identityType = IdentityType.Guid;

        groupPrincipal = GroupPrincipal.FindByIdentity(principalContext, identityType, strPropertyValue);

        if (groupPrincipal != null)
        {
            // Get all members that the group contains and add it to the list.
            // Note: The true flag in GetMembers() specifies a recursive search, which enables the application to search a group recursively and return only principal objects that are leaf nodes.
            listPrincipalSearchResult = groupPrincipal.GetMembers(bolRecursive);

            // Remove the nulls from the list, otherwise the foreach loop breaks prematurly on the first null found and misses all other object members.
            listPrincipalNoNull = listPrincipalSearchResult.Where(item => item.Name != null).ToList();

            foreach (Principal principal in listPrincipalNoNull)
            {
                listGroupMemberGuid.Add((Guid)principal.Guid);
            }
        }

        return listGroupMemberGuid;
    }
    catch (MultipleMatchesException)
    {
        // Multiple principals contain a matching identity. 
        // In other words, the same property value was found on more than one record in either of the six attributes that are listed within the IdentityType enum.
        throw new MultipleMatchesException(strPropertyValue);
    }
    finally
    {
        // Cleanup objects.
        listGroupMemberGuid = null;
        if(listPrincipalSearchResult != null) listPrincipalSearchResult.Dispose();
        if(principalContext != null) principalContext.Dispose();
        if(groupPrincipal != null) groupPrincipal.Dispose();
    }
}

更新2:

public static void Main()
{
    Program objProgram = new Program();

    // Other stuff here.

    objProgram.GetAllUserSingleDc();

    // Other stuff here.
}

private void GetAllUserSingleDc()
{
    string strDomainController = "domain.com"; 
    string strActiveDirectoryHost = "LDAP://" + strDomainController;
    int intActiveDirectoryPageSize = 1000;
    string[] strAryRequiredProperties = null;
    DirectoryEntry directoryEntry = null;
    DirectorySearcher directorySearcher = null;
    SearchResultCollection searchResultCollection = null;
    DataTypeConverter objConverter = null;
    Type fieldsType = null;

    fieldsType = typeof(AdUserInfoClass);

    objConverter = new DataTypeConverter();

    directoryEntry = new DirectoryEntry(strActiveDirectoryHost, null, null, AuthenticationTypes.Secure);

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

        SearchScope = SearchScope.Subtree,

        PageSize = intActiveDirectoryPageSize

        PropertiesToLoad = { "isDeleted","isCriticalSystemObject","objectGUID","objectSid","objectCategory","sAMAccountName","sAMAccountType","cn","employeeId",
                            "canonicalName","distinguishedName","userPrincipalName","displayName","givenName","sn","mail","telephoneNumber","title","department",
                            "description","physicalDeliveryOfficeName","manager","userAccountControl","accountExpires","lastLogon","logonCount","lockoutTime",
                            "primaryGroupID","pwdLastSet","uSNCreated","uSNChanged","whenCreated","whenChanged","badPasswordTime","badPwdCount","homeDirectory",
                            "dNSHostName" }
    };         

    searchResultCollection = directorySearcher.FindAll();            

    try
    {
        foreach (SearchResult searchResult in searchResultCollection)
        {
            clsAdUserInfo.GidObjectGuid = objConverter.ConvertByteAryToGuid(searchResult, "objectGUID");            
            clsAdUserInfo.StrDirectoryEntryPath = strActiveDirectoryHost + "/<GUID=" + clsAdUserInfo.GidObjectGuid + ">";
            clsAdUserInfo.StrSchemaClassName = new DirectoryEntry(clsAdUserInfo.StrDirectoryEntryPath, null, null, AuthenticationTypes.Secure).SchemaClassName;

            if (clsAdUserInfo.StrSchemaClassName == "group")
            {
                // Calling the functions here.
                List<string> listGroupMemberDnMethod1 = GetGroupMemberListStackOverflow(clsAdUserInfo.GidObjectGuid.ToString(), strActiveDirectoryHost, intActiveDirectoryPageSize);

                List<Guid> listGroupMemberGuidMethod2 = GetGroupMemberList(clsAdUserInfo.GidObjectGuid.ToString(), strDomainController, false)
            }   
            // More stuff here.
        }
    }
    finally
    {
        // Cleanup objects.
        // Class constructors.
        objProgram = null;
        clsAdUserInfo = null;
        // Variables.
        intActiveDirectoryPageSize = -1;
        strActiveDirectoryHost = null;
        strDomainController = null;
        strAryRequiredProperties = null;
        directoryEntry.Close();
        if(directoryEntry !=null) directoryEntry.Dispose();
        if(directorySearcher != null) directorySearcher.Dispose();
        if(searchResultCollection != null) searchResultCollection.Dispose();
        objConverter = null;
        fieldsType = null;
    }   
}

更新3:

以下是我using

的命名空间列表
using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using System.Security.Principal;
using System.Text;
using System.Linq;
using System.Collections;

更新4:Program.cs

using System;
using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.AccountManagement;
using System.Security.Principal;
using System.Text;
using System.Linq;

namespace activeDirectoryLdapExamples
{
    public class Program
    {
         public static void Main()
        {
            Program objProgram = new Program();
            objProgram.GetAllUserSingleDc();
        }

        #region GetAllUserSingleDc
        private void GetAllUserSingleDc()
        {
            Program objProgram = new Program();
            string strDomainController = "EnterYourDomainhere";
            string strActiveDirectoryHost = "LDAP://" + strDomainController;
            int intActiveDirectoryPageSize = 1000;
            DirectoryEntry directoryEntry = null;
            DirectorySearcher directorySearcher = null;
            SearchResultCollection searchResultCollection = null;
            DataTypeConverter objConverter = null;

            objConverter = new DataTypeConverter();

            directoryEntry = new DirectoryEntry(strActiveDirectoryHost, null, null, AuthenticationTypes.Secure);

            directorySearcher = new DirectorySearcher(directoryEntry)
            {
                Filter = "(sAMAccountName=GotRocks)", // Group

                SearchScope = SearchScope.Subtree,

                PageSize = intActiveDirectoryPageSize,

                PropertiesToLoad = { "isDeleted","isCriticalSystemObject","objectGUID","objectSid","objectCategory","sAMAccountName","sAMAccountType","cn","employeeId",
                                        "canonicalName","distinguishedName","userPrincipalName","displayName","givenName","sn","mail","telephoneNumber","title","department",
                                        "description","physicalDeliveryOfficeName","manager","userAccountControl","accountExpires","lastLogon","logonCount","lockoutTime",
                                        "primaryGroupID","pwdLastSet","uSNCreated","uSNChanged","whenCreated","whenChanged","badPasswordTime","badPwdCount","homeDirectory",
                                        "dNSHostName" }
            };

            searchResultCollection = directorySearcher.FindAll();

            try
            {
                foreach (SearchResult searchResult in searchResultCollection)
                {                    
                    Guid? gidObjectGuid = objConverter.ConvertByteAryToGuid(searchResult, "objectGUID");
                    string StrSamAccountName = objConverter.ConvertToString(searchResult, "sAMAccountName");
                    // Get new DirectoryEntry and retrieve the SchemaClassName from it by binding the current objectGUID to it.
                    string StrDirectoryEntryPath = strActiveDirectoryHost + "/<GUID=" + gidObjectGuid + ">";
                    string StrSchemaClassName = new DirectoryEntry(StrDirectoryEntryPath, null, null, AuthenticationTypes.Secure).SchemaClassName;

                    #region GetGroupMembers
                    if (StrSchemaClassName == "group")
                    {
                        // FAST!
                        var watch = System.Diagnostics.Stopwatch.StartNew();
                        List<string> listGroupMemberDn = GetGroupMemberList(gidObjectGuid.ToString(), strActiveDirectoryHost, intActiveDirectoryPageSize);
                        watch.Stop();
                        var listGroupMemberDnElapsedMs = watch.ElapsedMilliseconds;

                        // SLOW!
                        watch = System.Diagnostics.Stopwatch.StartNew();
                        List<Guid> listGroupMemberGuidRecursiveTrue = GetGroupMemberList(gidObjectGuid.ToString(), strDomainController, true);
                        watch.Stop();
                        var listGroupMemberGuidRecursiveTrueElapsedMs = watch.ElapsedMilliseconds;

                        watch = System.Diagnostics.Stopwatch.StartNew();
                        List<Guid> listGroupMemberGuidRecursiveFalse = GetGroupMemberList(gidObjectGuid.ToString(), strDomainController, false);
                        watch.Stop();
                        var listGroupMemberGuidRecursiveFalseElapsedMs = watch.ElapsedMilliseconds;

                        ////// Display all members of the list.
                        //listGroupMemberDn.ForEach(item => Console.WriteLine("Member GUID: {0}", item));
                        //listGroupMemberGuidRecursiveTrue.ForEach(item => Console.WriteLine("Member GUID: {0}", item));
                        //listGroupMemberGuidRecursiveFalse.ForEach(item => Console.WriteLine("Member GUID: {0}", item));

                        Console.WriteLine("objectGUID: {0}", gidObjectGuid);
                        Console.WriteLine("sAMAccountName: {0}", strSamAccountName);

                        // Result: 350
                        Console.WriteLine("\nlistGroupMemberDn Count Members: {0}", listGroupMemberDn.Count);
                        Console.WriteLine("Total RunTime listGroupMemberDnElapsedMs (in milliseconds): {0}", listGroupMemberDnElapsedMs);

                        // Result: 6500
                        Console.WriteLine("\nlistGroupMemberGuidRecursiveTrue Count Members: {0}", listGroupMemberGuidRecursiveTrue.Count);
                        Console.WriteLine("Total RunTime listGroupMemberGuidRecursiveTrueElapsedMs (in milliseconds): {0}", listGroupMemberGuidRecursiveTrueElapsedMs);

                        // Result: 6500
                        Console.WriteLine("\nlistGroupMemberGuidRecursiveFalse Count Members: {0}", listGroupMemberGuidRecursiveFalse.Count);
                        Console.WriteLine("Total RunTime listGroupMemberGuidRecursiveFalseElapsedMs (in milliseconds): {0}", listGroupMemberGuidRecursiveFalseElapsedMs);
                        Console.WriteLine("\n");
                    }
                    #endregion

                    #region CurrentSearchResult
                    else
                    {
                        Console.WriteLine("ObjectGuid = {0}", gidObjectGuid);
                        Console.WriteLine("SamAccountName = {0}", strSamAccountName);

                    }
                    #endregion
                }

                Console.WriteLine("\nPress any key to continue.");
                Console.ReadKey();
            }
            finally
            {
                objProgram = null;
                intActiveDirectoryPageSize = -1;
                strActiveDirectoryHost = null;
                strDomainController = null;
                directoryEntry.Close();
                if (directoryEntry != null) directoryEntry.Dispose();
                if (directorySearcher != null) directorySearcher.Dispose();
                if (searchResultCollection != null) searchResultCollection.Dispose();
                objConverter = null;
            }
        }
        #endregion

        #region GetGroupMemberListGuid
        private List<Guid> GetGroupMemberList(string strPropertyValue, string strDomainController, bool bolRecursive)
        {
            List<Guid> listGroupMemberGuid = null;
            List<Principal> listPrincipalNoNull = null;
            GroupPrincipal groupPrincipal = null;
            PrincipalSearchResult<Principal> listPrincipalSearchResult = null;
            PrincipalContext principalContext = null;
            ContextType contextType;
            IdentityType identityType;

            try
            {
                listGroupMemberGuid = new List<Guid>();

                contextType = ContextType.Domain;

                principalContext = new PrincipalContext(contextType, strDomainController);

                identityType = IdentityType.Guid;

                groupPrincipal = GroupPrincipal.FindByIdentity(principalContext, identityType, strPropertyValue);

                if (groupPrincipal != null)
                {
                    listPrincipalSearchResult = groupPrincipal.GetMembers(bolRecursive);

                    listPrincipalNoNull = listPrincipalSearchResult.Where(item => item.Name != null).ToList();

                    foreach (Principal principal in listPrincipalNoNull)
                    {
                        listGroupMemberGuid.Add((Guid)principal.Guid);
                    }
                }

                return listGroupMemberGuid;
            }
            catch (MultipleMatchesException)
            {
                throw new MultipleMatchesException(strPropertyValue);
            }
            finally
            {
                // Cleanup objects.
                listGroupMemberGuid = null;
                listPrincipalNoNull = null;
                principalContext = null;
                if (groupPrincipal != null) groupPrincipal.Dispose();
                if (listPrincipalSearchResult != null) listPrincipalSearchResult.Dispose();
                if (principalContext != null) principalContext.Dispose();
            }
        }
        #endregion

        #region GetGroupMemberListDn
        private List<string> GetGroupMemberList(string strPropertyValue, string strActiveDirectoryHost, int intActiveDirectoryPageSize)
        {
            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}-*" });
            }
            return members;
        }
        #endregion

        #region DataTypeConvert
        private class DataTypeConverter
        {
            public DataTypeConverter() { }

            public String ConvertToString(SearchResult searchResult, string strPropertyName)
            {
                String bufferObjectString = null;

                try
                {
                    bufferObjectString = (String)this.GetPropertyValue(searchResult, strPropertyName);

                    if (string.IsNullOrEmpty(bufferObjectString))
                    {
                        return null;
                    }
                    else
                    {
                        return bufferObjectString;
                    }
                }
                finally
                {
                    bufferObjectString = null;
                }
            }

            public Guid? ConvertByteAryToGuid(SearchResult searchResult, string strPropertyName)
            {
                Guid? bufferObjectGuid = null;

                try
                {
                    bufferObjectGuid = new Guid((Byte[])(Array)this.GetPropertyValue(searchResult, strPropertyName));

                    if (bufferObjectGuid == null || bufferObjectGuid == Guid.Empty)
                    {
                        throw new NullReferenceException("The field " + strPropertyName + ", of type GUID, can neither be NULL nor empty.");
                    }
                    else
                    {
                        return bufferObjectGuid;
                    }
                }
                finally
                {
                    bufferObjectGuid = null;
                }
            }
        }
        #endregion
    }
}

1 个答案:

答案 0 :(得分:2)

最后一个代码块(Update 2)就是答案!

用于阅读member属性的代码比它需要的更复杂。可能有一个原因导致它返回偏斜的结果,但我看起来并不太难,因为你根本不需要使用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;
}

这样称呼:

var members = GetGroupMemberList("00000000-0000-0000-0000-000000000000", "domain.com");

这不是递归的。要使其递归,您必须从每个成员创建一个新的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;
}

更新的 我确实需要编辑你的GetMembers示例,因为它一直在向我抛出异常。我注释掉.Where行,并更改了将成员添加到列表中的foreach循环:

        //listPrincipalNoNull = listPrincipalSearchResult.Where(item => item.Name != null).ToList();
        if (groupPrincipal != null) {
            foreach (Principal principal in listPrincipalSearchResult) {
                listGroupMemberGuid.Add(((DirectoryEntry)principal.GetUnderlyingObject()).Guid);
            }
        }

当然,这是编制Guids列表而不是DN。

更新2: 这个版本还会提取将该群组作为主要群组的用户(但未在该群组的member属性中列出组)。 GetMembers似乎是这样做的。用户创建的组作为主要组是奇怪的,但技术上是可行的。部分内容将从此处的答案中复制出来:How to retrieve Users in a Group, including primary group users

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;
}