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

我有一个小组,我们称之为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似乎validation了AccountManagement的结果。 我也检查了儿童财产,但它是空白的。 此外,DirectoryEntry中列出的所有成员都不属于SchemaName组 – 它们都是用户。

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

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

更新1:方法:

方法1:DirectoryEntry

 private List GetGroupMemberList(string strPropertyValue, string strActiveDirectoryHost, int intActiveDirectoryPageSize) { // Variable declaration(s). List listGroupMemberDn = new List(); string strPath = strActiveDirectoryHost + "/"; 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().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 GetGroupMemberList(string strPropertyValue, string strDomainController, bool bolRecursive) { // Variable declaration(s). List listGroupMemberGuid = null; GroupPrincipal groupPrincipal = null; PrincipalSearchResult listPrincipalSearchResult = null; List listPrincipalNoNull = null; PrincipalContext principalContext = null; ContextType contextType; IdentityType identityType; try { listGroupMemberGuid = new List(); 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 + "/"; clsAdUserInfo.StrSchemaClassName = new DirectoryEntry(clsAdUserInfo.StrDirectoryEntryPath, null, null, AuthenticationTypes.Secure).SchemaClassName; if (clsAdUserInfo.StrSchemaClassName == "group") { // Calling the functions here. List listGroupMemberDnMethod1 = GetGroupMemberListStackOverflow(clsAdUserInfo.GidObjectGuid.ToString(), strActiveDirectoryHost, intActiveDirectoryPageSize); List 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 + "/"; string StrSchemaClassName = new DirectoryEntry(StrDirectoryEntryPath, null, null, AuthenticationTypes.Secure).SchemaClassName; #region GetGroupMembers if (StrSchemaClassName == "group") { // FAST! var watch = System.Diagnostics.Stopwatch.StartNew(); List listGroupMemberDn = GetGroupMemberList(gidObjectGuid.ToString(), strActiveDirectoryHost, intActiveDirectoryPageSize); watch.Stop(); var listGroupMemberDnElapsedMs = watch.ElapsedMilliseconds; // SLOW! watch = System.Diagnostics.Stopwatch.StartNew(); List listGroupMemberGuidRecursiveTrue = GetGroupMemberList(gidObjectGuid.ToString(), strDomainController, true); watch.Stop(); var listGroupMemberGuidRecursiveTrueElapsedMs = watch.ElapsedMilliseconds; watch = System.Diagnostics.Stopwatch.StartNew(); List 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 GetGroupMemberList(string strPropertyValue, string strDomainController, bool bolRecursive) { List listGroupMemberGuid = null; List listPrincipalNoNull = null; GroupPrincipal groupPrincipal = null; PrincipalSearchResult listPrincipalSearchResult = null; PrincipalContext principalContext = null; ContextType contextType; IdentityType identityType; try { listGroupMemberGuid = new List(); 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 GetGroupMemberList(string strPropertyValue, string strActiveDirectoryHost, int intActiveDirectoryPageSize) { List listGroupMemberDn = new List(); string strPath = strActiveDirectoryHost + "/"; const int intIncrement = 1500; // https://msdn.microsoft.com/en-us/library/windows/desktop/ms676302(v=vs.85).aspx var members = new List(); // The count result returns 350. var group = new DirectoryEntry(strPath, null, null, AuthenticationTypes.Secure); //var group = new DirectoryEntry($"LDAP://{"EnterYourDomainHere"}/", 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 } } 

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

用于读取member属性的代码比它需要的更复杂。 可能有一个原因导致它返回偏斜的结果,但我看起来并不太难,因为你根本不需要使用DirectorySearcher 。 我只是重写了它。

这是它应该是什么样子,它是最简单的forms:

 private static List GetGroupMemberList(string groupGuid, string domainDns) { var members = new List(); var group = new DirectoryEntry($"LDAP://{domainDns}/", 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 GetGroupMemberList(string groupGuid, string domainDns, bool recurse = false) { var members = new List(); var group = new DirectoryEntry($"LDAP://{domainDns}/", 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示例,因为它一直在向我抛出exception。 我注释掉.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似乎这样做。 用户创建的组作为主要组是奇怪的,但在技术上是可行的。 部分内容从以下答案中复制: 如何检索组中的用户,包括主要组用户

 private List GetGroupMemberList(string strPropertyValue, string strActiveDirectoryHost, int intActiveDirectoryPageSize) { // Variable declaration(s). List listGroupMemberDn = new List(); string strPath = strActiveDirectoryHost + "/"; const int intIncrement = 1500; // https://msdn.microsoft.com/en-us/library/windows/desktop/ms676302(v=vs.85).aspx var members = new List(); // The count result returns 350. var group = new DirectoryEntry(strPath, null, null, AuthenticationTypes.Secure); //var group = new DirectoryEntry($"LDAP://{"EnterYourDomainHere"}/", 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; }