Если вам нужна скорость, вообще не используйте пространство имен System.DirectoryServices.AccountManagement
(GroupPrincipal
, UserPrincipal
и т. Д.). Это облегчает кодирование, но это slloooowwww.
Используйте только DirectorySearcher
и DirectoryEntry
. (Пространство имен AccountManagement
это просто оболочка для этого)
Я имел эту беседу с кем-то еще не так давно. Вы можете прочитать полный чат здесь , но в одном случае, когда в группе было 4873 члена, метод AccountManagement
GetMember()
занял 200 секунд, тогда как использование DirectoryEntry
заняло всего 16 секунд.
Однако есть несколько предостережений:
- Не смотрите на атрибут
memberOf
(как подсказывает ответ JPBlanc). Он не найдет членов группы Local Local. Атрибут memberOf
показывает только универсальные группы и глобальные группы только в одном домене. Домен Локальные группы там не отображаются.
- Просмотр атрибута
member
группы даст вам только 1500 участников за раз. Вы должны получить членов в партиях по 1500.
- Учетная запись может иметь для
primaryGroupId
любую группу и считаться частью этой группы (но не отображаться в атрибуте member
этой группы). Обычно это относится только к группе Domain Users
.
- Если в локальной доменной группе есть пользователи из внешнего домена, они отображаются как объекты принципала внешней безопасности, которые содержат SID для фактической учетной записи во внешнем домене. Необходимо выполнить дополнительную работу, чтобы найти учетную запись во внешнем домене.
Метод GetMember()
пространства имен AccountManagement
заботится обо всех этих вещах, но не так эффективно, как мог бы.
Помогая этому другому пользователю, я разработал метод, который охватит первые три проблемы выше, но не № 4. Это последний блок кода в этом ответе: https://stackoverflow.com/a/49241443/1202807
Обновление: Вы упомянули, что самая трудоемкая часть - это циклический просмотр членов. Это потому, что вы привязаны к каждому члену, что понятно. Вы можете уменьшить это, вызвав .RefreshCache()
для объекта 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;
}