c# - Cannot successfully iterate through AccountManagement.GroupPrincipal GetMembers Object
问题描述
I am using System.DirectoryServices.AccountManagement.GroupPrincipal FindByIdentity in C# to create an object containing the group members (user IDs and names) for the target group. My goal is to iterate through the resulting list of UserPrincipals and print the SamAccountName and DisplayName for each. For some target groups, this is working fine; for others it fails on a user (or perhaps more than one) that throws the following error:
System.DirectoryServices.AccountManagement.PrincipalOperationException HResult=0x80131501 Message=The specified directory service attribute or value does not exist.
When I use PowerShell’s Get-ADGroup to get the group object for one of the failing targets and iterate through it, there is no problem.
I’ve looked into the AD Group memberships and I believe the problem is that in some groups (those failing), some members may have been disabled, or may be part of a cross-domain trust. However, their status is of no consequence to me; I just want to list everything so the group owner can decide which members get migrated to new groups.
The method I am using is:
private static ArrayList EnumerateGroupMembers()
{
ArrayList gmObjects = new ArrayList();
string ldapVal = "DC=dc1,DC=dc2,DC=dcMain,DC=dcSecondary";
string ldapDom = "dc1.dc2.dcMain.dcSecondary:389";
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, ldapDom, ldapVal);
GroupPrincipal group = GroupPrincipal.FindByIdentity(ctx, "AD-GROUPNAME");
if (group != null)
{
var users = group.GetMembers(true);
//*** PrincipalOperationException occurs here ***
foreach (UserPrincipal p in users)
{
Console.WriteLine(p.SamAccountName + ", " + p.DisplayName);
}
Console.WriteLine("Done");
Console.ReadKey();
}
//*** Please note: I know I am returning an empty list here. I'm writing to Console during development
return gmObjects;
}
Can anyone suggest how I can iterate through the list of UserPrincipals without throwing a PrincipalOperationException? Or, at least a way to bypass the UserPrincipal occurrences that are causing these errors? Even if I cannot list the failing users I will survive.
解决方案
不幸的是System.DirectoryServices.AccountManagement
,正如您所发现的,命名空间与外国安全主体不能很好地配合使用。
您可以使用System.DirectoryServices
名称空间来执行此操作,这是AccountManagement
在幕后使用的。无论如何,您可能会发现它的性能更好,尽管它有点复杂。
无论如何,我一直想为我的网站写一些类似的东西,所以这里有一种方法可以找到一个组的所有成员并以DOMAIN\username
格式列出它们。它也可以选择扩展嵌套组。
public static List<string> GetGroupMemberList(DirectoryEntry group, bool recurse = false, Dictionary<string, string> domainSidMapping = null) {
var members = new List<string>();
group.RefreshCache(new[] { "member", "canonicalName" });
if (domainSidMapping == null) {
//Find all the trusted domains and create a dictionary that maps the domain's SID to its DNS name
var groupCn = (string) group.Properties["canonicalName"].Value;
var domainDns = groupCn.Substring(0, groupCn.IndexOf("/", StringComparison.Ordinal));
var domain = Domain.GetDomain(new DirectoryContext(DirectoryContextType.Domain, domainDns));
var trusts = domain.GetAllTrustRelationships();
domainSidMapping = new Dictionary<string, string>();
foreach (TrustRelationshipInformation trust in trusts) {
using (var trustedDomain = new DirectoryEntry($"LDAP://{trust.TargetName}")) {
try {
trustedDomain.RefreshCache(new [] {"objectSid"});
var domainSid = new SecurityIdentifier((byte[]) trustedDomain.Properties["objectSid"].Value, 0).ToString();
domainSidMapping.Add(domainSid, trust.TargetName);
} catch (Exception e) {
//This can happen if you're running this with credentials
//that aren't trusted on the other domain or if the domain
//can't be contacted
Console.WriteLine($"Can't connect to domain {trust.TargetName}: {e.Message}");
}
}
}
}
while (true) {
var memberDns = group.Properties["member"];
foreach (string member in memberDns) {
using (var memberDe = new DirectoryEntry($"LDAP://{member.Replace("/", "\\/")}")) {
memberDe.RefreshCache(new[] { "objectClass", "msDS-PrincipalName", "cn" });
if (recurse && memberDe.Properties["objectClass"].Contains("group")) {
members.AddRange(GetGroupMemberList(memberDe, true, domainSidMapping));
} else if (memberDe.Properties["objectClass"].Contains("foreignSecurityPrincipal")) {
//User is on a trusted domain
var foreignUserSid = memberDe.Properties["cn"].Value.ToString();
//The SID of the domain is the SID of the user minus the last block of numbers
var foreignDomainSid = foreignUserSid.Substring(0, foreignUserSid.LastIndexOf("-"));
if (domainSidMapping.TryGetValue(foreignDomainSid, out var foreignDomainDns)) {
using (var foreignUser = new DirectoryEntry($"LDAP://{foreignDomainDns}/<SID={foreignUserSid}>")) {
foreignUser.RefreshCache(new[] { "msDS-PrincipalName" });
members.Add(foreignUser.Properties["msDS-PrincipalName"].Value.ToString());
}
} else {
//unknown domain
members.Add(foreignUserSid);
}
} else {
var username = memberDe.Properties["msDS-PrincipalName"].Value.ToString();
if (!string.IsNullOrEmpty(username)) {
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;
}
这有几件事要做:
- 该
member
属性一次只会为您提供 1500 个帐户,因此您必须要求更多,直到没有剩余为止。 - Foreign Security Principal 拥有外域帐户的 SID,但您需要使用域的 DNS 名称连接到它(即
$"LDAP://{foreignDomainDns}/<SID={foreignUserSid}>"
)。所以这个方法将查找域的所有信任,并在域的 SID 和它的 DNS 名称之间创建一个映射。
你像这样使用它:
var group = new DirectoryEntry($"LDAP://{distinguishedNameOfGroup}");
var members = GetGroupMemberList(group);
或者,GetGroupMemberList(group, true)
如果您也想在嵌套组中查找用户,也可以使用。
请记住,这不会找到将此组作为主要组的用户,因为主要组不使用该member
属性。我在我的什么让会员成为会员一文中对此进行了描述。在大多数情况下,您不会在意。
推荐阅读
- c# - 如何获取服务器(不是网络服务器)的 IP 地址,而是在 Xamarin.Forms 的 wifi 模块上创建的 TCP 服务器
- azure-devops - 如何为 Pull Request 设置默认审阅者?
- android - 如何在 Android 应用程序中将 Realm 结果流式传输到 LiveData
- angular - Angular 2 - 具有 ChangeDetectorRef 依赖项的测试组件
- api - Create Zoho campaign with api gives error
- c - How does linux thread run while main () return
- docker - DNS not working between two linked docker containers - getaddrinfo EAI_AGAIN error
- c# - WPF SQLite EF "No such table" if run application from startup of shell
- php - Carbon setLocale 不工作 Laravel
- qt - Missing memberfunction of QOpenGLExtraFunctions