I created a WCF web service to return user and group information from Active Directory. It works for most groups and users.
I use directoryEntry.Invoke("groups",null) to return the groups a specified user is member of. This returns MOST groups. The odd thing is I can find any group and enumerate its members, even if it is one of the groups missing when I use the invoke query on one of its members.
Most of the groups that exhibit this behavior are Exchange-enabled. Most of the problematic user accounts are for users in a federated domain, who use an Exchange server in the domain that I query. I am not trying to query objects in the federated domain.
My theories so far:
some security restriction does not allow enumerating all groups via invoke() even though I can query missing groups and enumerate their members.
invoke has issues with some subset of groups. Perhaps universal, dynamic, or Exchange-enabled properties are at play
the invoke method does not pick up all groups because the "federated" accounts (created as part of their Exchange account setup) are somehow different than regular domain accounts beyond the sid mapping back to their login domain.
There are two known issues with using the "Groups" property on a DirectoryEntry:
- it will not show you the "Default group" a user is in (typically "Users")
- it will not show you nested group memberships
So if a user is member of a group A, and that group then in turn is member of Group B, then in Windows, this means that the user is also member of Group B. However, the DirectoryEntry will not show you that nested group membership.
Those are the two only restrictions I know of for straight Active Directory (without Exchange).
Getting the default group is a bit involved, but I do have a code sample for that.
private string GetPrimaryGroup(DirectoryEntry aEntry, DirectoryEntry aDomainEntry)
{
int primaryGroupID = (int)aEntry.Properties["primaryGroupID"].Value;
byte[] objectSid = (byte[])aEntry.Properties["objectSid"].Value;
StringBuilder escapedGroupSid = new StringBuilder();
// Copy over everything but the last four bytes(sub-authority)
// Doing so gives us the RID of the domain
for(uint i = 0; i < objectSid.Length - 4; i++)
{
escapedGroupSid.AppendFormat("\\{0:x2}", objectSid[i]);
}
//Add the primaryGroupID to the escape string to build the SID of the primaryGroup
for(uint i = 0; i < 4; i++)
{
escapedGroupSid.AppendFormat("\\{0:x2}", (primaryGroupID & 0xFF));
primaryGroupID >>= 8;
}
//Search the directory for a group with this SID
DirectorySearcher searcher = new DirectorySearcher();
if(aDomainEntry != null)
{
searcher.SearchRoot = aDomainEntry;
}
searcher.Filter = "(&(objectCategory=Group)(objectSID=" + escapedGroupSid.ToString() + "))";
searcher.PropertiesToLoad.Add("distinguishedName");
return searcher.FindOne().Properties["distinguishedName"][0].ToString();
}
Getting the nested groups also takes a few steps and I'll have to hunt for a solution to that one, if that's the problem.
Marc
PS: as a side note - why on earth are you doing a "DirectoryEntry.Invoke("groups", null)" call? Why don't you just enumerate the DirectoryEntry.Properties["memberOf"] property which is multi-valued (contains multiple values) and has the group's DN (distinguished name) in it?
foreach(string groupDN in myUser.Properties["memberOf"])
{
string groupName = groupDN;
}
OR if you're on .NET 3.5, you can make use of the new Security Principal classes in S.DS.AccountManagement. One of them is a "UserPrincipal", which has a method called "GetAuthorizationGroups()" which does all this hard work for you - for free, basically!
See an excellent MSDN article that describes these new .NET 3.5 S.DS features for you.
I think marc_s is correct. If you want all groups, you can use the following snippet:
using (DirectoryEntry obj = new DirectoryEntry("LDAP://" + dn))
{
obj.RefreshCache(new string[] { "tokenGroups" });
string[] sids = new string[obj.Properties["tokenGroups"].Count];
int i = 0;
foreach (byte[] bytes in obj.Properties["tokenGroups"])
{
sids[i] = _ConvertSidToString(bytes);
++i;
}
obj.Close();
return sids;
}
Note that calculating nested groups is an expensive operation, so RefreshCache might take a long time to complete.
On Freund,
I am trying to make use of your code and not getting very far. I have updated the directory entry path to be "LDAP://DC=myDomain,DC=co,DC=uk" but I am not getting any results (obj.Properties["tokenGroups"].Count = 0)
I don't udnerstand how the user to list group for is specified.
Could you please point me in the right direction?
Thanks
EDIT:
I got it sorted in the end. The directory entry to get the token groups from should be a user entry... if that makes sense...
I've included some code in case anyone else has the same query:
Dim directoryEntry As DirectoryEntry = _
New DirectoryEntry("LDAP://CN=users,DC=domanName,DC=com")
Dim directorySearcher As DirectorySearcher = _
New DirectorySearcher(directoryEntry, "(sAMAccountName=" & UserName & ")")
Dim searchResult As SearchResult = directorySearcher.FindOne()
If Not searchResult Is Nothing Then
Dim userDirectoryEntry As DirectoryEntry = searchResult.GetDirectoryEntry
userDirectoryEntry.RefreshCache(New String() {"tokenGroups"})
... etc ...
End If
来源:https://stackoverflow.com/questions/1264028/directoryentry-invokegroups-null-not-retrieving-all-groups