GroupPrincipal.Members.Remove() doesn't work with a large AD group
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 testin开发者_运维问答g 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 );
}
}
}
}
}
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 } );
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
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;
}
精彩评论