Active Directory:获取所有组成员

Active Directory: Get all group members

问题:如何以一致的方式检索所有群组成员?

上下文:我正在检索个人、组、联系人或计算机的所有对象:

Filter = "(|(objectCategory=person)(objectCategory=computer)(objectCategory=group))"

我现在需要检索组的所有成员。我已经开发了三种方法来做到这一点;但是,他们为同一组返回不同的结果,我不确定为什么。我怀疑它可能是由嵌套组(即组中组)引起的。调试它是一个挑战,因为某些组包含太多成员,以至于调试器超时并且没有显示任何结果。

方法 1 和 2 很慢。方法三很快。所以,我更喜欢使用方法3。

Group Name      Retrieval Method    Recursive Search    Count Members   Comment
Group1          AccountManagement   TRUE                505 
Group1          AccountManagement   FALSE               505 
Group1          DirectoryServices   N/A                 101 
Group2          AccountManagement   TRUE                440             Contains group name 'Group3'
Group2          AccountManagement   FALSE               440             Contains group name 'Group3'
Group2          DirectoryServices   N/A                 100             Contains group name 'Group3'
Group3          AccountManagement   TRUE                101             All Group3
Group3          AccountManagement   FALSE               2               All Group3
Group3          DirectoryServices   N/A                 2               1 user 1 group (Group2)

方法一和二:使用S.DS.AM获取群组成员,其中GetMembers()分别设置为true或false:https://msdn.microsoft.com/en-us/library/system.directoryservices.accountmanagement(v=vs.110).aspx

private static List<Guid> GetGroupMemberList(string strPropertyValue, string strDomainController, bool bolRecursive)
        {
            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);
                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);
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                listGroupMemberGuid = null;
                listPrincipalSearchResult.Dispose();
                principalContext.Dispose();
                groupPrincipal.Dispose();
            }
        }

方法三:使用S.DS.AD获取群成员:https://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectory(v=vs.110).aspx

private static List<string> GetGroupMemberList(string strPropertyValue, string strActiveDirectoryHost, int intActiveDirectoryPageSize)
        {
            List<string> listGroupMemberDn = new List<string>();
            string strPath = strActiveDirectoryHost + "/<GUID=" + strPropertyValue + ">";
            DirectoryEntry directoryEntryGroup;
            DirectoryEntry directoryEntryGroupMembers;
            DirectorySearcher directorySearcher;
            SearchResultCollection searchResultCollection;
            DataTypeConverter objConverter = null;

            objConverter = new DataTypeConverter();

            try
            {
                directoryEntryGroup = new DirectoryEntry(strPath, null, null, AuthenticationTypes.Secure);
                directoryEntryGroup.RefreshCache();
            }
            catch (Exception ex)
            {
                throw ex;
            }

            try
            {
                directorySearcher = new DirectorySearcher(directoryEntryGroup)
                {
                    //Filter = "(objectCategory=group)", // Group
                    SearchScope = SearchScope.Subtree,
                    PageSize = intActiveDirectoryPageSize,
                };
                directorySearcher.PropertiesToLoad.Add("objectGUID");
                searchResultCollection = directorySearcher.FindAll();
            }
            catch (Exception ex)
            {
                throw ex;
            }

            try
            {
                foreach (SearchResult searchResult in searchResultCollection)
                {
                    directoryEntryGroupMembers = searchResult.GetDirectoryEntry();

                    foreach (object objGroupMember in directoryEntryGroupMembers.Properties["member"])
                    {
                        listGroupMemberDn.Add((string)objGroupMember);
                    }
                }
                return listGroupMemberDn;
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                listGroupMemberDn = null;
                strPath = null;
                directoryEntryGroup.Dispose();
                directoryEntryGroupMembers = null;
                directorySearcher.Dispose();
                searchResultCollection.Dispose();
                objConverter = null;
            }
        }

方法 4:(循环执行 GetNextChunk() 方法)

 private static 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
        {
            // Setup a secure connection with Active Directory (AD) using Kerberos by setting the directoryEntry with AuthenticationTypes.Secure.
            directoryEntryGroup = new DirectoryEntry(strPath, null, null, AuthenticationTypes.Secure);

            // Load the property values for this DirectoryEntry object into the property cache.
            directoryEntryGroup.RefreshCache();
        }
        catch (Exception)
        { }

        #region Method1
        // Enumerate group members.
        try
        {
            // Check to see if the group has any members.
            if (directoryEntryGroup.Properties["member"].Count > 0)
            {
                int intStart = 0;
                while (true)
                {
                    // End of the range.
                    int intEnd = intStart + intIncrement - 1;

                    strMemberPropertyRange = string.Format("member;range={0}-{1}", intStart, intEnd);

                    directorySearcher = new DirectorySearcher(directoryEntryGroup)
                    {
                        // Set the Filter criteria that is used to constrain the search within AD.
                        Filter = "(|(objectCategory=person)(objectCategory=computer)(objectCategory=group))", // User, Contact, Group, Computer objects

                        // Set the SearchScope for how the AD tree is searched (Default = Subtree).
                        SearchScope = SearchScope.Base,

                        // The PageSize value should be set equal to the PageSize that is set by the AD administrator (Default = 0).
                        PageSize = intActiveDirectoryPageSize,

                        PropertiesToLoad = { strMemberPropertyRange }
                    };

                    try
                    {
                        // Populate the searchResultCollection with all records within AD that match the Filter criteria.
                        searchResultCollection = directorySearcher.FindAll();

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

                            var membersPropertyNames = membersProperties.PropertyNames.OfType<string>().Where(n => n.StartsWith("member;"));

                            foreach (var propertyName in membersPropertyNames)
                            {
                                // Get all members from the ranged result.
                                var members = membersProperties[propertyName];

                                foreach (string memberDn in members)
                                {
                                    // Add the member's "distinguishedName" attribute value to the list.
                                    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;
                    }

                    // Increment for the next range.
                    intStart += intIncrement;
                }
            }

            // return the listGroupMemberDn;
            return listGroupMemberDn;
        }
        #endregion

        finally
        {
            // Cleanup objects.
            listGroupMemberDn = null;
            strPath = null;
            strMemberPropertyRange = null;
            directoryEntryGroup.Dispose();
            directorySearcher.Dispose();
            searchResultCollection.Dispose();
        }
    }

System.DirectoryServices.AccountManagement 可能更方便,因为它隐藏了 AD 的大部分复杂性,但这也是它变慢的原因。您对正在发生的事情的控制较少。

DirectoryEntry 给你更多的控制权,但你必须处理一些复杂性。

这样就可以解释时差了。

但是您使用 DirectoryEntry 的方法似乎仍然过于复杂。为什么要使用 DirectorySearcher?它似乎没有添加任何东西。当您设置 directoryEntryGroup 时,您已经拥有该组。之后,您可以访问成员:

foreach (var member in directoryEntryGroup.Properties["member"]) {
    //member is a string of the distinguishedName
}

对于非常大的组,请注意默认情况下 AD 将记录限制为 returns 到 1500。因此一旦达到该数字,您将不得不要求更多。你这样做:

directoryEntryGroup.RefreshCache("member;range=1500-*")

然后以同样的方式再次遍历它们。如果你刚好又得到了 1500,那么再要(用 3000 代替 1500),等等,直到你得到所有。

这正是 System.DirectoryServices.AccountManagement 的 .NET Core 实现所做的(我认为 .NET 4.x 也是如此——我只是看不到该代码)。您可以在此处看到用于执行此操作的特殊 class .NET Core 代码(请参阅 GetNextChunk 方法):https://github.com/dotnet/corefx/blob/0eb5e7451028e9374b8bb03972aa945c128193e1/src/System.DirectoryServices.AccountManagement/src/System/DirectoryServices/AccountManagement/AD/RangeRetriever.cs

作为旁注:

catch (Exception ex)
{
    // Something went wrong. Throw an error.
    throw ex;
}

如果您打算 re-throw 一个异常而不做任何其他事情,那就不要捕获它。 Re-throwing 具有隐藏异常实际发生位置的效果,因为您的堆栈跟踪现在会说异常发生在 throw ex;,而不是告诉您异常发生的实际行。

即使是最后一个区块,您也可以使用 try/finally 而无需 catch