如果你想要速度,不要使用System.DirectoryServices.AccountManagement
完全命名空间(GroupPrincipal
, UserPrincipal
, ETC。)。它使编码变得更容易,但它太慢了。
仅供使用DirectorySearcher
and DirectoryEntry
. (The AccountManagement
无论如何,命名空间只是一个包装器)
不久前我和别人讨论过这个问题。你可以阅读完整的聊天内容在这里 https://chat.stackoverflow.com/rooms/166716/discussion-between-gabriel-luci-and-j-weezy,但在一个群组有 4873 名成员的情况下,AccountManagement
's GetMember()
方法花费了 200 秒,其中使用DirectoryEntry
仅用了16秒。
但是,有一些注意事项:
- 不要看
memberOf
属性(正如 JPBlanc 的回答所示)。它不会找到域本地组的成员。这memberOf
属性仅显示通用组,全局组仅显示在同一域中。域本地组不会显示在那里。
- 看着
member
一个群组的属性一次只能为您提供 1500 名成员。您必须以 1500 个为批次检索成员。
- 一个帐户可能有
primaryGroupId
设置为任何组,并被视为该组的一部分(但不会出现在member
该组的属性)。通常只有以下情况才会出现这种情况Domain Users
group.
- 如果域本地组有来自外部域的用户,它们将显示为外部安全主体对象,该对象保存外部域上实际帐户的 SID。需要完成一些额外的工作才能在外部域上查找帐户。
The AccountManagement
命名空间的GetMember()
方法可以处理所有这些事情,只是效率不高。
在帮助其他用户时,我确实整理了一个方法来解决上面的前三个问题,但不包括#4。这是这个答案中的最后一个代码块:https://stackoverflow.com/a/49241443/1202807 https://stackoverflow.com/a/49241443/1202807
Update:
(我已在我的网站上记录了所有这些:查找群组的所有成员 https://www.gabescode.com/active-directory/2018/11/30/find-all-members-of-group.html)
您提到最耗时的部分是循环访问成员。那是因为你对每个成员都有约束力,这是可以理解的。您可以通过致电来减少这种情况.RefreshCache()
on the DirectoryEntry
对象仅加载您需要的属性。否则,当你第一次使用时Properties
,它将获取每个具有值的属性,这会无缘无故地增加时间。
下面是我使用的一个例子。我对一个有 803 名成员的组(在嵌套组中)进行了测试,发现具有.RefreshCache()
线路持续缩短约 10 秒,甚至更多(不使用约 60 秒,使用时约 45-50 秒)。
此方法不会考虑我上面提到的第 3 点和第 4 点。例如,它会默默地忽略外国安全主体。但如果您只有一个没有信任的域,则无需关心。
private static List<string> GetGroupMemberList(DirectoryEntry group, bool recurse = false) {
var members = new List<string>();
group.RefreshCache(new[] { "member" });
while (true) {
var memberDns = group.Properties["member"];
foreach (var member in memberDns) {
var memberDe = new DirectoryEntry($"LDAP://{member}");
memberDe.RefreshCache(new[] { "objectClass", "sAMAccountName" });
if (recurse && memberDe.Properties["objectClass"].Contains("group")) {
members.AddRange(GetGroupMemberList(memberDe, true));
} else {
var username = memberDe.Properties["sAMAccountName"]?.Value?.ToString();
if (!string.IsNullOrEmpty(username)) { //It will be null if this is a Foreign Security Principal
members.Add(username);
}
}
}
if (memberDns.Count == 0) break;
try {
group.RefreshCache(new[] {$"member;range={members.Count}-*"});
} catch (COMException e) {
if (e.ErrorCode == unchecked((int) 0x80072020)) { //no more results
break;
}
throw;
}
}
return members;
}