I'm trying to export members of specific AD groups. I have a working solution to get ALL and filter, but that seems excessive if the group I want has 5 out of 1000 possible users..
I'm working in this direction:
public void PrintMembers(string groupname, string domain)
{
GroupPrincipal group = GroupPrincipal.FindByIdentity(new PrincipalContext(ContextType.Domain, domain), groupname);
foreach (Principal princ in group.Members)
{
if (princ.StructuralObjectClass == "user")
{
Response.Write(UserPrincipal.FindByIdentity(new PrincipalContext(ContextType.Domain, domain), princ.Name));
}
}
}
This sort of works, but fails to give members that have inherited the membership through an underlying group.
So:
"Specific group 1" = I get all 5 members alright
"Specific group 2" = I get all 7 members alright
"Mother group", that holds the two groups above = I get no members...
I could iterate that groups subgroups, but feel there must be another way....
Any suggestions?
Anything wrong with GetMembers(true)?
static void Main(string[] args)
{
foreach (string user in GetMemberNames("My Group", "domain.local"))
{
Console.WriteLine(user);
}
Console.ReadKey();
}
public static string[] GetMemberNames(string groupname, string domain)
{
using (PrincipalContext context = new PrincipalContext(ContextType.Domain, domain))
using (GroupPrincipal group = GroupPrincipal.FindByIdentity(context, groupname))
using (PrincipalSearchResult<Principal> results = group.GetMembers(true))
{
return results.OfType<UserPrincipal>().Select(u => u.SamAccountName).ToArray();
}
}
First of all : #shriop point the exact answer to your question.
As far as the bounty is concerned that is to say : "A solution which uses the LDAP protocol to enumerate the users in a group and it's subgroups without recursion". Here is something working withe Active-Directory begining Windows Server 2003 SP2 and called LDAP_MATCHING_RULE_IN_CHAIN. It Search recursively (but in one query) all the users from a group (be careful it return users from security and distribution groups). Here is the ADSI usage in C# :
static void Main(string[] args)
{
/* Connection to Active Directory
*/
string sFromWhere = "LDAP://SRVENTR2:389/dc=societe,dc=fr";
DirectoryEntry deBase = new DirectoryEntry(sFromWhere, "societe\\administrateur", "test.2011");
/* To find all the users member of groups "Grp1" :
* Set the base to the groups container DN; for example root DN (dc=societe,dc=fr)
* Set the scope to subtree
* Use the following filter :
* (member:1.2.840.113556.1.4.1941:=CN=Grp1,OU=MonOu,DC=X)
*/
DirectorySearcher dsLookFor = new DirectorySearcher(deBase);
dsLookFor.Filter = "(&(memberof:1.2.840.113556.1.4.1941:=CN=Grp1,OU=MonOu,DC=societe,DC=fr)(objectCategory=user))";
dsLookFor.SearchScope = SearchScope.Subtree;
dsLookFor.PropertiesToLoad.Add("cn");
dsLookFor.PropertiesToLoad.Add("samAccountName");
SearchResultCollection srcUsers = dsLookFor.FindAll();
/* Just show each user
*/
foreach (SearchResult srcUser in srcUsers)
{
Console.WriteLine("{0}", srcUser.Path);
Console.WriteLine("{0}", srcUser.Properties["samAccountName"][0]);
}
Console.ReadLine();
}
Related
I have been searching around for a solution on getting both users and contacts from a group in Active Directory, but cant find one.
I understand that I cant get contacts the same way as users because they are not security principals?
I use this code to get all users in my group, is it possible to extend this to retrieve name, and mobile number from contacts? Or do I need to write something new?
var context = new PrincipalContext(ContextType.Domain, "MY_DOMAIN");
using (var searcher = new PrincipalSearcher())
{
var groupName = "MY_GROUP";
var sp = new GroupPrincipal(context, groupName);
searcher.QueryFilter = sp;
var group = searcher.FindOne() as GroupPrincipal;
if (group == null)
Console.WriteLine("Invalid Group Name: {0}", groupName);
foreach (var f in group.GetMembers())
{
var principal = f as UserPrincipal;
if (principal == null || string.IsNullOrEmpty(principal.Name))
continue;
DirectoryEntry entry = (principal.GetUnderlyingObject() as DirectoryEntry);
DirectorySearcher entrySearch = new DirectorySearcher(entry);
entrySearch.PropertiesToLoad.Add("mobile");
entrySearch.PropertiesToLoad.Add("sAMAccountName");
entrySearch.PropertiesToLoad.Add("name");
SearchResultCollection results = entrySearch.FindAll();
ResultPropertyCollection rpc = results[0].Properties;
foreach (string rp in rpc.PropertyNames)
{
if (rp == "mobile")
Console.WriteLine(rpc["mobile"][0].ToString());
if(rp == "sAMAccountName")
Console.WriteLine(rpc["sAMAccountName"][0].ToString());
}
You cannot use the System.DirectoryServices.AccountManagement namespace to query contact information from Active Directory because as you point out, they are not security principles. You'll need to read and parse the member property of the group directly from the group's DirectoryEntry. This will be a list of distinguished names of all the objects which are a member of the group. There's no way to know from this what kind of object they are so you'll need to query AD for each to find out.
You have all the code needed to accomplish this already in what you posted, just add the member property to the load list and then loop though it loading new DirectoryEntry objects. The objectClass property will tell you if it's a user, group or contact.
Have a scenario to verify logged in User is belong to AD Group mentioned in web.config. The code used below is working fine, but taking lot of time when looping through. (Some case User has relation of 100+ group in AD)string.
Is there a way to achieve this with direct method for better performance.
Note: I have tried Contains() method of Group obj, but could not pass string as input.
allowedRoles = "Role1, Role2"; //Read from web.config
string[] allowedRolesList = allowedRoles.Split(',');
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, UserDomain);
UserPrincipal user = UserPrincipal.FindByIdentity(ctx,IdentityType.SamAccountName,SLID);
if (user != null)
{
var groups = user.GetAuthorizationGroups();
foreach (string allowedrole in allowedRolesList)
{
foreach (GroupPrincipal group in groups)
{
if (group.Name.ToLower() == allowedrole.ToLower())
{
return true;
}
}
}
}
Is there a way to get all active users in multiple groups?
For example:
Get all active users in "AdGroupA" OR "AdGroupB" OR "AdGroupC"
I have see post about single group but not multiple groups.
Thanks.
If I understand you correctly, you just want to return the entire list of users in multiple groups? That should be as easy as getting the users from the single group multiple times.
public IEnumerable<UserPrincipal> GetUsersFromGroups(string[] groupNames)
{
using (var ctx = new PrincipalContext(ContextType.Domain))
{
foreach (var groupName in groupNames)
{
foreach (var userPrincipal in GroupPrincipal.FindByIdentity(ctx, groupName)
.GetMembers())
{
yield return userPrincipal;
}
}
}
}
Here is a method not using AccountManagement:
using System.DirectoryServices;
public static IEnumerable<DirectoryEntry> GetUsersFromGroups(string[] groupNames)
{
if (groupNames.Length > 0)
{
var searcher = new DirectorySearcher();
string searchFilter = "(&(objectClass=Group)"; //filter for groups
searchFilter += "(|"; //start a group of or parameters
foreach (var group in groupNames) // loop through the group names
{
searchFilter += string.Format("(SAMAccountName={0})",group); //add a parameter for each group in the list
}
searchFilter += "))"; //close off the filter string
searcher.Filter = searchFilter; //add the filter to the searcher
searcher.PropertiesToLoad.Add("member"); // load the members property for the group
var searchResults = searcher.FindAll(); // perform the search
foreach (SearchResult result in searchResults)
{
var directoryEntry = (DirectoryEntry)result.GetDirectoryEntry(); // get the directory entry for the group
PropertyValueCollection members = directoryEntry.Properties["member"]; // get the members collection
foreach (string name in members) // iterate over the members. This string will be the distinguished name
{
yield return new DirectoryEntry(string.Format("LDAP://{0}",name)); //return the directory entry. You may get the entry and return the display name or just return distinguished name.
}
}
}
}
In my environment I found this to be about 25% faster on average than using DirectoryServices.AccountManagement for 1 group, but as the count of groups and users increased the AccountManagement method actually became faster. This only queries AD once while the first method queries once per group.
I've an Active Directory with domain myDomain.local, under it there exists a Distribution Group that contains many groups.
How can I read (programmatically) all these subgroups to retrieve a list of their names ?
And how to optimize the query to filter the result so that it just retrieves all the groups that ends with the word Region ?
BTW, I'm using C#.Net, ASP.Net and sharepoint, and i'm not experienced with AD.
If you're on .NET 3.5 (or can upgrade to it), you can use this code using the System.DirectoryServices.AccountManagement namespace:
// create the "context" in which to operate - your domain here,
// as the old-style NetBIOS domain, and the container where to operate in
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "YOURDOMAIN", "cn=Distribution Group,dc=YourDomain,dc=local");
// define a "prototype" - an example of what you're searching for
// Here: just a simple GroupPrincipal - you want all groups
GroupPrincipal prototype = new GroupPrincipal(ctx);
// define a PrincipalSearcher to find those principals that match your prototype
PrincipalSearcher searcher = new PrincipalSearcher(prototype);
// define a list of strings to hold the group names
List<string> groupNames = new List<string>();
// iterate over the result of the .FindAll() call
foreach(var gp in searcher.FindAll())
{
// cast result to GroupPrincipal
GroupPrincipal group = gp as GroupPrincipal;
// if everything - grab the group's name and put it into the list
if(group != null)
{
groupNames.Add(group.Name);
}
}
Does that satisfy your needs?
For more info on the System.DirectoryServices.AccountManagement namespace, read the Managing Directory Security Principals in the .NET Framework 3.5 article in MSDN magazine.
Here's the solution I made; for those who are interested:
public ArrayList getGroups()
{
// ACTIVE DIRECTORY AUTHENTICATION DATA
string ADDomain = "myDomain.local";
string ADBranchsOU = "Distribution Group";
string ADUser = "Admin";
string ADPassword = "password";
// CREATE ACTIVE DIRECTORY ENTRY
DirectoryEntry ADRoot
= new DirectoryEntry("LDAP://OU=" + ADBranchsOU
+ "," + getADDomainDCs(ADDomain),
ADUser,
ADPassword);
// CREATE ACTIVE DIRECTORY SEARCHER
DirectorySearcher searcher = new DirectorySearcher(ADRoot);
searcher.Filter = "(&(objectClass=group)(cn=* Region))";
SearchResultCollection searchResults = searcher.FindAll();
// ADDING ACTIVE DIRECTORY GROUPS TO LIST
ArrayList list = new ArrayList();
foreach (SearchResult result in searchResults)
{
string groupName = result.GetDirectoryEntry().Name.Trim().Substring(3);
list.Add(groupName);
}
return list;
}
public string getADDomainDCs(string ADDomain)
{
return (!String.IsNullOrEmpty(ADDomain))
? "DC=" + ADDomain.Replace(".", ",DC=")
: ADDomain;
}
I have code that searches for all users in a department:
string Department = "Billing";
DirectorySearcher LdapSearcher = new DirectorySearcher();
LdapSearcher.PropertiesToLoad.Add("displayName");
LdapSearcher.PropertiesToLoad.Add("cn");
LdapSearcher.PropertiesToLoad.Add("department");
LdapSearcher.PropertiesToLoad.Add("title");
LdapSearcher.PropertiesToLoad.Add("memberOf");
LdapSearcher.Filter = string.Format("(&(objectClass=user)(department={0}))", Department);
SearchResultCollection src = LdapSearcher.FindAll();
What would the filter need to look like if I only wanted everyone in the "Manager Read Only" AD Group?
Am I going about this all wrong?
Looking at your search I have a couple of points for you. First, the search uses objectClass (non-indexed) instead of objectCategory (indexed). Huge performance issue with that query. You would most always want to combine the two together depending on what you are trying to retrieve:
(&(objectCategory=person)(objectClass=user)) = All users (no contacts)
(&(objectCategory=person)(objectClass=contact)) = All contacts (no users)
(&(objectCategory=person)) = All users and contacts
As for looking up the users in a group you can enumerate the list of member objects of the specific group. In the member attribute of the group object is the distinguishedName of each user.
This article describes enumerating members of a group...
Don't forget that you may have to handle nested groups of the parent group, as there isn't a default way to handle this with LDAP queries. For that you may need to evaluate if the member object is a group and then get the member attribute for that child group.
Lastly, you should get in the habit of specifying a dns prefix to your query.
Without DNS prefix:
LDAP://ou=ouname,dc=domain,dc=com
With DNS prefix (all three work):
LDAP://servername/ou=ouname,dc=domain,dc=com
LDAP://servername.domain.com/ou=ouname,dc=domain,dc=com
LDAP://domain.com/ou=ouname,dc=domain,dc=com
A single domain won't cause you much issue but when you try and run a search in a multiple domain environment you will get bitten without this addition. Hope this helps move you closer to your goal.
I've always found Howto: (Almost) Everything In Active Directory via C# helps for most AD questions.
If you know the AD path to the group already it would probably be easier to open a DirectoryEntry on that, then do a DirectorySearcher from there.
using (DirectoryEntry de = new DirectoryEntry("LDAP://somedomain/CN=FooBar"))
{
DirectorySearcher search = new DirectorySearcher(de, ("(objectClass=user)"));
}
There is also a flag on the Searcher for whether to drill down to sub containers, I forget the name off hand.
I use following code (from http://blogs.technet.com/b/brad_rutkowski/archive/2008/04/15/c-getting-members-of-a-group-the-easy-way-with-net-3-5-discussion-groups-nested-recursive-security-groups-etc.aspx) it works fine.
IList<string> getMembers(string domainName, string groupName)
{
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, domainName);
GroupPrincipal grp = GroupPrincipal.FindByIdentity(ctx, IdentityType.Name, groupName);
if (grp == null) {
throw new ApplicationException("We did not find that group in that domain, perhaps the group resides in a different domain?");
}
IList<string> members = new List<String>();
foreach (Principal p in grp.GetMembers(true))
{
members.Add(p.Name); //You can add more attributes, samaccountname, UPN, DN, object type, etc...
}
grp.Dispose();
ctx.Dispose();
return members;
}
//Search for Group and list group members
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.DirectoryServices.AccountManagement;
namespace ExportActiveDirectoryGroupsUsers
{
class Program
{
static void Main(string[] args)
{
if (args == null)
{
Console.WriteLine("args is null, useage: ExportActiveDirectoryGroupsUsers OutputPath"); // Check for null array
}
else
{
Console.Write("args length is ");
Console.WriteLine(args.Length); // Write array length
for (int i = 0; i < args.Length; i++) // Loop through array
{
string argument = args[i];
Console.Write("args index ");
Console.Write(i); // Write index
Console.Write(" is [");
Console.Write(argument); // Write string
Console.WriteLine("]");
}
try
{
using (var ServerContext = new PrincipalContext(ContextType.Domain, ServerAddress, Username, Password))
{
/// define a "query-by-example" principal - here, we search for a GroupPrincipal
GroupPrincipal qbeGroup = new GroupPrincipal(ServerContext, args[0]);
// create your principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeGroup);
// find all matches
foreach (var found in srch.FindAll())
{
GroupPrincipal foundGroup = found as GroupPrincipal;
if (foundGroup != null)
{
// iterate over members
foreach (Principal p in foundGroup.GetMembers())
{
Console.WriteLine("{0}|{1}", foundGroup.Name, p.DisplayName);
// do whatever you need to do to those members
}
}
}
}
//Console.WriteLine("end");
}
catch (Exception ex)
{
Console.WriteLine("Something wrong happened in the AD Query module: " + ex.ToString());
}
Console.ReadLine();
}
}
}
}