Custom Properties in AD , Open DirectorySearcher Queries and Large LDAP Queries
QUESTION
Mike,
I am new to AD and LDAP but have programmed the last couple of years in C#.
I have read your articles. I am trying to write a program that will go in and check three columns in a User OU for each of the objects in that OU. I need to check the sAMAccountName, EmployeeNumber and UIDNumber. I have to make sure that the EN and the UID are the same but they must be different than the sAMAccountName. I then must make sure that that data is different from all the other objects.
do you have any suggestions? is there a way to do a Select * from LDAP here to just get those attributes from each object?
Thanks I appreciate any help you could provide.
Dean
RESPONSE
As I proceed, I want to clarify a little about properties and object references. Please forgive me if I seem critical in some of this – as I certainly do not intend to. Rather, I want to clarify object references in an effort to help others.
In AD, an OU does not share the properties EmployeeNumber or uid. However, a user object, referred to as the CN, does share these properties. So, when we’re talking about a user object, it is not the same as User OU – but is simply the user object, and is referenced by the CN= for that object. I hope this makes sense.
Meat and Potatoes
I would imagine that the real objective here is to determine how to query AD for specific properties and property values. I will focus on that for now, but if more is desired, please let me know and I will respond.
Foremost, the EmployeeNumber and the uid are standard properties available for user objects, and you can write to and read from these manually. They are not dynamically updated by AD – out of the box. What I mean by that is, if you have a standard deployment of AD and a typical network architecture, your AD server will not maintain these fields for you. In some organizations you may find Group Policy Objects (GPO’s) in place that do maintain specific properties within AD – but this is not very common. So, lets move forward…
I have an employee makeup that not only includes an AD user account (the sAMAccountName – or user login ID), but also includes a unique employeeNumber and uid (or user ID for another system perhaps). And I want to maintain these in my AD schema.
First, write a component that adds / updates / deletes these properties based on AD account maintenance. If you are already writing code that does basic user maintenance in AD, then you know how to read / write these additional properties.
OPEN QUERIES
Now, I have a database (forgive the reference) of users in AD that I want to query based on their employeeNumber and / or uid property settings.
The following assumptions are made:
- You have an application that will populate the search variables needed.
- The search variables included here are mEmpNumber and mUID.
- Data from this example is written to a listbox control mEmpsListBox
Now, the fun stuff…
First, simply retrieve just the employeeNumber / uid I’m looking for…
private void PopulateEmployee()
{
// Creates a new DE based on the current default context…
// This may be different than what you need, and you would thus
// modify the = new DirectoryEntry() to include the LDAP path
// and relative data for the context you want to bind to…
DirectoryEntry root = new DirectoryEntry();
DirectorySearcher searcher = new DirectorySearcher(root);
searcher.Filter = “(employeeNumber=”+mEmpNumber.ToString()+”;uid=”+mUID.ToString()+”)”;
// Generally, we are going to find only one instance of the data we’re looking
// for, but the following code can be used for multiple data…
foreach (SearchResult searchResults in Searcher.FindAll())
{
mEmpsListBox.Add(searchResults.Properties[“employeeNumber”].Value.ToString()+” – “+searchResults.Properties[“uid”].Value.ToString());
}
}
The above example performs a simple LDAP query for the employeeNumber and uid properties in the AD context referenced. We did not create a context based on a specific user account (CN=), because we wanted to search based on the employee number or unique UserID in this example – so we queried only those properties in the context referenced.
Also, this example searches for BOTH of the properties – and will fail if both are not found together. What if we wanted a group of employees whose number may begin with a specific range? For example, we have employee numbers unique for Accounting vs. Development, and Accounting could start with something like ACCT<number> and Development with something like DEV<number>. So, we want to return all developers only.
private void PopulateEmployees()
{
// Bind to our default context…
DirectoryEntry root = new DirectoryEntry();
DirectorySearcher searcher = new DirectorySearcher(root);
searcher.Filter = “(employeeNumber=DEV*”)”;
// For each item returned, populate our listbox…
foreach (SearchResult searchResults in Searcher.FindAll())
{
mEmpsListBox.Add(searchResults.Properties[“employeeNumber”].Value.ToString());
}
}
Pretty neat, eh? See how we can use the wildcard reference in our search? This snipet returns all AD objects that have an employeeNumber that begins with DEV – regardless of the actual number.
Now, what if we want to query on all developers, and we’re a large company with several thousand developers? We’re going to run into some inherent problems with the System.DirectoryServices DirectorySearcher class object. Inherently, data returned from AD is paged and the page size in memory is fixed!!!! (I didn’t say that!!!) Yea, believe it or not, out of the box, the number of records returned is limited by this paging setup – and out of the box – you will only get the first 1,000 employees!!!! So, what to do? Let’s modify our code and stipulate only a few properties per record be returned. This 1) tells the class to handle more pages and data per page, and 2) cuts down on the amount of data returned for each employee (or AD record reference).
private void PopulateEmployees()
{
// Bind to our default context…
DirectoryEntry root = new DirectoryEntry();
DirectorySearcher searcher = new DirectorySearcher(root);
searcher.Filter = “(employeeNumber=DEV*”)”;
// Narrow the number of properties and subsequent data to return…
searcher.PropertiesToLoad.Add(“sn”);
searcher.PropertiesToLoad.Add(“givenName”);
searcher.PropertiesToLoad.Add(“uid”);
searcher.PropertiesToLoad.Add(“telephoneNumber”);
searcher.PropertiesToLoad.Add(“employeeNumber”);
// Change our PageSize – NOTE: This is largely affected by the amount of memory in your server…
searcher.PageSize = 8;
searcher.SizeLimit = 8000; // With ample RAM (4GB+) this’ll give us 10’s of thousands of returns…
// For each item returned, populate our listbox…
foreach (SearchResult searchResults in Searcher.FindAll())
{
mEmpsListBox.Add(searchResults.Properties[“employeeNumber”].Value.ToString()+” – “+searchResults.Properties[“givenName”].Value.ToString()+” “+searchResults.Properties[“sn”].Value.ToString+” | “+searchResults.Properties[“telephoneNumber”].Value.ToString());
}
}
I was not going to get heavily into the DirectorySearcher class until Part 3 of my work on Directory Services development – but I wanted to take time to respond to this request, and I hope this snipet is useful. I realize that some of this is rarely documented stuff – particularly the paging issue with the DirectorySearcher. I will try to address this more thoroughly in my upcoming Parts on Directory Services Programming.
Finally, please forgive any typos (in case you copied/pasted and there was one).