Active Directory With C#

If you work in the kind of large institution that I do and are using Microsoft Active Directory then the chances are that at certain times you will need to perform actions on the directory that are outside the scope of the MSAD tools. This could be things like specialised queries, bulk account creation or mass updates of user information. The MSAD tools and even some of the command line tools are quite limiting and difficult to use in this regard.

Whatever the reason, you may find that at some point you need to either purchase additional software for managing AD or write your own. Obviously I’d rather write my own software as it’s cheaper, more rewarding and you can customise it however you like!

I found that when I was trying to learn how to make C# work nicely with AD there were a lack of simple tutorials to get me started, although I did find a few useful blog posts. Often any examples that I found did much more in the program than I was after, so it was difficult to pick out the few lines that I was actually interested in.

So, this page contains a few basic but fully working programs which illustrate common scenarios that you may have. If you can read and understand these examples you should be able to apply the principles to much larger and very powerful programs as I have done.

Obviously you need to be careful with this kind of programming and where ever possible you shouldn’t be testing on a live environment. Queries are safe enough but when you get on to account creation and modification the potential to royally muck up a lot of account very quickly is a real danger, so take care!

Adjusting the Filter

In all of the examples where the program asks for a username the program then matches this to the field cn, which is what the AD GUI refers to as ‘Full Name’ and is what is listed as ‘name’ in the tabulated account lising of Active Directory Users and Computers.

You could change the username to something else by adjusting the filter. For example if you wanted to enter a user logon name (called samaccountname in the schema), you could set the filter as follows:

search.Filter = "(samaccountname=" + username + ")";

The createDirectoryEntry Function

All of these examples contain the same function called createDirectoryEntry, located at the bottom of the program. In order to try out the examples you will need to edit this function and enter both a hostname for your own AD server and also an appropriate search path. I have left in as examples the paths that I used when creating the programs.

If you are logged into a system as a domain administrator or a user with appropriate privilages then you should not need to specify a username and password for the connection.

However, if you are running the program as an unprivilaged user then you will need to add (or prompt for and program accordingly) a username and password to the DirectoryEntry object. The function is overloaded several times so you can just append as follows:

DirectoryEntry ldapConnection = new DirectoryEntry("server", "username", "password");

Example one : Retrieving All Information From a User’s Record

This first example will introduce you to the classes needed for querying the AD using C#. I will explain this example fully as this will give a good understanding of the other examples also, once you grasp the major principles involved.

What we are going to do first is retrieve a full LDAP entry for a particular user. This isn’t something that you would want to do very often as it isn’t at all selective and would be overkill when querying a lot of users.

This is useful however if you need to find out what a particular field in the Active Directory is called.

For example, in the AD GUI we can set a ‘PO Box’ as part of the address (in College we use this for pigeon hole numbers). When you wish to query this information in your C# program the field is actually called postofficebox.

ad post box

There is no tool that I know of which shows the correlation between the fields in the GUI and what the fields are called in the schema, so it has been necessary for me several times during development to set one of the fields to ‘foo’ and then run a full query looking for ‘foo’ in order to reveal the correct field.

The example is not too hard to understand, however there are several different classes used in order to accomplish the task. First we create a DirectoryEntry object. As you will have guessed from the section above regarding your setting, this class will contain all of the information which describes the server we are trying to connect to such as address, username and so on.

We then create a DirectorySearcher object. This class describes a search and operates against the DirectoryEntry object, so it knows where to search, and has it’s own properties such as its Filter so it knows what to search for.

We then use the class SearchResult against the DirectorySearcher object, which represents an LDAP entry. This object has a number of Properties (such as user name, e-mail address) and a number of generic objects associated with each property:

SearchResult result
Properties Objects
cn Ian Atkinson
mail santa@clause.ac.uk
memberof
users
staff
domain administrators

The properties have generic objects associated with them as the class has no concept of their content. If you wish you will need to cast or convert to more specific classes in order to perform some operations, for example a telephone extension could be cast to an int.

In many cases there will be a single object associated with each property, for example a user can have only one user logon name (or samaccountname).

However some properties, such as memberof which represents a user’s group membership, will have many objects (one for each group in this case).

The SearchResult object operates like an array, so we can retrieve a particular value such as result.Properties["cn"][0] for the first object associated with the cn property. In the example above result.Properties["memberof"][1] is "staff".

We can also iterate through all of the objects associated with a given property by using the ResultPropertyCollection class, which is what we do in the example below.

NB: this first example is more heavily commented than the rest in order to outline the common parts. In subsequent examples I have removed the comments and only commented the new or relevant parts.

retrieve_all_info.cs:

using System;
using System.Text;
using System.DirectoryServices;

namespace activeDirectoryLdapExamples
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write("Enter user: ");
            String username = Console.ReadLine();

            try
            {
                // create LDAP connection object

                DirectoryEntry myLdapConnection = createDirectoryEntry();

                // create search object which operates on LDAP connection object
                // and set search object to only find the user specified

                DirectorySearcher search = new DirectorySearcher(myLdapConnection);
                search.Filter = "(cn=" + username + ")";

                // create results objects from search object

                SearchResult result = search.FindOne();
                
                if (result != null)
                {
                    // user exists, cycle through LDAP fields (cn, telephonenumber etc.)

                    ResultPropertyCollection fields = result.Properties;

                    foreach (String ldapField in fields.PropertyNames)
                    {
                        // cycle through objects in each field e.g. group membership
                        // (for many fields there will only be one object such as name)

                        foreach (Object myCollection in fields[ldapField]) 
                            Console.WriteLine(String.Format("{0,-20} : {1}", ldapField, myCollection.ToString()));
                               
                    }
                }

                else
                {
                    // user does not exist
                    Console.WriteLine("User not found!");
                }
            }

            catch (Exception e)
            {
                Console.WriteLine("Exception caught:\n\n" + e.ToString());
            }
        }

        static DirectoryEntry createDirectoryEntry()
        {
            // create and return new LDAP connection with desired settings

            DirectoryEntry ldapConnection     = new DirectoryEntry("rizzo.leeds-art.ac.uk");
            ldapConnection.Path               = "LDAP://OU=staffusers,DC=leeds-art,DC=ac,DC=uk";
            ldapConnection.AuthenticationType = AuthenticationTypes.Secure;

            return ldapConnection;
        }
    }
}

Here is an (abbreviated) example of the output:

H:\Desktop\adcsharp>retrieve_all_info

Enter user: Ian Atkinson

distinguishedname    : CN=Ian Atkinson,OU=IT,OU=Business Support,OU=staffusers,DC=leeds-art,DC=ac,DC=uk
cn                   : Ian Atkinson
mailnickname         : iana
displayname          : Ian Atkinson
title                : Senior Infrastructure Support Engineer
samaccountname       : iana
givenname            : Ian
mail                 : santa@clause.ac.uk
sn                   : Atkinson
postofficebox        : J10

<snip>

Example 2 - Retrieving Selected Information From a User’s Record

This example is almost identical to the above example, however we are now selective about which fields from the AD we want to bring in. This is a much more realistic example as it’s obviously bad practise to query more data than is required.

We load certain properties by calling the PropertiesToLoad.Add method on our DirectorySearcher object.

retrieve_some_info.cs:

using System;
using System.Text;
using System.DirectoryServices;

namespace activeDirectoryLdapExamples
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write("Enter user: ");
            String username = Console.ReadLine();

            try
            {
                DirectoryEntry myLdapConnection = createDirectoryEntry();
                DirectorySearcher search = new DirectorySearcher(myLdapConnection);
                search.Filter = "(cn=" + username + ")";

                // create an array of properties that we would like and
                // add them to the search object

                string[] requiredProperties = new string[]{"cn", "postofficebox", "mail"};
                foreach (String property in requiredProperties) search.PropertiesToLoad.Add(property);

                SearchResult result = search.FindOne();
                
                if (result != null)
                {
                    foreach (String property in requiredProperties)
                        foreach (Object myCollection in result.Properties[property]) 
                            Console.WriteLine(String.Format("{0,-20} : {1}", property, myCollection.ToString()));
                }

                else Console.WriteLine("User not found!");
            }

            catch (Exception e)
            {
                Console.WriteLine("Exception caught:\n\n" + e.ToString());
            }
        }

        static DirectoryEntry createDirectoryEntry()
        {
            // create and return new LDAP connection with desired settings

            DirectoryEntry ldapConnection = new DirectoryEntry("rizzo.leeds-art.ac.uk");
            ldapConnection.Path = "LDAP://OU=staffusers,DC=leeds-art,DC=ac,DC=uk";
            ldapConnection.AuthenticationType = AuthenticationTypes.Secure;
            return ldapConnection;
        }
    }
}

Here is an example of the output:

H:\Desktop\adcsharp>retrieve_some_info
Enter user: Ian Atkinson
cn                   : Ian Atkinson
postofficebox        : J10
mail                 : santa@clause.ac.uk

Example 3 - Retrieving Information for All Users

So far we have only retrieved information for a single user. In this example we will retrieve some information for all of the users in our search base.

We can accomplish this simply by using the FindAll rather than the FindOne method on our DirectorySearcher object and then iterating through the results.

all_users.cs:

using System;
using System.Text;
using System.DirectoryServices;

namespace activeDirectoryLdapExamples
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write("Enter property: ");
            String property = Console.ReadLine();

            try
            {
                DirectoryEntry myLdapConnection = createDirectoryEntry();

                DirectorySearcher search = new DirectorySearcher(myLdapConnection);
                search.PropertiesToLoad.Add("cn");
                search.PropertiesToLoad.Add(property);

                SearchResultCollection allUsers = search.FindAll();

                foreach(SearchResult result in allUsers)
                {
                    if (result.Properties["cn"].Count > 0 && result.Properties[property].Count > 0)
                    {
                        Console.WriteLine(String.Format("{0,-20} : {1}",
                                          result.Properties["cn"][0].ToString(),
                                          result.Properties[property][0].ToString()));
                    }
                }  
            }

            catch (Exception e)
            {
                Console.WriteLine("Exception caught:\n\n" + e.ToString());
            }
        }

        static DirectoryEntry createDirectoryEntry()
        {
            // create and return new LDAP connection with desired settings

            DirectoryEntry ldapConnection = new DirectoryEntry("rizzo.leeds-art.ac.uk");
            ldapConnection.Path = "LDAP://OU=staffusers,DC=leeds-art,DC=ac,DC=uk";
            ldapConnection.AuthenticationType = AuthenticationTypes.Secure;
            return ldapConnection;
        }
    }
}

Here is an example of the output:

H:\Desktop\adcsharp>all_users
Enter property: mail

Ian Atkinson         : santa@clause.ac.uk
Rudolph              : rudolph@clause.ac.uk
Elf                  : elf@clause.ac.uk

Example 4 - Updating a User

Having covered querying the AD we will now move on to updating the AD! This is much simpler than you might imagine as the search results that we have already found really represent actual objects on the server, so we can easily edit the properties of the result and then write this information back to the AD.

We do this by creating a DirectoryEntry object from the search result (using the GetDirectoryEntry method) and then setting the Value for any property that we would like to change. When we are finished we use the CommitChanges method to actually write the changes.

In this small example we retrieve a user’s job title (title in the schema) and then change it for a new one.

update_user.cs:

using System;
using System.Text;
using System.DirectoryServices;

namespace activeDirectoryLdapExamples
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write("Enter user      : ");
            String username = Console.ReadLine();

            try
            {
                DirectoryEntry myLdapConnection = createDirectoryEntry();

                DirectorySearcher search = new DirectorySearcher(myLdapConnection);
                search.Filter = "(cn=" + username + ")";
                search.PropertiesToLoad.Add("title");

                SearchResult result = search.FindOne();

                if (result != null)
                {
                    // create new object from search result

                    DirectoryEntry entryToUpdate = result.GetDirectoryEntry();

                    // show existing title

                    Console.WriteLine("Current title   : " + entryToUpdate.Properties["title"][0].ToString());
                    Console.Write("\n\nEnter new title : ");

                    // get new title and write to AD

                    String newTitle = Console.ReadLine();

                    entryToUpdate.Properties["title"].Value = newTitle;
                    entryToUpdate.CommitChanges();

                    Console.WriteLine("\n\n...new title saved");
                }

                else Console.WriteLine("User not found!");
            }

            catch (Exception e)
            {
                Console.WriteLine("Exception caught:\n\n" + e.ToString());
            }
        }

        static DirectoryEntry createDirectoryEntry()
        {
            // create and return new LDAP connection with desired settings

            DirectoryEntry ldapConnection = new DirectoryEntry("rizzo.leeds-art.ac.uk");
            ldapConnection.Path = "LDAP://OU=staffusers,DC=leeds-art,DC=ac,DC=uk";
            ldapConnection.AuthenticationType = AuthenticationTypes.Secure;
            return ldapConnection;
        }
    }
}

Here is an (abbreviated) example of the output. Note how when the program is run for the second time the title that is retrieved is the one entered the first time around:

H:\Desktop\adcsharp>update_user
Enter user      : Ian Atkinson
Current title   : Senior Infrastructure Support Engineer


Enter new title : Dogsbody


...new title saved

H:\Desktop\adcsharp>update_user
Enter user      : Ian Atkinson
Current title   : Dogsbody


Enter new title : Senior Infrastructure Support Engineer


...new title saved