I have some code that queries Active Directory to verify user existence. I am trying to verify a long list of about 1300 ids. I've tried several methods to verify if a user account (LINQ to AD, DirectorySearcher (with and without a parent DirectoryEntry) and also a DirectoryEntry that links to the WinNT:// path). Every time it will come back and say that several users do not exist. If I hardcode their userids in the code and execute for individually, it validates existence. If I try and do it in a foreach loop, I get several false negatives.
Here's the code I am using right now..
static string[] userIDs = new string[] "user1","user2","user3","user4","user5","user6","user7","user8"...,"user1300"};
List<string> nonExistingUsers = new List<string>();
List<string> ExistingUsers = new List<string>();
foreach (string s in userIDs)
{
DirectorySearcher search = new DirectorySearcher();
search.Filter = String.Format("(SAMAccountName={0})", s);
search.PropertiesToLoad.Add("cn");
DirectorySearcher ds = new DirectorySearcher(de, "(&(objectClass=user)(cn=" + s + "))", new string[] { "Name" }, SearchScope.Subtree);
SearchResultCollection resultCollection = ds.FindAll();
SearchResult result = search.FindOne();
if (result != null)
ExistingUsers.Add(s);
else
nonExistingUsers.Add(s);
}
Any suggestions or reasons why I am getting the false negatives?
Couple of things:
first of all, try using the "anr=" (ambiguous name resolution) in your LDAP filter - it searches several name-related attributes and make searching easier. The UserID might not be part of the actual "common name" (CN=user1)
secondly, use the objectCategory instead of objectClass - the objectCategory is single-valued and indexed and thus a fair bit faster on searches
thirdly: why are you first calling .FindAll() and then .FindOne() on the next line? Doesn't seem really necessary at all....
WinNT:// really is only for backward compatibility and if you need to deal with local computer accounts - try to avoid it whenever possible, it also exposes a lot less properties than LDAP
Here's my code I'd write:
static string[] userIDs = new string[] "user1","user2","user3","user4","user5","user6","user7","user8"...,"user1300"};
DirectoryEntry searchRoot = new DirectoryEntry("LDAP://cn=Users,dc=YourComp,dc=com");
List<string> nonExistingUsers = new List<string>();
List<string> ExistingUsers = new List<string>();
foreach (string s in userIDs)
{
DirectorySearcher search = new DirectorySearcher(searchRoot);
search.SearchScope = SearchScope.Subtree;
search.Filter = string.Format("(&(objectCategory=person)(anr={0}))", s);
SearchResultCollection resultCollection = ds.FindAll();
if(resultCollection != null && resultCollection.Count > 0)
ExistingUsers.Add(s);
else
nonExistingUsers.Add(s);
}
Does that work in your scenario??
Also, if you're using .NET 3.5 or higher, things got a lot easier - see:
Managing Directory Security Principals in the .NET Framework 3.5
Related
I am trying to return all of the users in 2 OUs. The first OU (below) is IT Users within the HSD Users OU. This returns null every time, but I can get all of the users back with the following.
search.Filter = "(&(objectClass=user))";
I have tried many variations of the OU and DC in combination with no results.
string DomainPath = "LDAP://hs.domain.org";
DirectoryEntry searchRoot = new DirectoryEntry(DomainPath);
DirectorySearcher search = new DirectorySearcher(searchRoot);
//The following filter does work
//search.Filter = "(&(objectClass=user))";
search.Filter = string.Format("(&(objectClass=user)(OU=IT Users,OU=HSD Users,DC=hs,DC=domain,DC=org)");
search.PropertiesToLoad.Add("samaccountname");
search.PropertiesToLoad.Add("mail");
search.PropertiesToLoad.Add("usergroup");
search.PropertiesToLoad.Add("displayname");//first name
search.PropertiesToLoad.Add("manager");
SearchResult result;
SearchResultCollection resultCol = search.FindAll();
if (resultCol != null)
{
for (int counter = 0; counter < resultCol.Count; counter++)
The LDAP filter you use is invalid, hence no records are returned. To locate objects in a particular container/OU or sub-tree, you need to set searchRoot to the path.
To find all entries contained directly in a specific container/OU, set the SearchScope to 1 (OneLevel) with the following constructor:
DirectorySearcher(DirectoryEntry, String, String[], SearchScope)
If you want to find all matching entries under a specific container/OU, then you can use the constructor above but use the specific container/OU as your searchRoot.
I need to fetch active directory records and insert in SQL database. There are approximately 10,000 records. I have used this code:
List<ADUser> users = new List<ADUser>();
DirectoryEntry entry = new DirectoryEntry("LDAP://xyz.com");
ADUser userToAdd = null;
IList<string> dict = new List<string>();
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(&(objectClass=user))";
search.PropertiesToLoad.Add("samaccountname");
search.PageSize = 1000;
foreach (SearchResult result in search.FindAll())
{
DirectoryEntry user = result.GetDirectoryEntry();
if (user != null && user.Properties["displayName"].Value!=null)
{
userToAdd = new ADUser
{
FullName = Convert.ToString(user.Properties["displayName"].Value),
LanId = Convert.ToString(user.Properties["sAMAccountName"].Value)
};
users.Add(userToAdd);
}
}
How can I optimize the above code in terms of speed and space complexity? Can I use traversal in binary tree as Active Directory structure looks similar to binary tree.
The list returned by DirectorySearcher.FindAll() is just a list. So you can't traverse it any better than you already are.
To optimize this, don't use GetDirectoryEntry(). That is doing two things:
Making another network request to AD that is unnecessary since the search can return any attributes you want, and
Taking up memory since all those DirectoryEntry objects will stay in memory until you call Dispose() or the GC has time to run (which it won't till your loop finally ends).
First, add displayName to your PropertiesToLoad to make sure that gets returned too. Then you can access each property by using result.Properties[propertyName][0]. Used this way, every property is returned as an array, even if it is a single-value attribute in AD, hence you need the [0].
Also, if your app is staying open after this search completes, make sure you call Dispose() on the SearchResultCollection that comes out of FindAll(). In the documentation for FindAll(), the "Remarks" section indicates you could have a memory leak if you don't. Or you can just put it in a using block:
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(&(objectClass=user))";
search.PropertiesToLoad.Add("sAMAccountName");
search.PropertiesToLoad.Add("displayName");
search.PageSize = 1000;
using (SearchResultCollection results = search.FindAll()) {
foreach (SearchResult result in results) {
if (result.Properties["displayName"][0] != null) {
userToAdd = new ADUser {
FullName = (string) result.Properties["displayName"][0],
LanId = (string) result.Properties["sAMAccountName"][0]
};
users.Add(userToAdd);
}
}
}
I'd like to search my active directory for all users that belong to a particular physicalDeliveryOfficeName (LDAP) and store them into an array of type SearchResult. Can I do this with a DirectorySearcher filter? Or is there a better approach?
I'm using asp.net, visual c#. Thanks!
Using the DirectorySeacher class, your query could be
(&(objectClass=user)(physicalDeliveryOfficeName=Kalkutta))
where objectClass=user to get only user entries and physicalDeliveryOfficeName=Kalkutta is your query for an office.
DirectoryEntry entry = new DirectoryEntry("LDAP://...");
DirectorySearcher search = new DirectorySearcher(entry)
{
SearchScope = SearchScope.Subtree,
Filter = "(&(objectClass=user)(physicalDeliveryOfficeName=Kalkutta))"
};
search.PropertiesToLoad.Add("cn");
SearchResultCollection result = search.FindAll();
foreach (SearchResult r in result)
Response.Write(r.Properties["cn"][0]);
I am trying to access the names of all users in a specific folder, and i cannot find out how to do this. So far I can only search the entire active directory and limit my filter options but this is not enough. This is what I have so far:
DirectoryEntry dir = new DirectoryEntry("LDAP://path.local", "username", "password");
string filter = "(&(objectCategory=person)(objectClass=user);(!userAccountControl:1.2.840.113556.1.4.803:=2)(c=CA))";
string[] propertiesToLoad = new string[1] { "name" };
DirectorySearcher searcher = new DirectorySearcher(dir, filter, propertiesToLoad);
SearchResultCollection results = searcher.FindAll();
List<string> usernames = new List<string>();
foreach (SearchResult result in results)
{
string name = (string)result.Properties["name"][0];
if (name != null)
usernames.Add(name);
}
How would i go about searching for a specific file in the active directory, say if the path was Buisness\Canada\Users?
Ok, using Directories, you should not talk about folders, but OrganizationaUnit (OU).
So here is your vision :
And here (using ADSIEDIT.MSC) is the LDAP vision :
So the path to the MonOU folder is :
OU=MonOU,DC=SILOGIX-ESS01,DC=local
So the code to start your search begining this folder is :
DirectoryEntry dir = new DirectoryEntry("LDAP://path.local/OU=MonOU,DC=SILOGIX-ESS01,DC=local", "username", "password");
string filter = "(&(objectCategory=person)(objectClass=user);(!userAccountControl:1.2.840.113556.1.4.803:=2)(c=CA))";
string[] propertiesToLoad = new string[1] { "name" };
DirectorySearcher searcher = new DirectorySearcher(dir, filter, propertiesToLoad);
SearchResultCollection results = searcher.FindAll();
List<string> usernames = new List<string>();
foreach (SearchResult result in results)
{
string name = (string)result.Properties["name"][0];
if (name != null)
usernames.Add(name);
}
That's not all, you should also configure the scope of your search using :
searcher.SearchScope = SearchScope.Subtree;
Have a look to the 3 volues of SearchScope.
Last thing in a production context, you should write in your search the attributes you want to retreive (like in a SQL query) in order to be sure to retreive those you want and not to retreive too much (performance).
searcher.PropertiesToLoad.Add("cn");
searcher.PropertiesToLoad.Add("objectSid");
If your domain is called contoso.com and you want to search the path mentioned, you'd set this line like this:
DirectoryEntry dir = new DirectoryEntry("LDAP://OU=Users,OU=Canada,OU=Business,DC=contoso,DC=com", "username", "password");
OK, working with active directory, my one remaining requirement is to pull back all the users in a given group and get their details - first name, last name, username, email.
Having read up around this I was surprised to discover that there doesn't seem to be a way of doing this in an efficient fashion. I've worked out two ways of getting the results, but both seem absurdly wasteful.
The first is to search members of a group, like this:
DirectoryEntry searchRoot = new DirectoryEntry("LDAP://" + server, username, password);
DirectorySearcher search = new DirectorySearcher(searchRoot);
search.Filter = string.Format("(|(CN={0})(CN={1}))", "Group1", "Group2");
search.PropertiesToLoad.Add("member");
SearchResultCollection results = search.FindAll();
Which brings back a single result containing usernames, and usernames only. You can dissect this into an array of individual usernames, but then to get the details of each user you have to search AD again on a per-name basis.
The other approach is to do this:
DirectoryEntry searchRoot = new DirectoryEntry("LDAP://" + server, username, password);
DirectorySearcher search = new DirectorySearcher(searchRoot);
search.Filter = string.Format("(&(&(objectClass=user)(objectCategory=person))(memberOf=*))");
search.PropertiesToLoad.Add("memberOf");
search.PropertiesToLoad.Add("name");
search.PropertiesToLoad.Add("mail");
search.PropertiesToLoad.Add("displayName");
SearchResultCollection mySearchResultColl = search.FindAll();
foreach (SearchResult result in mySearchResultColl)
{
foreach (string prop in result.Properties["memberOf"])
{
if (prop.Contains("Group1") || prop.Contain("Group2"))
{
//add user to list
}
}
}
Which gets me what I want but involves retrieving every active directory user and then iterating through the collection to find ones that match. That works fine on my little test directory, but I shudder to think how slow it's going to be on a system with thousands of users.
I'm aware you can do what I need using the PrinicpalContext object, but as best I can tell that only works if the code is running in the same domain, which I can't guarantee. I need to be able to query across domains.
Is there a better way of doing this? Or am I just going to have to suck on the performance issues?
If you only need to find direct members, you can use Attribute Scope Query (ASQ).
This requires domain/forest functional level of 2003 (forgot domain or forest).
DirectoryEntry groupEntry = new DirectoryEntry("LDAP://<server>/<group DN>", "user", "pwd");
DirectorySearcher searcher = new DirectorySearcher(groupEntry);
searcher.SearchScope = SearchScope.Base;
searcher.AttributeScopeQuery = "member";
searcher.Filter = "(&(objectCategory=person)(objectClass=user))";
searcher.PropertiesToLoad.Clear();
searcher.PropertiesToLoad.Add("name");
searcher.PropertiesToLoad.Add("mail");
searcher.PropertiesToLoad.Add("displayName");
foreach (SearchResult result in searcher.FindAll())
{
Console.WriteLine(result.Path);
}
For nested group members, you may use the LDAP_MATCHING_RULE_IN_CHAIN matching rule.
This requires domain/forest functional level of 2008 R2 (again, forgot domain or forest).
DirectoryEntry rootEntry = new DirectoryEntry("GC://<server>", "user", "pwd");
DirectorySearcher searcher = new DirectorySearcher(rootEntry);
searcher.SearchScope = SearchScope.Subtree;
searcher.Filter = "(&(objectCategory=person)(objectClass=user)(memberOf:1.2.840.113556.1.4.1941:=<group DN>))";
searcher.PropertiesToLoad.Clear();
searcher.PropertiesToLoad.Add("name");
searcher.PropertiesToLoad.Add("mail");
searcher.PropertiesToLoad.Add("displayName");
foreach (SearchResult result in searcher.FindAll())
{
Console.WriteLine(result.Path);
}
Limitations:
Both methods don't handle primary group membership
ASQ don't work across domain, an exception will be thrown if member from another domain is found. So normally you can only perform this for global groups.
You could utilize an ANR search for some of those attributes For more information, see this article - the functionality has been there since 2000:
http://support.microsoft.com/kb/243299
In order to search for other attributes not in the default set, you'd need to tweak the schema (which may not be suitable in your situation).