C# Active Directory:DirectoryEntry成员列表GroupPrincipal.GetMembers()
我有一个小组,我们叫它GotRocks。我试图获得它的所有成员,但我得到的结果在DirectoryEntry和AccountManagement之间有着天壤之别。以下是按成员检索方法列出的计数: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
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;
}