kain64b
kain64b

Reputation: 2326

GroupPrincipal.GetMembers and cross-domain members error

I have 2 domains, A and B. The Domain A has the group GroupA which contains users from Domain B.
My code:

 using (var context = new PrincipalContext(ContextType.Domain, DomainName, User, Password))
{

    using (var groupPrincipal = GroupPrincipal.FindByIdentity(context, IdentityType.SamAccountName,
                                                              groupName))
    {
        if (groupPrincipal == null) return null;
        using (var principalSearchResult = groupPrincipal.GetMembers(true))
        {
            var changedUsersFromGroup =
                principalSearchResult
                .Where(member => member is UserPrincipal)
                .Where(member => IsModifiedUser(member, usnChanged))
                .Cast<UserPrincipal>()
                .Select(adsUser => new AdsUser(adsUser)).Cast<IAdsUser>()
                .ToArray();

            return changedUsersFromGroup;
        }
    }

}

System.DirectoryServices.AccountManagement.PrincipalOperationException: While trying to resolve a cross-store reference, the target principal could not be found in the domain indicated by the principal's SID.

But if I add user from here

new PrincipalContext(ContextType.Domain, DomainName, User, Password)

to domain B, it works correctly.
How can I fix it?

Upvotes: 1

Views: 2285

Answers (5)

GuestUser
GuestUser

Reputation: 11

At least in .NET 4.7, you can work around it by manually managing the enumerator. This has been tested and has been able to successfully get past the errors.

static System.Guid[] GetGroupMemberGuids(System.DirectoryServices.AccountManagement.GroupPrincipal group)
{
    System.Collections.Generic.List<System.Guid> result = new List<Guid>();
    if (group == null) return null;
    System.DirectoryServices.AccountManagement.PrincipalCollection px = group.Members;
    System.Collections.IEnumerator en = px.GetEnumerator();
    bool hasMore = true;
    int consecFaults = 0;
    while (hasMore && consecFaults < 10)
    {
        System.DirectoryServices.AccountManagement.Principal csr = null;
        try
        {
            hasMore = en.MoveNext();
            if (!hasMore) break;
            csr = (System.DirectoryServices.AccountManagement.Principal)en.Current;
            consecFaults = 0;
        }
        catch (System.DirectoryServices.AccountManagement.PrincipalOperationException e)
        {
            Console.Error.WriteLine("    Unable to enumerate a member due to the following error: {0}", e.Message);
            consecFaults++;
            csr = null;
        }
        if (csr is System.DirectoryServices.AccountManagement.UserPrincipal)
            result.Add(csr.Guid.Value);
    }
    if (consecFaults >= 10) throw new InvalidOperationException("Too many consecutive errors on retrieval.");
    return result.ToArray();
}

Upvotes: 1

kain64b
kain64b

Reputation: 2326

problem found and reported to MS as bug. currently impossible to do it with .net :( but it works via native c++ api via queries

Upvotes: 0

Sebastian
Sebastian

Reputation: 379

Unfortunately I currently cannot test it, but maybe you can try this

var contextB = new PrincipalContext(ContextType.Domain, DomainName_B, User_B, Password_B)
[..]
    var changedUsersFromGroup =
                    principalSearchResult
                    .Where(member => member is UserPrincipal)
                    .Where(member => IsModifiedUser(member, usnChanged))
                    .Select(principal => Principal.FindByIdentity(contextB, principal.SamAccountName))
                    .Cast<UserPrincipal>()
                    .Select(adsUser => new AdsUser(adsUser)).Cast<IAdsUser>()
                    .ToArray();

This could work, but only, if all members are in Domain B of course. If they are mixed in different domains you may have to filter it before that and iterate through your domains.
Alternatively you could run your application with a domain account? Then don't pass user/pass and give the required access rights to this account to avoid the error.
Explanation: The domain context will switch for your retrieved principals (you can view this in debugging mode if you comment out the new AdsUser / IAdsUser Cast part). This is the one, that seems to cause the exception. Though exactly this is the part I cannot test, I think the creation of AdsUser creates a new ldap Bind to the target Domain. This fails with the "original" Credentials. Is AdsUser Part of ActiveDS or 3rd Party?
I did not find any hint if passing credentials uses basic authentication, but I think it should. Using application credentials uses negotiate and "handling this over" to the new AdsUser(..) should fix the issue as well.

Upvotes: 0

Itay Podhajcer
Itay Podhajcer

Reputation: 2656

It seems you are defining a PrincipalContext of domain A (so you can get the group), but because the users inside the group are defined in domain B the context cannot access them (as it's a domain A context).

You might need to define a second 'PricipalContext` for domain B and run the query against it and filter the objects using maybe the list of SIDs of the users located in the domain A group (you'll need to get the list of SIDs without causing the underlying code to try and resolve them).

Hope it helps!

Upvotes: 0

user2316116
user2316116

Reputation: 6824

Check if it behaves differently when not casting to UserPrincipal, e.g.

var changedUsersFromGroup = principalSearchResult.ToArray();

As per other threads it might be some issue there. Also as per MSDN, using GetMembers(true) returned principal collection that does not contain group objects, only leaf nodes are returned, and so maybe you don't need that casting at all. Next, is to check how many results such search would return. If your AD has many users/nested groups, it might be better to try not to use GetMembers(true) to ensure it works on small groups of users.

Upvotes: 0

Related Questions