5

I'm using the System.DirectoryServices.AccountManagement namespace classes to manage the membership of several groups. These groups control the population of our print accounting system and some of them are very large. I'm running into a problem removing any user from one of these large groups. I have a test program that illustrates the problem. Note that the group I'm testing is not nested, but user.IsMemberOf() also seems to have the same problem, whereas GetAuthorizationGroups() correctly shows the groups a user is a member of. The group in question has about 81K members, which is more than it should have since Remove() isn't working, and will normally be about 65K or so.

I'd be interested to hear from other people who have had this problem and have resolved it. I've got an open case with Microsoft, but the turn around on the call is slow since the call center is about 17 hours time difference so they don't arrive for work until about an hour before I usually leave for home.

using (var context = new PrincipalContext( ContextType.Domain ))
{
    using (var group = GroupPrincipal.FindByIdentity( context, groupName ))
    {
        using (var user = UserPrincipal.FindByIdentity( context, userName ))
        {
            if (user != null)
            {
                var isMember = user.GetAuthorizationGroups()
                                   .Any( g => g.DistinguishedName == group.DistinguishedName );
                Console.WriteLine( "1: check for membership returns: {0}", isMember );
                if (group.Members.Remove( user ))
                {
                    Console.WriteLine( "user removed successfully" );
                    group.Save();
                }
                else
                {
                    // do save in case Remove() is lying to me
                    group.Save();
                    Console.WriteLine( "user remove failed" );
                    var isStillMember = user.GetAuthorizationGroups()
                                            .Any( g => g.DistinguishedName == group.DistinguishedName );
                    Console.WriteLine( "2: check for membership returns: {0}", isStillMember );

                }
            }
        }
    }
}
Per Noalt
  • 5,052
  • 2
  • 29
  • 20
tvanfosson
  • 524,688
  • 99
  • 697
  • 795
  • If I were you I would take a look at the System.DirectoryServices.Protocols namespace when dealing with group that are as large as yours. The code is more difficult to write but you'll be using the native LDAP interface which can be a lot more efficient in your case. MSDN has a great guide @ http://msdn.microsoft.com/en-us/library/bb332056.aspx. – Per Noalt Dec 11 '09 at 08:57

3 Answers3

5

Turns out this is a bug in the GroupPrincipal.Members.Remove() code in which remove fails for a group with more than 1500 members. This has been fixed in .NET 4.0 Beta 2. I don't know if they have plans to back port the fix into 2.0/3.x.

The work around is to get the underlying DirectoryEntry, then use Invoke to execute the Remove command on the IADsGroup object.

 var entry = group.GetUnderlyingObject() as DirectoryEntry;
 var userEntry = user.GetUnderlyingObject() as DirectoryEntry;
 entry.Invoke( "Remove", new object[] { userEntry.Path } );
Gabriel Luci
  • 38,328
  • 4
  • 55
  • 84
tvanfosson
  • 524,688
  • 99
  • 697
  • 795
  • Hi tvanfosson, could you please show me the URL of the case you logged with Microsoft? We are hitting an issue that looks like yours, and would like to check if any hotfix available for .net3.5 before we try out the workaround. I've been Bing and Google the web but didn't find any related follow-up in MS for this issue. – Ricky Jan 18 '11 at 10:33
  • The case was actually submitted by our sysadmin team. I don't have the URL. I was told at the time that they were not going to back port the fix to 3.5, though. Yes, I did ask them to. – tvanfosson Jan 18 '11 at 12:55
  • Small bug in the example, the last line should be: `entry.Invoke( "Remove", new object[] { userEntry.Path } );` – Henrik Høyer Aug 02 '18 at 14:54
1

This post helped point me in the right direction, just wanted to add some addition info.

It also works binding directly to the group, and you can use it for adding group members.

using (var groupEntry = new DirectoryEntry(groupLdapPath))
{
    groupEntry.Invoke("remove", new object[] { memberLdapPath });
    groupEntry.Invoke("add",    new object[] { memberLdapPath });
}

Also be aware, with the standard 'member' attribute, you use the user or group distinguishedName, but invoke requires the path with LDAP:// prefix, otherwise it throws a vague InnerException:

Exception from HRESULT: 0x80005000
WhoIsRich
  • 4,053
  • 1
  • 33
  • 19
0
public bool RemoveUserFromGroup(string UserName, string GroupName)
{
   bool lResult = false;
   if (String.IsNullOrEmpty(UserName) || String.IsNullOrEmpty(GroupName)) return lResult;
   try
   {
      using (DirectoryEntry dirEntry = GetDirectoryEntry())
      {
         using (DirectoryEntry dirUser = GetUser(UserName))
         {
            if (dirEntry == null || dirUser == null)
            {
               return lResult;
            }
            using (DirectorySearcher deSearch = new DirectorySearcher())
            {
               deSearch.SearchRoot = dirEntry;
               deSearch.Filter = String.Format("(&(objectClass=group) (cn={0}))", GroupName);
               deSearch.PageSize = 1000;
               SearchResultCollection result = deSearch.FindAll();
               bool isAlreadyRemoved = false;
               String sDN = dirUser.Path.Replace("LDAP://", String.Empty);
               if (result != null && result.Count > 0)
               {
                  for (int i = 0; i < result.Count; i++)
                  {
                     using (DirectoryEntry dirGroup = result[i].GetDirectoryEntry())
                     {
                        String sGrDN = dirGroup.Path.Replace("LDAP://", String.Empty);
                        if (dirUser.Properties[Constants.Properties.PROP_MEMBER_OF].Contains(sGrDN))
                        {
                           dirGroup.Properties[Constants.Properties.PROP_MEMBER].Remove(sDN);
                           dirGroup.CommitChanges();
                           dirGroup.Close();
                           lResult = true;
                           isAlreadyRemoved = true;
                           break;
                        }
                     }
                     if (isAlreadyRemoved)
                        break;
                  }
               }
            }
         }
      }
   }
   catch
   {
      lResult= false;
   }
   return lResult;
}
  • 1
    Simply providing a code is not a very good answer. Please try to explain what it does and why. – Yurii Feb 06 '14 at 18:33