C# 当组(或递归的子组)包含ForeignSecurityPrincipal时,GroupPrincipal.GetMembers失败

C# 当组(或递归的子组)包含ForeignSecurityPrincipal时,GroupPrincipal.GetMembers失败,c#,active-directory,directoryservices,C#,Active Directory,Directoryservices,这与其说是一个问题,不如说是为遇到相同问题的任何人提供信息。 出现以下错误: System.DirectoryServices.AccountManagement.PrincipalOperationException: An error (87) occurred while enumerating the groups. The group's SID could not be resolved. at System.DirectoryServices.AccountManagement.

这与其说是一个问题,不如说是为遇到相同问题的任何人提供信息。

出现以下错误:

System.DirectoryServices.AccountManagement.PrincipalOperationException: An error (87) occurred while enumerating the groups. The group's SID could not be resolved. 
at System.DirectoryServices.AccountManagement.SidList.TranslateSids(String target, IntPtr[] pSids) 
at System.DirectoryServices.AccountManagement.SidList.ctor(List`1 sidListByteFormat, String target, NetCred credentials) 
at System.DirectoryServices.AccountManagement.ADDNLinkedAttrSet.TranslateForeignMembers()
当运行以下代码且组或子组包含ForeignSecurityPrincipal时:

private static void GetUsersFromGroup()
{
    var groupDistinguishedName = "CN=IIS_IUSRS,CN=Builtin,DC=Domain,DC=com";
    //NB: Exception thrown during iteration of members rather than call to GetMembers.    
    using (PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "Domain", "Username", "Password"))
    {
        using (GroupPrincipal groupPrincipal = GroupPrincipal.FindByIdentity(ctx, IdentityType.DistinguishedName, groupDistinguishedName))
        {                    
            using (var searchResults = groupPrincipal.GetMembers(true))//Occurs when false also.
            {
                foreach (UserPrincipal item in searchResults.OfType())
                {
                    Console.WriteLine("Found user: {0}", item.SamAccountName)
                }
            }
        }
    }
}
我向Microsoft打了一个支持电话,他们已经确认这是一个问题。内部出现了一个bug,但尚未确认是否会修复。

Microsoft建议使用以下变通代码,但由于重复调用UserPrincipal.FindByIdentity,它在用户数量众多的组上的性能很差

class Program
{
    //"CN=IIS_IUSRS,CN=Builtin,DC=dev-sp-sandbox,DC=local"; //TODO MODIFY THIS LINE ACCORDING TO YOUR DC CONFIGURATION

    static void Main(string[] args)
    {
        if (args.Length != 1)
        {
            Console.WriteLine("Usage: ListGroupMembers \"group's DistinguishedName\"");
            Console.WriteLine("Example: ListGroupMembers \"CN=IIS_IUSRS,CN=Builtin,DC=MyDomain,DC=local\"");
            return;
        }

        string groupDistinguishedName = args[0];

        PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "dev-sp-dc", "Administrator", "Corp123!");
        List<UserPrincipal> users = new List<UserPrincipal>();
        listGroupMembers(groupDistinguishedName, ctx, users);

        foreach (UserPrincipal u in users)
        {
            Console.WriteLine(u.DistinguishedName);
        }
    }

    //Recursively list the group's members which are not Foreign Security Principals
    private static void listGroupMembers(string groupDistinguishedName, PrincipalContext ctx, List<UserPrincipal> users)
    {
        DirectoryEntry group = new DirectoryEntry("LDAP://" + groupDistinguishedName);
        foreach (string dn in group.Properties["member"])
        {

            DirectoryEntry gpMemberEntry = new DirectoryEntry("LDAP://" + dn);
            System.DirectoryServices.PropertyCollection userProps = gpMemberEntry.Properties;

            object[] objCls = (userProps["objectClass"].Value) as object[];

            if (objCls.Contains("group"))
                listGroupMembers(userProps["distinguishedName"].Value as string, ctx, users);

            if (!objCls.Contains("foreignSecurityPrincipal"))
            {                    
                UserPrincipal u = UserPrincipal.FindByIdentity(ctx, IdentityType.DistinguishedName, dn);
                if(u!=null)  // u==null for any other types except users
                    users.Add(u);
            }
        }                 
    }
}
类程序
{
//“CN=IIS\u IUSRS,CN=Builtin,DC=dev sp sandbox,DC=local”;//根据DC配置修改此行
静态void Main(字符串[]参数)
{
如果(args.Length!=1)
{
Console.WriteLine(“用法:ListGroupMembers\“group'sDiscrimitedName\”);
WriteLine(“示例:ListGroupMembers\“CN=IIS\u IUSRS,CN=Builtin,DC=MyDomain,DC=local\”);
返回;
}
字符串GroupDifferentiedName=args[0];
PrincipalContext ctx=新PrincipalContext(ContextType.Domain,“dev sp dc”,“Administrator”,“Corp123!”);
列表用户=新列表();
listGroupMembers(GroupDifferentiedName、ctx、users);
foreach(用户中的用户主体u)
{
Console.WriteLine(u.DifferentiedName);
}
}
//递归列出非外部安全主体的组成员
私有静态void listGroupMembers(字符串GroupDifferentiedName、PrincipalContext ctx、列表用户)
{
DirectoryEntry组=新的DirectoryEntry(“LDAP://”+GroupDifferentizedName);
foreach(group.Properties[“member”]中的字符串dn)
{
DirectoryEntry gpMemberEntry=新的DirectoryEntry(“LDAP://”+dn);
System.DirectoryServices.PropertyCollection userProps=gpMemberEntry.Properties;
对象[]objCls=(userProps[“objectClass”].Value)作为对象[];
if(对象包含(“组”))
listGroupMembers(userProps[“DifferentizedName”]。值为字符串、ctx、用户);
如果(!objCls.Contains(“foreignSecurityPrincipal”))
{                    
UserPrincipal u=UserPrincipal.FindByIdentity(ctx,IdentityType.DifferentiedName,dn);
if(u!=null)//u==null对于除用户以外的任何其他类型
用户。添加(u);
}
}                 
}
}
可以修改上述代码以查找导致组中出现问题的外部安全主体

Microsoft提供了有关外国安全主体的以下信息:

这是AD中的一类对象,表示来自外部源的安全主体(因此是另一个林/域或下面的“特殊”帐户之一)。 课程记录如下: 容器记录在这里: FSP不是AD中的真实对象,而是指向位于不同的受信任域/林中的对象的占位符(指针)。它也可以是“特殊身份”之一,这是一组众所周知的帐户,这些帐户也被归类为FSP,因为它们的SID与域SID不同。 例如,此处记录的匿名、经过身份验证的用户、批处理和其他几个帐户:
accountmanagement库有许多令人痛心的缺陷,这只是众多缺陷中的另一个

可以做的一件事是调整LDAP查询,使其作为查询的一部分而不是在循环中同时检查组成员资格和对象类型,从而使事情稍微快一点。老实说,我怀疑这会有多大不同

这个问题的大部分灵感来自于

查询:
(&(!objectClass=foreignSecurityPrincipal)(memberof=CN=YourGroup,OU=Users,DC=YourDomain,DC=com))

注意:这是一个未经测试的查询

如果有一种方法可以在AccountManagement中运行LDAP查询(我的另一个抱怨),那么问题就到此为止了,因为您可以运行查询并让AccountManagement从此处开始,但此选项不存在


根据个人经验,如果你坚持使用AccountManagement,我看不到任何其他选择。您可以做的是转储AccountManagement并仅使用DirectoryServices。AccountManagement所做的只是包装DirectoryEntry对象。无论如何,您可以编写一些帮助器类来完成类似的工作。

作为替代方案,您可以使用以下代码获取成员:

var pth = "LDAP://ex.invalid/CN=grpName,OU=Groups,OU=whatever,DC=ex,DC=invalid";
var dirEntry = new DirectoryEntry(pth);
var members = dirEntry.Invoke("Members"); //COM object
foreach (var member in (IEnumerable)members) {
    var userEntry = new DirectoryEntry(member); //member is COM object
    var sid = new SecurityIdentifier((byte[]) userEntry.InvokeGet("objectSid"), 0);
    var typ = typeof(System.Security.Principal.NTAccount);
    var account = (NTAccount)sid.Translate(typ);
    Console.WriteLine(account.Value);
}

当然,这是一个旧线程,但可能会帮助别人。我用下面的代码块解决了这个问题。Principal类公开了一个名为StructuralObjectClass的属性,该属性告诉您该Principal的AD类是什么。我用它来决定对象是否是用户。GetMembers(true)递归搜索相关groupPrincipal中的所有嵌套成员

希望这对别人有帮助

    List<UserPrincipal> members = new List<UserPrincipal>();
    foreach (var principal in groupPrincipal.GetMembers(true))
    {
        var type = principal.StructuralObjectClass;
        if (type.Contains("user"))
            members.Add((UserPrincipal)principal);
    }
列表成员=新列表();
foreach(groupPrincipal.GetMembers中的var主体(true))
{
变量类型=principal.StructuralObjectClass;
if(type.Contains(“用户”))
添加((用户主体)主体);
}
谢谢,
R

非常感谢,彼得。我还没试过你的建议。你有机会试过我的答案吗?或者您是否采取了另一种方法?您知道此错误是否在较新的.net framework版本中得到了修复吗?
foreach
如果
groupPrincipal.GetMembers()
count为0,则将失败。您可以取消订阅。这是多年来唯一有效的解决方案!