Fast way to get all users + specify attribute + use paging from LDAP - c#

I would like to get all users with their attributes from active directory
I checked many topics includes Linq to LDAP + enter link description here
But all seems to be complicated.
I started with this:
public SearchResultCollection GetAllUsrs()
{
var dirEntry = new DirectoryEntry(string.Format("LDAP://{0}/{1}", "x.y.com", "DC=x,DC=y,DC=com"));
var searcher = new DirectorySearcher(dirEntry)
searcher.Filter = "(&(&(objectClass=user)(objectClass=person)))";
searcher.PageSize = 999;
return searcher.FindAll();
}
How can i use pagging since the active directory will only return 1000 record at the time
+ how can i specify the attribute?
Problem:
I want to query a domain that contain up to 60 K users with console application
I want to specify the attribute
Performance is very important.
Can you please guide me to the best way to achieve this?

Paging is not required. AD will return more than 1000 objects. Leave PageSize at 0 and set SizeLimit as required. Use int.MaxValue ;) if you're unsure.

Here's how you can do it with LINQ to LDAP:
using (var connection = new LdapConnection("x.y.com"))
{
using (var context = new DirectoryContext(connection))
{
List<IDirectoryAttributes> users = context
.Query("DC=x,DC=y,DC=com")
.Where("(&(objectClass=user)(objectClass=person))")
.InPagesOf(1000);
}
}

Related

How to get LDAP nested groups from attribute

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

How to search a user that has a letter (or a few) in his display name

I'll try to explain myself better.
I'm using C# to build a mini-program to myself. I want to search a user within my active directory, but I want to search a user without a full display name. Let me explain myself.
For example, my display name can be: "David Holonka\Jeramy".
Is there a way to search the letters "lonka" and it'll find me all the users that has these combinations of letters within their display name?
My current code:
using (var pc = new PrinicpalContext(ContextType.Domain, "MyDomain"))
{
UserPrincipal user = new UserPrinicpal(pc);
User.DisplayName = "Holonka";
PrinicpalSearcher scrh = new PrinicpalSearcher(user);
Prinicpal found = scrh.FindOne();
}
}
Right now it doesn't find anything becasue there isn't a user that its display name is only "Holonka", but I want it to find the user that i've mentioned before
Thank you very much!
You can use query string in the PrinicpalSearcher:
UserPrincipal user = new UserPrinicpal(pc);
User.DisplayName = "*Holonka*";
PrinicpalSearcher searcher = new PrinicpalSearcher(user);
var results = searcher.FindAll();
You can also using PrincipalSearcher to find users with “or” parameters like in the example below:
List<UserPrincipal> searchPrinciples = new List<UserPrincipal>();
searchPrinciples.Add(new UserPrincipal(context) { DisplayName="*Holonka*"});
searchPrinciples.Add(new UserPrincipal(context) { SamAccountName = "*Holonka*" });
searchPrinciples.Add(new UserPrincipal(context) { MiddleName = "*Holonka*" });
searchPrinciples.Add(new UserPrincipal(context) { GivenName = "*Holonka*" });
List<Principal> results = new List<Principal>();
foreach (var item in searchPrinciples)
{
var searcher = new PrincipalSearcher(item);
// Results may contains duplicate values because of separate searchers can handle the same user
results.AddRange(searcher.FindAll());
}
You can also try using Ambiguous Name Resolution, which is a special AD query that looks for partial matches in several attributes (the list of those attributes is in that article).
You can't use PrincipalSearcher do use ANR though. You have to use DirectorySearcher (which is what PrincipalSearcher uses in the background anyway).
Here's an example:
var searchRoot = new DirectoryEntry("LDAP://domain.com");
var searcher = new DirectorySearcher(searchRoot) {
Filter = "(anr=Holonka)"
};
searcher.PropertiesToLoad.Add("displayName");
using (var results in = searcher.FindAll()) {
foreach (SearchResult result in results) {
if (result.Properties.Contains("displayName")) {
var displayName = (string) result.Properties["displayName"][0];
//do something else
}
}
}
When using DirectorySearcher, it's wise to use PropertiesToLoad. If you don't, it will return every attribute that has a value, which is likely way more data than you need. It's a waste of network traffic and time.
The using statement is also wise, since SearchResultCollection can't be completely cleaned up by the garbage collector.
I wrote some more about that in an article I wrote: Active Directory: Better Performance

Get Active Directory users in Group

I'm trying to get all users in "Programmers" group from AD.
If I use directory entry as LDAP://DC=Domain and filter as memberOf=CN=Programmers,CN=Users,DC=Domain, I can get user list.
But if I directly use entry as LDAP://CN=Programmers,CN=Users,DC=Domain, I can't get any result.
using (DirectoryEntry de = new DirectoryEntry(string.Format("LDAP://CN=Programmers,CN=Users,DC=Domain")))
using (DirectorySearcher ds = new DirectorySearcher(de))
{
int pageIndex = PAGESIZE * nPage + 1;
ds.SearchScope = SearchScope.Subtree;
ds.Sort = new SortOption("samaccountname", SortDirection.Ascending);
ds.VirtualListView = new DirectoryVirtualListView(0, PAGESIZE - 1, pageIndex);
var results = ds.FindAll();
}
Can anybody tell why?
The reason why I want to use this instead of "memberOf" filter, is for the performance consideration. But I'm not sure if this will actually improve the performance. So the second question is: is there any performance difference between these two methods?
It finally worked out...
The code should be:
ds.SearchScope = SearchScope.Base;
ds.AttributeScopeQuery = "member";
Then it will work.

LDAP query doesn't display certain users

I am unable to display some users from LDAP. I dont know why. Here's my code
try
{
string path = "LDAP://" + Program.domain;
DirectoryEntry dEntry = new DirectoryEntry(path);
DirectorySearcher dSearcher = new DirectorySearcher(dEntry);
dSearcher.Filter = "(&(objectClass=user)(objectCategory=person))";
//perform search on active directory
sResults = dSearcher.FindAll();
//loop through results of search
foreach (SearchResult searchResult in sResults)
{
//string view = searchResult.Properties["samaccountname"][0].ToString();
// Console.WriteLine(searchResult.Properties["userprincipalname"][0].ToString());
if (searchResult.Properties["samaccountname"][0].ToString() == Program.username)
{
Console.WriteLine("**********UserDetails******************");
foreach (Object propertyName in searchResult.Properties.PropertyNames)
{
ResultPropertyValueCollection valueCollection =
searchResult.Properties[(string)propertyName];
foreach (Object propertyvalue in valueCollection)
{
Console.WriteLine((string)propertyName + " : " + propertyvalue);
result = true;
}
}
Console.WriteLine("************************************");
}
}
This displays few users but few other users who exist in AD are not displayed.
They're also Domain Admins and Domain users. I don't see any permission issues too yet...
I seriously need some help.Can someone help me please?
Thanks
There are two likely causes:
0) Access control: You do not have the appropriate level of access to view the objects in question (or the properties required to match them in the filter (be it objectClass or objectCategory)).
1) The target objects in question do not actually match the filter specified. Users can be something other than (&(objectClass=user)(objectCategory=person)).
My suggestion is to approach the problem as follows:
0) Take one sample user that you expect to match and inspect it carefully. Check to ensure that objectClass does in fact contain user and objectCategory is set to person. If not, modify your query to be inclusive of all of the users you are trying to find. (You can consult the AD schema to see the relationship between these things)
1) Make sure the context under which you're doing the query has access to all of the objects you want to find including the attributes that you're using in your filter. AD won't return a match to a query if you don't have access to all of the attributes in the filter...if it did, it'd be a form of information disclosure.

Find Recursive Group Membership (Active Directory) using C#

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]);
}

Categories

Resources