getting user details from AD is slow - c#

Im using the following code to get a bunch of information about employees from specific departments and returning a list from AD...
Whilst it works, it appears to be quite slow, is a there more efficient way of getting various user details from AD?
public static List<Employee> GetEmployeeListForDepartment(string departpment)
{
using (HostingEnvironment.Impersonate())
{
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, domain);
GroupPrincipal gp = GroupPrincipal.FindByIdentity(ctx, departpment);
PrincipalSearchResult<Principal> members = gp.GetMembers();
List<Employee> employees = new List<Employee>();
foreach (var member in members)
{
var emp = CreateEmployee(member);
employees.Add(emp);
}
return employees.OrderBy(x => x.FirstName).ToList();
}
}
private static Employee CreateEmployee(Principal member)
{
if (member != null)
{
DirectoryEntry de = (member.GetUnderlyingObject() as DirectoryEntry);
if (de != null)
{
string mobile = de.Properties.Contains("mobile") ? de.Properties["mobile"][0].ToString() : "";
string title = de.Properties.Contains("description") ? de.Properties["description"][0].ToString() : "";
//ETC ETC...
return new Employee { etc.. };
}
}
return new Employee();
}

Your problem is that you are using System.DirectoryServices.AccountManagement... While I hate saying it, it's sadly the truth. The way AccountManagement works under the hood is that it runs a seperate LDAP query to retrieve each item seperately. So when you iterate through members it's making a seperate call back through LDAP for each member. What you want to do instead is run an LDAP query using System.DirectoryServices.DirectorySearcher.
My assumption is that department is a group, based on how you are using it. Here is how I would do it. (my code is in VB.Net... sorry). Make sure to get the fully qualified DN for your group, or look it up in advance and plug it into the query.
Dim results = LDAPQuery("(memberOf=CN=Fully,OU=Qualified,DC=Group,DC=Distinguished,DC=Name)", New String() {"mobile", "description"})
Dim emps = (from c as System.DirectoryServices.SearchResult in results _
Select New Employee() {.Name = c.Properties("description"), .Mobile = c.Properties("mobile")}).ToList()
Public Function LDAPQuery(ByVal query As String, ByVal attributes As String()) As SearchResultCollection
'create directory searcher from CurrentADContext (no special security available)
Dim ds As New DirectorySearcher(System.DirectoryServices.ActiveDirectory.Domain.GetCurrentDomain().GetDirectoryEntry())
ds.PageSize = 1000
'set filter string
ds.Filter = query
'specify properties to retrieve
ds.PropertiesToLoad.AddRange(attributes)
'get results
Dim results As SearchResultCollection = ds.FindAll()
Return results
End Function

You should be able to use the Active Directory API directly.
Most of it is available under 'System.DirectoryServices'
I don't have any code to hand to do exactly what you need, but I do have an article on my Blog from about a year back, that shows how to create local user accounts in AD, all of which uses the same assemblies as needed to get user information.
http://shawtyds.wordpress.com/2010/12/08/a-little-bit-of-ldap-here-there/
NOTE: AD however by it's very nature is slow, so there's a good chance you might not be able to get any faster if you have a large directory.

Related

Retrieve records from Active Directory using data structure algorithm in c#

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

Get all users and contacts from a group in Active Directory

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.

Dynamic Search using Suitetalk

I am trying to create a C# application (Using suitetalk) that would allow us to search through Netsuite records. The record type will be specified dynamically. Please can you help?
I have checked the webservices and identified that SearchRecord class has many sub classes of type AccountSearch, ItemSearch, etc.
However, I wanted to do these searches dynamically.
AccountSearch acc = new AccountSearch();
SearchResult searchresult = new SearchResult();
searchresult = _service.search(acc);
The above code gives me the list of accounts. But, the AccountSearch is hardcoded here.
The piece of code below works.
SearchRecord search;
SearchRecord searchCriteria;
SearchRecordBasic searchBasicCriteria;
if(recordType.equals(RecordType.account)){
search = new AccountSearchAdvanced();
searchCriteria = new AccountSearch();
searchBasicCriteria = new AccountSearchBasic();
//set criteria on searchBasicCriteria
((AccountSearch) searchCriteria).setBasic((AccountSearchBasic) searchBasicCriteria);
((AccountSearchAdvanced) search).setCriteria((AccountSearch) searchCriteria);
}else if(recordType.equals(RecordType.customer)){
search = new CustomerSearchAdvanced();
searchCriteria = new CustomerSearch();
searchBasicCriteria = new CustomerSearchBasic();
//set criteria on searchBasicCriteria
((CustomerSearch) searchCriteria).setBasic((CustomerSearchBasic) searchBasicCriteria);
((CustomerSearchAdvanced) search).setCriteria((CustomerSearch) searchCriteria);
}else{
search = null;
}
if(search != null) _service.search(search);
But I think a better solution would be to create specific methods for each type of search. That way the code is more readable, plus you avoid all that casting. Then you would have to handle the returned RecordList for each specific record type.

Active Directory search - filter by Manager

I'm trying to get a list of users from the Active Directory, who have a specified manager.
I used the following LDAP filter without success:
(manager=CN=Misterboss_n*)
However, it returns no result. Users have the following value in the manager attribute:
"CN=Misterboss_n,OU=xyz user,DC=xyz,DC=local"
What am I doing wrong? If I replace the above filter with something like this:
(givenName=John*)
it works okay (returns all users whose given name is John).
Wider context:
public List<ADUserDetail> GetAllEmployeesUnderMisterboss()
{
List<ADUserDetail> userlist = new List<ADUserDetail>();
string filter = "";
_directoryEntry = null;
DirectorySearcher directorySearch = new DirectorySearcher(SearchRoot);
directorySearch.Asynchronous = true;
directorySearch.CacheResults = true;
filter = "(manager=CN=Misterboss_n*)";
directorySearch.Filter = filter;
SearchResultCollection userCollection = directorySearch.FindAll();
foreach (SearchResult users in userCollection)
{
DirectoryEntry userEntry = new DirectoryEntry(users.Path, LDAPUser, LDAPPassword);
ADUserDetail userInfo = ADUserDetail.GetUser(userEntry);
userlist.Add(userInfo);
}
return userlist;
}
Thanks for the help!
I don't think there is a start-of-field search available for DN-typed properties. You will have to use the full DN of the manager. If you don't know the full DN, find the manager's LDAP object first and use its distinguishedName property.
Be sure to escape the DN value properly before building your filter - not every character that is valid in a DN is also valid in an LDAP filter expression:
* as \2a
( as \28
) as \29
\ as \5c
NUL as \00
/ as \2f
For code samples, see this related thread where I answered a very similar question: Getting all direct Reports from Active Directory

Active Directory not finding all users in C#

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

Categories