Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Querying Microsoft Active Directory Using Microsoft .NET Framework Library

0.00/5 (No votes)
13 Feb 2005 152  
Describes several useful patterns of querying Microsoft Active Directory using standard .NET Framework methods. Some features discussed and some tips suggested. Contains a number of C# code examples with comments.

Introduction

In addition to Microsoft SQL Server as a data store, most of the modern infrastructure solutions for enterprises also include Microsoft Active Directory. In many cases, AD can serve as a very useful source of users� information. To get this information, developers have to query Microsoft Active Directory. For example: get a list of users, get a list of users of the particular group, get a particular user information such as first or last name, and so on. Microsoft .NET Framework provides a standard library for working with Active Directory: System.DirectoryServices namespace in the System.DirectoryServices.dll. Unfortunately, there is only a bit of documentation and there are not a lot of examples provided. Developers could spend a lot of time for researching errors especially if they�re not very experienced in AD. But luckily, there are some very useful patterns that developers can use and even customize. Here, you can find several C# code examples that demonstrate how to query AD in an efficient manner. Hope this article will be helpful.

Getting Started

To start querying Active Directory from your C# code, you simply add a reference to the System.DirectoryServices.dll in your project and the following using statement to your code:

using System.DirectoryServices;

According to examples provided in the Visual Studio.NET Help, Microsoft recommends using two main classes from the System.DirectoryServices namespace: DirectoryEntry and DirectorySearcher. In most cases, it is enough to use DirectorySearcher class only. Because I am going to talk about querying AD only, not managing AD, I won�t provide detailed explanation of using DirectoryEntry class. But later on, I will show and comment an example of DirectoryEntry usage. In addition, I assume that Active Directory is always up and running in your environment. One more thing I want to mention before we start is that I will separate all AD queries by two large groups: user-oriented queries and common queries. The difference between these types of queries we�ll figure out later. Let�s get started.

User Oriented Queries

User Name

First of all, I have to tell that a user-oriented query always uses a user name as a parameter. Let�s figure out what the user name is. This is the name part of a login name, which users enter while logging in Windows. This name part follows the �\� symbol that separates the domain name and the user name. It�s a good practice to extract the user name from the login name before using it in the AD query. We can do it easily:

string ExtractUserName(string path)
{
    string [] userPath = path.Split(new char [] {'\\'});
    return userPath[userPath.Length-1];
}

First Query

Now we know the user name, and it�s good to know if this user exists in the AD. Let�s do it like:

bool IsExistInAD(string loginName)
{
    string userName = ExtractUserName(loginName);
    DirectorySearcher search = new DirectorySearcher(); 
    search.Filter = String.Format("(SAMAccountName={0})", userName);
    search.PropertiesToLoad.Add("cn");
    SearchResult result = search.FindOne();

    if (result == null)
    {
        return false;
    }
    else
    {
        return true;
    }
}

This is our first AD query, so let�s see what we are doing here. First of all, I extract the user name from the login name that we received from Windows, using the ExtractUserName method. Then I create a new DirectorySearcher object. DirectorySearcher class provides a lot of constructors, but I use a default constructor on purpose in order to show how we will initialize the DirectorySearcher before starting a query.

Actually, the query itself is located in the Filter property of the DirectorySearcher object. I have to initialize it before starting the query. Here we come to the Active Directory territory. To create a Filter string, you have to be familiar with the AD object structure. But in this article, I won�t talk about AD itself, so simply take a look at the code above and learn that the filter keyword "SAMAccountName" allows us to select an AD object with the given name equal to the login name.

Now I have to assign a PropertiesToLoad property of the DirectorySearcher object. This is a collection containing attribute names of AD objects that we want the query to return. By analogy with SQL query, the Filter property serves as the WHERE clause and the PropertiesToLoad property works as a list of column names that the query will return. The "cn" keyword means a common name of the AD object, and in this query, we are not interested in any other properties to return. Basically, this is a good practice to limit the amount of returning properties as much as you can. It can reduce execution time of the query significantly.

After all, we are ready to run a query. I simply call a FindOne() method of the DirectorySearcher object that returns a special SearchResult object containing a searching result. If the result object is null, that means there is no information in the AD corresponding to our query. In that particular case, it means a user with the given name is not found. Otherwise, we assume that the user exists in the AD. In order to make sure that the user does exist, we can check out the result object. As we remember, the result object should contain the "cn" property. We can test it out like:

 lang=csstring cn = (string)result.Properties["cn"][0];

The result object contains a special property named Properties that returns a typed collection containing the values of properties of the object found in the AD. We can also use this method for retrieving additional information about the user from AD. To do this, we only need to add additional PropertiesToLoad arguments before searching and retrieving them after searching. The following code shows that:

    search.PropertiesToLoad.Add("samaccountname");
    search.PropertiesToLoad.Add("givenname");
    search.PropertiesToLoad.Add("sn");
    SearchResult result = search.FindOne();
    ...
    string samaccountname = (string)result.Properties[�samaccountname�][0];
    string givenname = (string)result.Properties[�givenname�][0];
    string surname = (string)result.Properties[�sn�][0];

The example above contains the names of the most widely used properties. You can for sure retrieve any possible property from AD simply using its name.

That�s it actually. Now I am going to show you several more queries, but you already know how to query AD.

User�s Groups

Let�s find out what groups our user belongs to. See the code:

string GetADUserGroups(string userName) {
    DirectorySearcher search = new DirectorySearcher();
    search.Filter = String.Format("(cn={0})", userName);
    search.PropertiesToLoad.Add("memberOf");
    StringBuilder groupsList = new StringBuilder();
        
    SearchResult result = search.FindOne();
    if (result != null)
    {
        int groupCount = result.Properties["memberOf"].Count;
                        
        for(int counter = 0; counter < groupCount; counter++)
        {
            groupsList.Append((string)result.Properties["memberOf"][counter]);
            groupsList.Append("|");
        }
    }
    groupsList.Length -= 1; //remove the last '|' symbol

    return groupsList.ToString();
}

We can notice that the Filter property and PropertiesToLoad property are what makes the difference from the previous query. And also in this query, I assume there are several outputs in the result object. This method returns a list of the AD groups the user belongs to as a string containing '|'-symbol separated group names. Actually, group names in that list appear in a relatively unusual format, and you can examine this format by yourself.

Common Queries

Common queries allow us to retrieve much more information from AD. This type of queries are potentially dangerous because of potentially huge amount of information that might be received back from AD. So you have to be careful while experimenting with such queries.

Group�s Users

Let�s get a list of users belonging to a particular AD group. The code below shows how to do this:

ArrayList GetADGroupUsers(string groupName)
{    SearchResult result;
    DirectorySearcher search = new DirectorySearcher();
    search.Filter = String.Format("(cn={0})", groupName);
    search.PropertiesToLoad.Add("member");
    result = search.FindOne();

    ArrayList userNames = new ArrayList();
    if (result != null)
    {
        for (int counter = 0; counter < 
                 result.Properties["member"].Count; counter++)
        {
            string user = (string)result.Properties["member"][counter];
            userNames.Add(user);
        }
    }
    return userNames;
}

Here I use the same search filter as for user search, but different set of PropertiesToLoad. So the query returns to us an object with the common name equal to the group name and a properties list containing a list of other objects that are a "member" of the group object obtained. Then I iterate through the list of the "member" objects and add them to the ArrayList being returned. I encourage you to investigate a format of the ArrayList items in order to figure out what actually you have back from AD. You�ll see it�s not a simple user name.

All Users

The last query I want to discuss in this article is meant to return a list of all the AD domain users. This is an extremely risky operation because of a large amount of users in the AD domain and possible incorrect implementation of AD. I�d like to explain the second sentence. This query uses a special search filter that looks for every object with a specific property that should mean this object is a user object. But sometimes administrators design AD structure even without this property or enter new records to AD not setting this property. So it�s very possible to get a list of all objects in AD instead of domain user objects only. But it�s enough of bad things, let�s get to the code.

ArrayList GetAllADDomainUsers(string domainpath)
{
    ArrayList allUsers = new ArrayList();

    DirectoryEntry searchRoot = new DirectoryEntry(domainpath);
    DirectorySearcher search = new DirectorySearcher(searchRoot);
    search.Filter = "(&(objectClass=user)(objectCategory=person))";
    search.PropertiesToLoad.Add("samaccountname");

    SearchResult result;
    SearchResultCollection resultCol = search.FindAll();
    if (resultCol != null)
    {
        for(int counter=0; counter < resultCol.Count; counter++)
        {
            result = resultCol[counter];
            if (result.Properties.Contains("samaccountname"))
            {
                allUsers.Add((String)result.Properties["samaccountname"][0]);
            }
        }
    }
    return allUsers;
}

If we go through the code above, we can see some differences from the previous examples. First, I use a DirectoryEntry object. I do it to connect to a specific context of AD and limit my search by this context. In my case, I suppose to use a single domain context and thus I have to provide a path to the domain I�m interested in. I have to use a specific form of this path in terms of LDAP language. It could look like the following:

"LDAP://DC=<domain>"

Here <domain> is the name of a particular domain. Another example is using a current domain. To find out the name of the current domain, I can get it from the user login name (remember ExtractUserName method?), or I can determine it programmatically like the following:

DirectoryEntry entryRoot = new DirectoryEntry("LDAP://RootDSE");
string domain = entryRoot.Properties["defaultNamingContext"][0];
DirectoryEntry entryDomain = new DirectoryEntry("LDAP://" + domain);

This is an example of using a DirectoryEntry class.

Well, let�s get back to the code. The next thing you may want to pay attention is a Filter string. It contains no parameters, but instead it consists of an AD specific pattern. (If you have no idea what that pattern means, probably it�s a good idea to get familiar with AD some day ;-)) And the last difference is the use of FindAll() method instead of the FindOne().

Best Practice

In the examples above, I was using a simplified code for demonstration purpose only. But the real code could be much more complex. Especially in the part of PropertiesToLoad and parsing of search outcome. To help you work with AD, I�d like to share some useful tips that have been practically proven.

  1. Always limit the amount of information returned by DirectorySearcher object by setting its public property PropertiesToLoad.
  2. Querying Active Directory is a very time-consuming process. Besides, information in AD always doesn�t change frequently. So the good practice is to cache information received from AD as much as possible.
  3. Results returned by DirectorySearcher object depend on the AD implementation very much. So even if you expect some object properties to be returned, always check them for null before using.
  4. Querying AD is a network operation. That means you never know if there are some problems with network unless they happened. As a developer, you should assume the worst scenario and always place your AD-querying code in the try-catch block.
  5. And the last one. Usually, you don�t know how much information AD contains. So keep off using very common queries that potentially can return a very large and even huge amount of information as a result. There are two main risks in such queries: first, the query can last for a very long time like tens of minutes and even hours; and second, the size of the result returned can exceed the memory size and crush your program.

Conclusion

This article is aimed to illustrate the opportunities of working with Microsoft Active Directory provided by the .NET Framework library. Here I have shortly described a common methodology of building AD queries using the .NET Framework standard features. This methodology was illustrated by a number of code examples that can be used practically. Also, some practically proven tips are provided. Using the approach described, developers can create their own AD queries and extend the ones suggested.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here