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
Related
I have an ActiveDirectory Group called "AuditUsers" and it has some members.
This is my code:
` DirectoryEntry de = new DirectoryEntry(ADSPath);
DirectorySearcher deSearch = new DirectorySearcher(LDAP://CN=AuditUsers,OU=WEB Groups,OU=Groups,DC=doamin,DC=com);
deSearch.Filter = "(&(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))";
object members = de.Invoke("Members", null);`
So this object member doesn't pull all the users who are actually their members. Only some members are showing up. Does anyone have any idea why some people are missing here even if they are part of this group? IS any key field is missing in AD for those who are missed?
I checked the missing member's AD and found their objectCategory has person, objectClass has user. userAccountControl is same for those members who are pulled and not pulled.
Thanks
Invoking IADsGroup.Members is just the harder way of enumerating de.Properties["member"]. Both just read the member attribute of the group.
But that is not the only way a user can be a member. I go into it in more detail in an article I wrote, but what I suspect is going on here is that the group is used as the primary group for many of your users. And that membership is done differently.
If that is the case, then the primaryGroupId attribute of the users will be set to the Relative Identifier (RID) of the group. The RID is the last set of numbers in the SID (which can also be retrieved from the primaryGroupToken attribute of the group). In the article I wrote about finding all the members of a group, I included an example of how to find all the users that have the group as their primary group. On my site it was returning the DOMAIN\username, but I modified it here so it returns the distinguishedName, so you can add what you get from this to what you get from the member attribute.
public static IEnumerable<string> GetPrimaryGroupMemberList(DirectoryEntry group) {
group.RefreshCache(new[] { "distinguishedName", "primaryGroupToken" });
var groupDn = (string) group.Properties["distinguishedName"].Value;
var ds = new DirectorySearcher(
new DirectoryEntry($"LDAP://{groupDn.Substring(groupDn.IndexOf("DC=", StringComparison.Ordinal))}"),
$"(&(objectClass=user)(primaryGroupId={group.Properties["primaryGroupToken"].Value}))",
new [] { "distinguishedName" })
{
PageSize = 1000
};
using (var primaryMembers = ds.FindAll()) {
foreach (SearchResult primaryMember in primaryMembers) {
yield return (string) primaryMember.Properties["distinguishedName"][0];
}
}
}
I was using following code to get all the users from a specific Active Directory group in a specific domain. This code is working fine.
using (var context = new PrincipalContext(ContextType.Domain, "dept.mycomp.net"))
{
using (var group = GroupPrincipal.FindByIdentity(context, "IT Team"))
{
if (group != null)
{
var users = group.GetMembers(true);
foreach (UserPrincipal user in users)
{
Console.WriteLine("Name: " + user.DisplayName);
Console.WriteLine("Network Id: " + user.SamAccountName);
}
}
}
}
I saw there is Entire Directory option in Active Directory Lookup window. So I searched a bit and found following code; this code will get me all the users from Entire Directory root level. This code is also working fine in my case:
var currentForest = Forest.GetCurrentForest();
var gc = currentForest.FindGlobalCatalog();
using (var userSearcher = gc.GetDirectorySearcher())
{
userSearcher.Filter = "(&((&(objectCategory=Person)(objectClass=User)))(samaccountname=" + username + "))";
SearchResult result = userSearcher.FindOne();
}
Question: how would I modify the later code to fetch all the users for a specific group at root level? I would be passing just the GroupName Instead of Domain & Group Name.
The Entire Directory option searches the Global Catalog, rather than just the domain, as it looks like you've found out. The only difference in the search is which port it connects to. Port 389 is the LDAP port, which searches only the domain of the server you're connecting to. Port 3268 is the Global Catalog. A short form for this is using GC:// instead of LDAP://.
If you're working only in a single environment where you know the domain, you can just hard code it. It'll save the network requests of GetCurrentForest() and FindGlobalCatalog().
This is what I mean:
var searcher = new DirectorySearcher(new DirectoryEntry("GC://dept.mycomp.net"));
On to your other question of searching for a specific group: Keep in mind that the Global Catalog searches your AD forest, which can be more than one domain. The names of any object are only enforced unique within the domain, not the forest. So if you search the GC for the name of a group, you can potentially get duplicates. (there could be an "IT Team" group on all of your domains)
But anyway, if we assume you only have one group by that name in your whole forest, this is how you would search for it and get the members:
var groupname = "IT Team";
var members = new List<string>();
using (var searcher = new DirectorySearcher(new DirectoryEntry("GC://dept.mycomp.net"))) {
searcher.Filter = "(&(objectCategory=group)(objectClass=group)(cn=" + groupname + "))";
searher.PropertiesToLoad.Add("member"); //only get the member attribute
using (SearchResult result = searcher.FindOne()) {
foreach (var member in result.Properties["member"]) {
members.Add(member);
}
}
}
When that completes, members will be a list of the distinguishedName of each member. If you want a different attribute (like displayName) then you will need to create a DirectoryEntry for each member and get that attribute.
There are a couple caveats that may or may not be relevant:
There could be groups inside that group. This does not look for members of those groups.
If your domain has a trust with another domain outside your forest, then members from that external domain show up differently. I talked about that in a post I made on my site called What makes a member a member?
This may or may not limit how many members you see to a max of 1000 (if the group has more than 1000 members). I can't remember off hand if that happens with this method. I know it does when reading the 'member' attribute from a DirectoryEntry. If your group isn't that big, then it's not an issue.
I'm messing up with getting a full and usable list of all recursive groups to populate up a dropdownlist control to select a group to administer right within a ASP.net page.
The used Code:
List<string> groups = new List<string>();
using (DirectorySearcher searcher = new DirectorySearcher(dEntry))
{
searcher.Filter = "(objectCategory=group)";
foreach (SearchResult result in searcher.FindAll())
{
string group = result.Path.Substring(result.Path.IndexOf("/CN=")+4);
string formatedResult = group.Substring(0, group.IndexOf(",CN="));
groups.Results.Add(formatedResult);
}
}
The result list is long, containing a bunch of system-groups, which are not needed.
Only specific system-groups should be included in the groups list, like "Administrators" and all "non-system-defined" or "system-related" groups. (Like: Departments, Applicationgroups, etc. defined in the AD-Structure).
Any hint how to do that with DirectoryEntry?
If the 0x1 flag is present in groupType attribute, the group is created by the system.
Can modify the filter to search for:
system groups:
(&(objectCategory=group)(groupType:1.2.840.113556.1.4.803:=1))
non-system groups:
(&(objectCategory=group)(!(groupType:1.2.840.113556.1.4.803:=1)))
I've used the example in the link below with success. I've used it to search usernames in our AD, but it can ofcourse be modified.
AccessingtheActiveDirectoryfromMicrosoftNET
We got a special multivalue attribute. Let's call it ourOwnManagedBy which can contain users or groups (their DN) that manages the current group.
How can I retrieve a list of all groups that a specific user manages (with the help of managedBy and ourOwnManagedBy)?
For instance. Let's say that the user is member of the group GlobalAdministrators and that the group ApplicationAdministrators has GlobalAdministrations as a member. And finally the group MyApplication which has ApplicationAdministrators in the ourOwnManagedBy attribute.
User is member of GlobalAdministrators
GlobalAdministrators is member of ApplicationAdministrators
MyApplication got ApplicationAdministrators in ourOwnManagedBy
How do I use that information to find all groups that a specific user manages? Is it possible to do some kind of recursive check in custom attributes (that contains DNs of users and groups)?
Update
I've tried to use a directory search filter like this:
string.Format("(ourOwnManagedBy:1.2.840.113556.1.4.1941:={0})", dn);
but I might have missunderstood what 1.2.840.113556.1.4.1941 does? (MSDN page)
This is, I'm afraid, not possible to accomplish with only one LDAP query. You will have to split it into subqueries and run the each separately, which in turn will choke the domain controller if there's a lot to iterate over.
I tried to do it the way I described, and the performance was horrible, at least doing it using the available modules for .NET.
The following page says 3.1.1.3.4.4 LDAP Matching Rules (extensibleMatch) says that the LDAP_MATCHING_RULE_TRANSITIVE_EVAL that you are using does work in Windows 2008 and higher editions. If you are using 2003, it may not work.
No recursion, no idea on how it will do performance wise, may have bugs.
string user = "username";
//get domain
DirectoryEntry de = System.DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain().GetDirectoryEntry();
//get users dn first
string userDN;
using (var searcher = new DirectorySearcher(de))
{
searcher.Filter = String.Format("(&(objectCategory=person)(objectClass=user)(sAMAccountName={0}))", user);
searcher.PropertiesToLoad.Add("distinguishedName");
userDN = searcher.FindOne().Properties["distinguishedName"][0].ToString();
}
//get list of all users groups
List<string> groups;
//see http://stackoverflow.com/questions/6252819/find-recursive-group-membership-active-directory-using-c-sharp
using (var searcher2 = new DirectorySearcher(de))
{
searcher2.Filter = String.Format("(member:1.2.840.113556.1.4.1941:={0})", userDN);
searcher2.SearchScope = SearchScope.Subtree;
searcher2.PropertiesToLoad.Add("distinguishedName");
SearchResultCollection src = searcher2.FindAll();
groups = (from SearchResult c in src
select c.Properties["distinguishedName"][0].ToString()).ToList();
}
//build giant search query
SearchResultCollection srcGroups;
using (var searcher = new DirectorySearcher(de))
{
string baseString = "(|{0})";
string managedbybase = "(managedBy={0})";
//I've read that you can search multivalued lists using a standard ='s.
string ourOwnManagedByBase = "(ourOwnManagedBy={0})";
StringBuilder sb = new StringBuilder();
//add user DN to list of group dn's
groups.Add(userDN);
foreach (string g in groups)
{
sb.AppendFormat(managedbybase, g);
sb.AppendFormat(ourOwnManagedByBase, g);
}
searcher.Filter = string.Format(baseString, sb.ToString());
srcGroups = searcher.FindAll();
}
I'll be honest and say that this doesn't actually work for me :) But I think it's because of the way our domain is configured. If nothing else maybe it will push you in the right direciton.
I have a series of applications that all use the same C#, .Net 2.0 code that I've created to check and see if a user is a member of an Active Directory group.
I haven't had any trouble with my code until recently, when I added a user from another, trusted AD domain to one of my AD groups. My question is how can I check to see if a user is a member of an Active Directory group, regardless of their domain. In other words, they may or may not be in the same domain as my group. Below is the code that I have written and used for years to search to see if the user is in an Active Directory group. I'm not sure where I adapted this code from but I'd assume it came from an MSDN article. Also, the solution must be for the .Net 2.0 framework. I have found quite a few answers that may work for this problem in .Net 3.5. Unfortunately, that won't work for my scenario.
//This method takes a user name and the name of an AD Group (role).
//Current implementations of this method do not contain the user's domain
//with userName, because it comes from the Environment.UserName property.
private static bool IsInRole(string userName, string role)
{
try
{
role = role.ToLowerInvariant();
DirectorySearcher ds = new DirectorySearcher(new DirectoryEntry(null));
ds.Filter = "samaccountname=" + userName;
SearchResult sr = ds.FindOne();
DirectoryEntry de = sr.GetDirectoryEntry();
PropertyValueCollection dir = de.Properties["memberOf"];
for (int i = 0; i < dir.Count; ++i)
{
string s = dir[i].ToString().Substring(3);
s = s.Substring(0, s.IndexOf(',')).ToLowerInvariant();
if (s == role)
return true;
}
throw new Exception();
}
catch
{
return false;
}
}
This is not the answer you are waiting for, but I hope it can help.
First ; You suppose you code is working in a domain, but I don't see where it takes care of the user 'principal group'. If you select a group as the 'user principal group', this group is no longer part of the member attribute.
Second ; In my understanding, a way (I hope not the only one, but I'am still looking for) to see, if a user, is present in a group is to 'recusively' look for the user DN in the 'member' attribute of 'group' objects. So, in your case, you may ask your domain and the other domain. You can do that doing ONE search per domain. Here is a sample of such a 'recursive one shoot search' using control :
/* Connection to Active Directory
*/
string sFromWhere = "LDAP://WIN-COMPUTER:389/";
DirectoryEntry deBase = new DirectoryEntry(sFromWhere, "dom\\user", "password");
/* To find all the groups that "user1" is a member of :
* Set the base to the groups container DN; for example root DN (dc=dom,dc=fr)
* Set the scope to subtree
* Use the following filter :
* (member:1.2.840.113556.1.4.1941:=cn=user1,cn=users,DC=x)
*/
DirectorySearcher dsLookFor = new DirectorySearcher(deBase);
dsLookFor.Filter = "(member:1.2.840.113556.1.4.1941:=CN=user1 Users,OU=MonOu,DC=dom,DC=fr)";
dsLookFor.SearchScope = SearchScope.Subtree;
dsLookFor.PropertiesToLoad.Add("cn");
SearchResultCollection srcGroups = dsLookFor.FindAll();
Remark : you can use a more accurate filter to exclude distribution groups for example.
Edited (to answer comments questions) :
First : Are the credentials needed ? I would say no if the request is done from a computer that belongs to the domain or the approved domain.
Second and third : Yes filters are documented by Microsoft in AD Search Filter Syntax. The way I wrote this filter is a deduction from the samples.