GroupPrinciple.FindbyIdentity is too slow

2k views Asked by At

I have created a function to add one user at a time into particular group (Administrators etc) but it seems to be taking too long to respond on GroupPrinciple(5-10 seconds) and group.members.add(5-10 seconds) call and slowing down my app, it takes almost 15-20 sec to respond, is there a faster way to do this?

private static void Add()
{
 var userContext = new PrincipalContext(ContextType.Domain);
 var user = new UserPrincipal(userContext);
 user.SamAccountName = "c1111111";
 var searcher = new PrincipalSearcher(user);
 user = searcher.FindOne() as UserPrincipal;

var machineContext = new PrincipalContext(ContextType.Machine, "ABCDEFGHI1",
   null, ContextOptions.Negotiate, "c123789", "test123");
var group = GroupPrincipal.FindByIdentity(machineContext,"Administrators"); 

group.Members.Add(user); 

Console.WriteLine("saving group");
group.Save();

}
2

There are 2 answers

5
Scott Chamberlain On

I have run in to this myself, because you are using the same group every single time see if you can refactor out the finding of the group from the adding of the user and add multiple users at once.

private static void Add(IEnumerable<UserPrincipal> users)
{
    var machineContext = new PrincipalContext(ContextType.Machine, "ABCDEFGHI1",
       null, ContextOptions.Negotiate, "c123789", "test123");
    var group = GroupPrincipal.FindByIdentity(machineContext,"Administrators"); 
    foreach(var user in users)
    {
        group.Members.Add(user); 
    }
    Console.WriteLine("saving group");
    group.Save();
}

Or another option is find the group once but then cache it. Using Task.Run makes this very easy, just start the task up in the static constructor then grab the result in your Add function. .Result will block till the task finishes then will be instant after that. Important note, GroupPrincipal is not thread safe so you will need to lock around the modifications to the class.

static YourClassName()
{
    _administratorsGroup = Task.Run(() =>
    {
        var machineContext = new PrincipalContext(ContextType.Machine, "ABCDEFGHI1",
           null, ContextOptions.Negotiate, "c123789", "test123");
        return GroupPrincipal.FindByIdentity(machineContext,"Administrators"); 
    });
}

private static Task<GroupPrincipal> _administratorsGroup;

private static void Add(UserPrincipal user)
{
    group = _administratorsGroup.Result;
    lock(group)
    {
        group.Members.Add(user); 

        Console.WriteLine("saving group");
        group.Save();
    }
}

If this will be on a UI thread replace the _administratorsGroup.Result with await _administratorsGroup, it also has the same waiting behavior but will not lock up your UI.

0
tar On

FindAll() is way faster for ContextType.Machine. Then just filter the result by your available input: Name, UserPrincipalName, SAMAccountName or Sid.Value.

Linq variant, untested for ContextType.Domain:

PrincipalContext principalContext = new PrincipalContext(ContextType.Machine);

// user (input: userSID)
PrincipalSearcher userPrincipalSearcher = new PrincipalSearcher(new UserPrincipal(principalContext));
UserPrincipal userPrincipal = userPrincipalSearcher.FindAll().FirstOrDefault(x => (x is UserPrincipal) && x.Sid.Value == userSID) as UserPrincipal;

// group (input: groupSID)
PrincipalSearcher groupPrincipalSearcher = new PrincipalSearcher(new GroupPrincipal(principalContext));
GroupPrincipal groupPrincipal = groupPrincipalSearcher.FindAll().FirstOrDefault(x => (x is GroupPrincipal) && x.Sid.Value == groupSID) as GroupPrincipal;