我有一个小组,我们称之为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
}
}
答案 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;
}