Related
I can search the user and find only the groups that the user belongs to. And now i want to return all the groups/roles and assign a user to a specific group/role.
DirectoryEntry and PrincipalContext doesn't work in my case and i have tried that for days.
This is my working code for searching user group/roles which is working fine.
How can i get all the groups/roles?
And Add user to a group/role
Container = “ou=containername,ou=xx,ou=xx,O=xxxxx”
Domain = “mydomain.com”
group = ou=groups,ou=containername,ou=xx,ou=xx,O=xxxx
List<string> roles = new List<string>();
SearchRequest request = new SearchRequest("", "(&(objectClass=person)(mail=myusername))", System.DirectoryServices.Protocols.SearchScope.Subtree);
SearchResponse response = (SearchResponse)con.SendRequest(request);
if (response.Entries.Count == 0)
{
return null;
}
else
{
foreach (SearchResultEntry entry in response.Entries)
{
if (entry.Attributes["member"] != null)
{
roles = (entry.Attributes["member"].GetValues(typeof(string))).ToArray().Select(r => r.ToString()
.Substring(r.ToString().IndexOf("cn=") + 3,
r.ToString().IndexOf(",") - 3))
.ToList();
}
}
}
return roles;
I don't think you're using Active Directory. What tipped me off is that you're getting data from the member attribute of a user. That's not how it works with Active Directory (it would be memberOf).
I'm not entirely sure what you're asking for. Your title mentioned "nested groups", which means when one group is a member of another group. So I assume that would mean that you want to find every group the user is a member of and all groups that those groups are members of, etc. If that's the case, you will really have to find out what type of server you're connecting to before anyone can give you a good answer on that.
But in your question you say "How can i get all the groups/roles?" So does that mean you just want to find every group that exists? To do that, you can just do a new search and use this as the filter:
(objectClass=group)
For adding a user to a group, I think it would be something like this (where userDn is the distinguishedName of the user you want to add, and groupDn is that of the group):
var mod = new DirectoryAttributeModification {
Name = "member",
Operation = DirectoryAttributeOperation.Add
}
mod.Add(userDn);
var response = (ModifyResponse) connectionObject.SendRequest(
new ModifyRequest {
DistinguishedName = groupDn,
Modifications = { mod }
}
);
But I've never actually used LdapConnection, so you might need to tweak it.
By default, the ADLDS or AD MemberOf (User object) Member (Group object) attribute is not retrieved.
Example Solution for User
SearchRequest request = new SearchRequest("", "(&(objectClass=user)(mail=myusername))", System.DirectoryServices.Protocols.SearchScope.Subtree);
request.Attributes.Add("memberOf");
or Group
SearchRequest request = new SearchRequest("", "(&(objectClass=group))", System.DirectoryServices.Protocols.SearchScope.Subtree);
request.Attributes.Add("member");
Default LDAP Filters and Attributes for Users, Groups and Containers
I need to show all my security groups and the users that are members of the security groups in a listView.
At the moment I can display all the security groups but not sure how to display the users in the security group.
Here is my code I am currently using:
private void Security_group_btn_Click(object sender, EventArgs e)
{
DirectorySearcher searcher = new DirectorySearcher(DomainName);
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, DomainName);
UserPrincipal userPrin = new UserPrincipal(ctx);
userPrin.Name = "*";
var search = new System.DirectoryServices.AccountManagement.PrincipalSearcher();
search.QueryFilter = userPrin;
var results = searcher.FindAll();
ListView lvwListView = this.security_listView;
lvwListView.Clear();
lvwListView.Columns.Add("Security Group", 175, HorizontalAlignment.Left);
lvwListView.Columns.Add("Users", 175, HorizontalAlignment.Left);
searcher.Filter = "(&(objectClass=group))";
searcher.SearchScope = SearchScope.Subtree;
searcher.PropertiesToLoad.Add("sAMAccountName");
SearchResultCollection result = searcher.FindAll();
foreach (SearchResult entry in result)
{
lvwListView.Items.Add(entry.GetDirectoryEntry().Properties["sAMAccountName"].Value.ToString());
}
result.Dispose();
searcher.Dispose();
}
}
}
So basically I would like to display something like this in my ListView:
Security Group Name
Users User1
User2
User3
Administrators User1
User2
User3
User4
Thanks
First, be careful of this:
lvwListView.Items.Add(entry.GetDirectoryEntry().Properties["sAMAccountName"].Value.ToString());
Particularly, using GetDirectoryEntry() just to get a value. When you get a value using DirectoryEntry.Properties, it checks to see if it already has the value you're asking for in the cache. If not, it asks AD for every attribute that has a value, which will usually be a whole lot more data than you actually need. Since you're looping over a bunch of accounts, that can significantly slow you down.
You are already asking for the sAMAccountName in the search results, since you used searcher.PropertiesToLoad.Add("sAMAccountName"), so you can pull the value from the SearchResult object directly:
lvwListView.Items.Add((string) entry.Properties["sAMAccountName"][0]);
I talk more about that in an article I wrote about better performance when programming with Active Directory.
Now to actually answer your question:
Getting the members is a different story. You can ask for the member attribute in the search, but you get problems when a group has more than 1500 members. You have to ask for the members in pages. But also, you have to decide how you are going to handle nested groups: when a group is a member of a group. Do you want to just list that group name? Or do you want to look inside that group and get those members too?
I wrote a whole article about this too: Find all the members of a group
But it's more than likely you can just use one of the sample methods I show in that article. This will take a DirectoryEntry object of a group, and give you a list of strings containing the DOMAIN\username of each object inside the group (you can change that if you want). You can use the recursive parameter to decide what you want to do with nested groups.
public static IEnumerable<string> GetGroupMemberList(DirectoryEntry group, bool recursive = false) {
var members = new List<string>();
group.RefreshCache(new[] { "member" });
while (true) {
var memberDns = group.Properties["member"];
foreach (string member in memberDns) {
using (var memberDe = new DirectoryEntry($"LDAP://{member.Replace("/", "\\/")}")) {
memberDe.RefreshCache(new[] { "objectClass", "msDS-PrincipalName", "cn" });
if (recursive && memberDe.Properties["objectClass"].Contains("group")) {
members.AddRange(GetGroupMemberList(memberDe, true));
} else {
var username = memberDe.Properties["msDS-PrincipalName"].Value.ToString();
if (!string.IsNullOrEmpty(username)) {
members.Add(username);
}
}
}
}
if (memberDns.Count == 0) break;
try {
group.RefreshCache(new[] {$"member;range={members.Count}-*"});
} catch (COMException e) {
if (e.ErrorCode == unchecked((int) 0x80072020)) { //no more results
break;
}
throw;
}
}
return members;
}
You would call it from your code like this:
var members = GetGroupMemberList(entry.GetDirectoryEntry());
Then you can display them however you want.
This will work fine if you are only working in a single AD forest. But if your domain trusts other domains outside your forest and you can expect to see users from those domains in the groups you are looking at, then you need to account for that. But I cover that in that article. I also cover primary groups there too, which is rare that you need to care about, but you might.
Update: To add two columns in a ListViewItem, you have to create it separately. You can use the constructor that takes a string array of the "subitems", as they call it.
You could put something like this inside your loop where you loop through the members, where groupName is the name of the group and member is the name of the group member.
lvwListView.Items.Add(new ListViewItem(new [] {
groupName,
member
});
If you only want the groupName on the first one, then you can just put an empty string ("") instead of groupName for the others.
I need to get all users and their groups from a specific category.
Examples of users :
user | memberof
user1 | CN=group_1,OU=Groupes,OU=CR 1,DC=zcam,DC=ztech
user1 | CN=group_2,OU=Groupes,OU=CR 1,DC=zcam,DC=ztech
user2 | CN=group_2,OU=Groupes,OU=CR 1,DC=zcam,DC=ztech
user3 | CN=group_3,OU=Groupes,OU=CR 2,DC=zcam,DC=ztech
I need to get every user where memberof contains OU=Groupes,OU=CR 1,DC=zcam,DC=ztech (user1 and user2 from my example)
Following this doc (https://learn.microsoft.com/fr-fr/windows/desktop/ADSI/search-filter-syntax) I tried the following syntaxes :
DirectoryEntry ldap = new DirectoryEntry("LDAP://xxx.xxx.xxx.xxx");
using (DirectorySearcher searcher = new DirectorySearcher(ldap))
{
// Works but return everything
searcher.Filter = "(&(objectClass=user)(memberof=*))";
// Works but only for one group
searcher.Filter = "(&(objectClass=user)(memberof=CN=group_1,OU=Groupes,OU=CR 1,DC=zcam,DC=ztechh))";
// Doesn't work because searcher.FindAll().Count returns 0
searcher.Filter = "(&(objectClass=user)(memberof=*,OU=Groupes,OU=CR 1,DC=zcam,DC=ztechh))";
// searcher.FindAll().Count returns 0
foreach (SearchResult result in searcher.FindAll())
{
[...]
}
Following this (https://community.servicenow.com/community?id=community_question&sys_id=00d29fa1db101fc01dcaf3231f96197f) I tried to change the wildcard * by a % but it didn't changed the result.
I need to get every user where memberof contains OU=Groupes,OU=CR 1,DC=zcam,DC=ztech (user1 and user2 from my example)
If I understand correctly, I think that sentence sums up what you're trying to do. You want to find all users who are a member of any group in that OU.
Active Directory doesn't let you use wildcards on any attribute that takes a distinguishedName. That includes member and memberOf. So the only way to do this is in two steps:
Find the distinguishedName of all groups in that OU.
Search for all users whose memberOf includes one of the values found in step 1.
Something like this (I haven't tested this against AD, so you might need to tweak it):
var groupSearch = new DirectorySearcher(
new DirectoryEntry("LDAP://OU=Groupes,OU=CR 1,DC=zcam,DC=ztech"), //notice the OU
"(objectClass=group)");
//if you don't do this, it will return *every* attribute, which is slower
groupSearch.PropertiesToLoad.Add("distinguishedName");
//build a user query with all the groups
var userFilter = new StringBuilder("(&(objectClass=user)(|");
using (var results = groupSearch.FindAll()) {
foreach (SearchResult result in results) {
userFilter.Append($"(memberOf={result.Properties["distinguishedName"][0]})");
}
}
userFilter.Append(")");
var userSearch = new DirectorySearcher(
new DirectoryEntry("LDAP://DC=zcam,DC=ztech"),
userFilter.ToString());
//userSearch.PropertiesToLoad.Add(""); //add only the attributes you need to make it quicker
using (var results = userSearch.FindAll()) {
foreach (SearchResult result in results) {
//do something
}
}
Note that this will only find direct members of those groups. It won't return users that are in nested groups (when a user is in a group that is a member of one of these groups). If you want that, you can adjust the filter to include a special flag that tells AD to search recursively:
userFilter.Append($"(memberOf:1.2.840.113556.1.4.1941:={result.Properties["distinguishedName"][0]})");
Depending on your domain, you might want to be aware of two things:
This will not return users who have one of these groups as their primary group, since that relationship is not stored using member/memberOf.
If these groups have members from external trusted domains, then you'll end up finding Foreign Security Principal objects, instead of their actual user objects. That's a whole other thing to deal with, if it's an issue for you.
I've written a few articles on the subject, if you're curious. Start with this one: Active Directory: What makes a member a member?
edited version; extend for all your group_X
(
&(objectClass=user)
(|(memberof=CN=group_1,OU=Groupes,OU=CR 1,DC=zcam,DC=ztechh)
(memberof=CN=group_2,OU=Groupes,OU=CR 1,DC=zcam,DC=ztechh))
)
Finally I have found another way to do.
In fact, this property OU=CR 1 in the memberof correspond to the division in my AD.
So I just filter like this :
DirectoryEntry ldap = new DirectoryEntry("LDAP://xxx.xxx.xxx.xxx");
using (DirectorySearcher searcher = new DirectorySearcher(ldap))
{
searcher.Filter = "(&(objectClass=user)(division=CR 1))";
foreach (SearchResult result in searcher.FindAll())
{
[...]
}
Thanks everyone for your help.
I have a user query that can't be done at an OU level. I am trying to only return users where they are a member of a group (I need to filter by the group string value, not the actual group object). Here is what it currently looks like:
using (var entry = new DirectoryEntry("LDAP://foo.net/DC=appName,DC=domainName,DC=com", Username, Password)) {
using (var searcher = new DirectorySearcher()) {
searcher.Filter = "(&(objectClass=user))";
searcher.SearchRoot = entry;
searcher.PropertiesToLoad.Add("memberOf");
foreach (SearchResult sr in searcher.FindAll()) {
var member = GetSearchResultProperty(sr, "memberOf");
}
}
}
This query goes across multiple OU's where there are different user and group containers.
Is there a way I can filter on the memberOf property when I execute my query (without specifying any sort of OU)?
Just add another term to the filter:
searcher.Filter = "(&(objectClass=user)(memberOf=myGroup))";
It looks like what I am trying to do isn't possible at the time of query execution because the memberOf property needs to be a full path to a group. In my case, I don't actually care about the group object, but, rather the group name (each OU will have different groups, but, could have the same group name). I had to take a different approach to solve this which could have performance implications later on. Just in case someone else finds this question looking for an answer, here is how I am solving it as of now:
using (var entry = new DirectoryEntry("LDAP://foo.net/DC=appName,DC=domainName,DC=com", Username, Password)) {
using (var searcher = new DirectorySearcher()) {
searcher.Filter = "(&(objectClass=user))";
searcher.SearchRoot = entry;
searcher.PageSize = 5000;
searcher.PropertiesToLoad.Add(DirectoryConstants.AD_PROPERTY_MEMBER_OF);
searcher.PropertiesToLoad.Add(DirectoryConstants.AD_PROPERTY_DISPLAY_NAME);
foreach (SearchResult sr in searcher.FindAll()) {
//The memberOf property will be a string array if the user is in multiple groups.
string[] memberOf = GetSearchResultProperties(sr, DirectoryConstants.AD_PROPERTY_MEMBER_OF);
//Check if the user is in the group by analyzing the string.
var group = memberOf.FirstOrDefault(m => m.StartsWith(string.Format("CN={0},", groupName)));
if (group != null) {
//The user is in the group!
}
}
}
}
You need to include the full distinguished name of the group in your search filter, as the memberOf property will contain that value.
searcher.Filter = "(&(objectClass=user)(memberOf=CN=myGroup,CN=groups,OU=theOU,DC=appName,DC=domainName,DC=com))";
EDIT :
My apologies, as I misread the question. Without including an OU, the only other way of doing this is taking the opposite approach, and locating the group object first :
searcher.Filter = "(&(objectClass=group)(name=Your Group Name)"
Then iterate through the DirectoryEntry using it's properties :
foreach(object dn in group.Properties["member"] )
{
string DistinguishedName = dn.ToString();
}
If you're retrieving more than 1,000+ users though, you'll need to take a more segmented approach, which is detailed in this article.
I am looking to get a list of all of the groups that a user is a member of in Active Directory, both explicitly listed in the memberOf property list as well as implicitly through nested group membership. For example, if I examine UserA and UserA is a part of GroupA and GroupB, I also want to list GroupC if GroupB is a member of GroupC.
To give you a bit more insight into my application, I will be doing this on a limited basis. Basically, I want a security check occasionally that will list these additional memberships. I will want to differentiate the two but that shouldn't be hard.
My problem is that I have not found an efficient way to make this query work. The standard text on Active Directory (This CodeProject Article) shows a way to do this that is basically a recursive lookup. That seems terribly inefficient. Even in my small domain, a user might have 30+ group memberships. That means 30+ calls to Active Directory for one user.
I've looked into the following LDAP code to get all of the memberOf entries at once:
(memberOf:1.2.840.113556.1.4.1941:={0})
where {0} would be my LDAP path (ex: CN=UserA,OU=Users,DC=foo,DC=org). However, it does not return any records. The downside of this method, even if it worked, would be that I wouldn't know which group was explicit and which was implicit.
That is what I have so far. I would like to know if there is a better way than the CodeProject article and, if so, how that could be accomplished (actual code would be wonderful). I am using .NET 4.0 and C#. My Active Directory is at a Windows 2008 functional level (it isn't R2 yet).
Thirst thanks for this an interesting question.
Next, just a correction, you say :
I've looked into the following LDAP code to get all of the memberOf entries at once:
(memberOf:1.2.840.113556.1.4.1941:={0})
You don't make it work. I remember I make it work when I learnt about its existence, but it was in an LDIFDE.EXE filter. So I apply it to ADSI in C# and it's still working. There were too much parenthesis in the sample I took from Microsoft, but it was working (source in AD Search Filter Syntax).
According to your remark concerning the fact that we don't know if a user explicitly belongs to the group I add one more request. I know this is not very good, but it's the best I'am abable to do.
static void Main(string[] args)
{
/* Connection to Active Directory
*/
DirectoryEntry deBase = new DirectoryEntry("LDAP://WM2008R2ENT:389/dc=dom,dc=fr");
/* 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();
/* Just to know if user is explicitly in group
*/
foreach (SearchResult srcGroup in srcGroups)
{
Console.WriteLine("{0}", srcGroup.Path);
foreach (string property in srcGroup.Properties.PropertyNames)
{
Console.WriteLine("\t{0} : {1} ", property, srcGroup.Properties[property][0]);
}
DirectoryEntry aGroup = new DirectoryEntry(srcGroup.Path);
DirectorySearcher dsLookForAMermber = new DirectorySearcher(aGroup);
dsLookForAMermber.Filter = "(member=CN=user1 Users,OU=MonOu,DC=dom,DC=fr)";
dsLookForAMermber.SearchScope = SearchScope.Base;
dsLookForAMermber.PropertiesToLoad.Add("cn");
SearchResultCollection memberInGroup = dsLookForAMermber.FindAll();
Console.WriteLine("Find the user {0}", memberInGroup.Count);
}
Console.ReadLine();
}
In my test tree this give :
LDAP://WM2008R2ENT:389/CN=MonGrpSec,OU=MonOu,DC=dom,DC=fr
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpSec,OU=MonOu,DC=dom,DC=fr
cn : MonGrpSec
Find the user 1
LDAP://WM2008R2ENT:389/CN=MonGrpDis,OU=ForUser1,DC=dom,DC=fr
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpDis,OU=ForUser1,DC=dom,DC=fr
cn : MonGrpDis
Find the user 1
LDAP://WM2008R2ENT:389/CN=MonGrpPlusSec,OU=ForUser1,DC=dom,DC=fr
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpPlusSec,OU=ForUser1,DC=dom,DC=fr
cn : MonGrpPlusSec
Find the user 0
LDAP://WM2008R2ENT:389/CN=MonGrpPlusSecUniv,OU=ForUser1,DC=dom,DC=fr
adspath : LDAP://WM2008R2ENT:389/CN=MonGrpPlusSecUniv,OU=ForUser1,DC=dom,DC=fr
cn : MonGrpPlusSecUniv
Find the user 0
(edited)
'1.2.840.113556.1.4.1941' is not working in W2K3 SP1, it begins to work with SP2. I presume it's the same with W2K3 R2. It's supposed to work on W2K8. I test here with W2K8R2. I'll soon be able to test this on W2K8.
If there is no way other than recursive calls (and I don't believe there is) then at least you can let the framework do the work for you: see the UserPrincipal.GetAuthorizationGroups method (in the System.DirectoryServices.AccountManagement namespace and introduced in .Net 3.5)
This method searches all groups
recursively and returns the groups in
which the user is a member. The
returned set may also include
additional groups that system would
consider the user a member of for
authorization purposes.
Compare with the results of GetGroups ("Returns a collection of group objects that specify the groups of which the current principal is a member") to see whether the membership is explicit or implicit.
Use the ldap filter recursively but query for all groups returned after each query to reduce the number of round trips.
Ex:
Get all groups where user is a member
Get all groups where Step 1 Groups are members
Get all groups where Step 2 Groups are members
...
In my experience there are rarely more then 5 but should definitiely be much less then 30.
Also:
Make sure to only pull the properties
you are going to need back.
Caching results can significantly aid
performance but made my code much
more complicated.
Make sure to utilize connection pooling.
Primary group has to be handled seperately
you can utilize the tokenGroups and tokenGroupsGlobalAndUniversal properties if you are on Exchange server.
tokenGroups will give you all the security groups this user belongs to, including nested groups and domain users, users, etc
tokenGroupsGlobalAndUniversal will include everything from tokenGroups AND distribution groups
private void DoWorkWithUserGroups(string domain, string user)
{
var groupType = "tokenGroupsGlobalAndUniversal"; // use tokenGroups for only security groups
using (var userContext = new PrincipalContext(ContextType.Domain, domain))
{
using (var identity = UserPrincipal.FindByIdentity(userContext, IdentityType.SamAccountName, user))
{
if (identity == null)
return;
var userEntry = identity.GetUnderlyingObject() as DirectoryEntry;
userEntry.RefreshCache(new[] { groupType });
var sids = from byte[] sid in userEntry.Properties[groupType]
select new SecurityIdentifier(sid, 0);
foreach (var sid in sids)
{
using(var groupIdentity = GroupPrincipal.FindByIdentity(userContext, IdentityType.Sid, sid.ToString()))
{
if(groupIdentity == null)
continue; // this group is not in the domain, probably from sidhistory
// extract the info you want from the group
}
}
}
}
}
If you are using .NET 3.5 or higher you can use the System.DirectoryServices.AccountManagement namespace which really makes this easy.
See the related answer here: Active Directory nested groups
static List<SearchResult> ad_find_all_members(string a_sSearchRoot, string a_sGroupDN, string[] a_asPropsToLoad)
{
using (DirectoryEntry de = new DirectoryEntry(a_sSearchRoot))
return ad_find_all_members(de, a_sGroupDN, a_asPropsToLoad);
}
static List<SearchResult> ad_find_all_members(DirectoryEntry a_SearchRoot, string a_sGroupDN, string[] a_asPropsToLoad)
{
string sDN = "distinguishedName";
string sOC = "objectClass";
string sOC_GROUP = "group";
string[] asPropsToLoad = a_asPropsToLoad;
Array.Sort<string>(asPropsToLoad);
if (Array.BinarySearch<string>(asPropsToLoad, sDN) < 0)
{
Array.Resize<string>(ref asPropsToLoad, asPropsToLoad.Length+1);
asPropsToLoad[asPropsToLoad.Length-1] = sDN;
}
if (Array.BinarySearch<string>(asPropsToLoad, sOC) < 0)
{
Array.Resize<string>(ref asPropsToLoad, asPropsToLoad.Length+1);
asPropsToLoad[asPropsToLoad.Length-1] = sOC;
}
List<SearchResult> lsr = new List<SearchResult>();
using (DirectorySearcher ds = new DirectorySearcher(a_SearchRoot))
{
ds.Filter = "(&(|(objectClass=group)(objectClass=user))(memberOf=" + a_sGroupDN + "))";
ds.PropertiesToLoad.Clear();
ds.PropertiesToLoad.AddRange(asPropsToLoad);
ds.PageSize = 1000;
ds.SizeLimit = 0;
foreach (SearchResult sr in ds.FindAll())
lsr.Add(sr);
}
for(int i=0;i<lsr.Count;i++)
if (lsr[i].Properties.Contains(sOC) && lsr[i].Properties[sOC].Contains(sOC_GROUP))
lsr.AddRange(ad_find_all_members(a_SearchRoot, (string)lsr[i].Properties[sDN][0], asPropsToLoad));
return lsr;
}
static void Main(string[] args)
{
foreach (var sr in ad_find_all_members("LDAP://DC=your-domain,DC=com", "CN=your-group-name,OU=your-group-ou,DC=your-domain,DC=com", new string[] { "sAMAccountName" }))
Console.WriteLine((string)sr.Properties["distinguishedName"][0] + " : " + (string)sr.Properties["sAMAccountName"][0]);
}