Search code examples
c#.netactive-directoryuser-accounts

Delay in Active Directory user creation when using System.DirectoryServices.AccountManagement


I am creating accounts and setting properties on them using System.DirectoryServices.AccountManagement in .NET 4.5. One of the requirements is that the group membership (including Primary Group) be copied from a template account. The code includes the following:

foreach (var group in userPrincipal.GetGroups()) {
    var groupPrincipal = (GroupPrincipal) @group;

    if (groupPrincipal.Sid != templatePrimaryGroup.Sid) {
        groupPrincipal.Members.Remove(userPrincipal);
        groupPrincipal.Save();
    }
}

This works about 90% of the time. The rest of the time, it fails with:

System.DirectoryServices.DirectoryServicesCOMException was unhandled HResult=-2147016656 Message=There is no such object on the server.

Source=System.DirectoryServices ErrorCode=-2147016656 ExtendedError=8333 ExtendedErrorMessage=0000208D: NameErr: DSID-03100213, problem 2001 (NO_OBJECT), data 0, best match of: 'OU=Whatever,DC=domain,DC=local`

on the GetGroups call. My guess is that there is a race condition of some sort with the user not being fully created before I next go to access it. I know from diagnostic logging that I am going against the same domain controller each time (it's using the same PrincipalContext so that matches my expectation) so it's not a replication issue.

Is my guess accurate? Is there a good way to handle this? I could just throw in a Sleep but that seems like a cop-out at best and fragile at worst. So what is the right thing to do?


Solution

  • Try something like:

            int maxWait = 120;
            int cnt = 0;
            bool usable = false;
    
            while (usable == false && cnt < maxWait)
            {
                try
                {
                    foreach (var group in userPrincipal.GetGroups())
                    {
                        var groupPrincipal = (GroupPrincipal)@group;
    
                        if (groupPrincipal.Sid != templatePrimaryGroup.Sid)
                        {
                            groupPrincipal.Members.Remove(userPrincipal);
                            groupPrincipal.Save();
                        }
                    }
                    usable = true;
                    break;
                }
                catch
                {
                    System.Threading.Thread.Sleep(500);
                }
            }
            if (usable)
                //All okay
                ;
            else
                //Do something
                ;
    

    This way you can try for "a while". If it works good, if not do something like log an error, so you can run a fix-it script later.