.NET Active Directory Wrapper    
Access Active Directory From Your C# or VB .Net Code
 

Access LDAP Active Directory functions from .NET Framework

Introduction

In today’s networked computing environment it’s important to control access of the users to the available resources on the network.

The System.DirectoryServices namespace of .NET framework is required to manage the resources which the network offers like printers, files, users, applications and so forth. Microsoft provides Active Directory Services interface (ADSI) that works along with the System.DirectoryServices’ classes to perform this functionality. ADSI can interact with any of the following Directory Service providers:

  • WinNT
  • Internet Information Services (IIS)
  • Lightweight Directory Access Protocol (LDAP)
  • Novell NetWare Directory Service (NDS)

LDAP - Introduction

LDAP stands for Lightweight Directory Access Protocol. It is an application protocol that is used to locate and manage the resources in a network. It actually enables querying and modifying directory services that run over TCP/IP. Before the advent of LDAP, X.500 directory services were accessed via X.500 DAP i.e. Directory Access Protocol. DAP required the OSI (Open Systems Interconnection) protocol stack. LDAP was a replacement for DAP which could access the directory services via a simpler protocol stack TCP/IP. It is to note here that TCP/IP is necessary for internet access. Thus using LDAP one can search any directory information on the internet. Another advantage of using LDAP is that it is platform independent and standard based, so the applications that access directory services through LDAP are agnostic to the type of server that would be hosting the directory.

Active Directory - Introduction

‘Directory’ is nothing but a location where the information is stored in a systematic and organized manner.
Active Directory sounds similar to a telephone directory. Actually it is, as you can track any name in a telephone directory similarly you can track any object using an Active Directory.

Active Directory is a tool that allows any object on a network to be located and managed.. It organizes information about users, printers and any other local network resource on the distributed network. It provides central authentication and authorization services, i.e. it provides a means of centrally organizing, managing, and controlling access to the resources. It is a hierarchical database that contains all of your network resources and enables one to administer and control the access rights to these resources. Since AD has a hierarchical structure, it means that if you grant some access to a resource at a higher level (say at a class level), the objects (say methods in that class) contained in that higher level would automatically be accessible by that resource. It lets you integrate the whole internet together. This can be accomplished by building applications that would give a single point of access to directories in a network environment using ADSI i.e. Active Directory Service Interfaces.

Implementation to Access Active Directory

There are three main interfaces that are used to access the Active Directory:

LDAP: The Lightweight Directory Access Protocol (LDAP) is the software protocol that runs on TCP/IP and enables tracking, accessing and modifying Internet Directories.

ADSI: The Active Directory Services Interface (ADSI) is an Interface to accomplish the task.

System.DirectoryServices: It is built on ADSI API and .NET Framework provides this namespace to provide programming access to Active Directory.

ADSI

ADSI is a collection of various methods that provide an interface to the various directories. It provides the ability to track and control the resources on the network. Thus using ADSI you can create applications that can manage all the resources (user, printers etc) on the network. ADSI supports NT Directory, Active Directory, Novell Bindery, Novell NDS, IIS and other LDAP based directories.
There are different ways to access the Active Directory procedures using ADSI from.NET framework.

  • Using COM. For this you have to include the Active Domain Services (DS) component into your application.
  • Using ADsDSOObject i.e. the Active Directory Services OleDb provider.
  • Using System.DirectoryServices

Before we get into actual implementation of ADSI in our application, it’s important to know about System.DirectoryServices namespace and LDAP.

System.DirectoryServices

SystemDirectoryServices is an API that lets you access Active Directory from your .NET application. This namespace contains two main classes DirectoryEntry and DirectorySearcher. These classes can operate with any of the service providers WinNT, LDAP, NDS and IIS.

A DirectoryEntry class represents an object of a resource on the network. It is used to manage these resources or reading their values (properties).

While the DirectorySearcher class, is used to search in the Active Directory. Note that only LDAP provider support searching. It contains several properties for various search options you would require to perform a LDAP query. A few important properties are listed below.

-Filter: Gets or sets the search filter string. The syntax of a filter string is:

(<logicaloperator><comparison><comparison...>)

Logical operator can be AND (&), OR(|) or NOT(!).
In this Example, the filter string finds all the objects with ‘Empname’ as ‘williams’ and ‘Department’ as ‘IT’ or ‘HR’.

obj.filter(&(EmpName=Williams)(|(Department=IT)(Department=HR)));

Here, ‘obj’ is an object of DirectorySearcher class.

-PropertiesToLoad: Gets or sets the attributes to return from a search.

Example, the below given lines of code retrieve ‘Empname’ and ‘Department’ from Active Directory. If required, any number of attributes can be added using similar syntax.

obj.PropertiesToLoad.Add(EmpName);
obj.PropertiesToLoad.Add(Department);

-SearchRoot: Gets or sets the base from which the search should start. In other words the node in the Active Directory hierarchy where the search should start from is declared using this property. The SearchRoot property accepts a DirectoryEntry object representing the search base.

Example:

obj.SearchRoot = New DirectoryEntry("LDAP://dc=mysite,dc=com")

-SearchScope: Gets or sets the scope of the search. SearchScope is an Enumeration with the following members:

  • Base: It limits the search to the base object. Therefore the result can be a maximum of one object.
  • OneLevel: It lets the search to widen to the immediate child objects of the base object, but not the base object.
  • Subtree: It further widens the spectrum of search, with the whole subtree including the base object and all its child objects. It is the default SearchScope value.

Example:

obj.SearchScope = SearchScope.Subtree;

.Net Active Directory Wrapper Lite - Sample Application

.Net Active Directory Wrapper Lite is free package software that is capable of performing the following functions:

  • Creates a new user.
  • Check whether the user exists in the Active Directory or not.
  • Enables the account of the Active Directory user provided.
  • Disablesthe account of the Active Directory user provided.
  • Changes the password of the given user with a new password.
  • Adds a given user to a given user group.

It consists of two Visual Studio Solutions (.sln). The first Visual Studio Solution is a Class Library application. The output binary files contain one dll that facilitates calling of Active Directory functions from your code. Please see below for sample usage.
The second Visual Studio Solution is a Windows Application. This application uses the output dll that is generated by the Class Library application and demonstrates how the component should be used.

Configuration of the component is easy. Four parameters need to be set in code: The Active Directory domain that needs to be accessed, the username of the account that has permissions to execute Active Directory functions and the password for that account and finally the IP address of the domain controller.

The working of this application can be discussed under the following sections:

Creating a Setup

The main setup is established through our dll, the class that contains all the setup members and functions is DotnetAD. As already talked about, in order to communicate with Active Directory the following connection information should be provided:

  • Domain Name
  • Domain Computer Name/IP
  • Administrator Username
  • Administrator Password

And we handle this information as data members of class DotnetAD. They are:

  • DotnetAD.ADAdminUser to store administrator name.
  • DotnetAD.ADAdminPassword to store password
  • DotnetAD.ADFullPath to store "LDAP://" + "domain.com".
  • DotnetAD.ADServer to store the IP address.

These members are assigned the values through the second Visual Studio application which is a Windows application. It has a class called “mainform”. It is to note that we have added the reference to the dll, and imported the namespace in our windows application as:

using Inclive;

The buttons on this form have their respective event handlers which would call on to the functions in the dll and perform the basic functionalities of creating a user, enabling, disabling etc. The code handling these functionalities are discussed below.

1. Adding a User

The function CmdCreate_Click, in the “mainform” class is the handler that handles this functionality of creating a new user. It assigns the value to the data members of the dll which is the basic information required for communication.

DotnetAD.ADAdminUser = this.TxtAdminUser.Text;
DotnetAD.ADAdminPassword = this.TxtAdminPassword.Text;
DotnetAD.ADFullPath = "LDAP://" + this.TxtDomainController.Text;
DotnetAD.ADServer = this.TxtDomain.Text;

The right hand side in the above lines of code represents the text entered in the text boxes of our interface. The function checks that all first 4 textboxes and the user name are entered.

DirectoryEntry user = DotnetAD.CreateNewUser(this.TxtUsername.Text);

It calls the function CreateNewUser() defined in the class DotnetAD of dll. This function takes the username as the parameter. A DirectoryEntry class represents an object of a resource on the network and therefore a user (which is a kind of resource) is returned through this function.

 

public static DirectoryEntry  CreateNewUser(string cn)
{
            string LDAPDomain ="/CN=Users," + GetLDAPDomain();
            DirectoryEntry oDE= GetDirectoryObject(LDAPDomain);
            DirectoryEntry user = oDE.Children.Add("CN=" + cn, "user");
            user.CommitChanges();

            oDE.Close();
            oDE.Dispose();
            return user;

}

The string “LDAPDomain” sets the qualification so that the user will be created under the ‘Users’ resource of the network. Here, CN stands for container. The “GetLDAPDomain()” method reads the ADServer value and return it as an LDAP path i.e. DC=dotnetad, DC=net (as we have entered in the txtDomain text box). This is required when creating DirectoryEntry other than the root.
Note that ADserver is a data member of DotnetAd class.

private static string GetLDAPDomain()
{
            StringBuilder LDAPDomain = new StringBuilder();
            string[] LDAPDC =ADServer.Split('.');
            for(int i=0;i < LDAPDC.GetUpperBound(0)+1;i++)
            {
                  LDAPDomain.Append("DC="+LDAPDC[i]);
                  if(i <LDAPDC.GetUpperBound(0))
                  {
                        LDAPDomain.Append(",");
                  }
            }
            return LDAPDomain.ToString();
      }

To add an object, you must first get a reference to the parent object i.e. oDE, as defined in the code below and then add a child. Here a DirectoryEntry constructor is being used to get the reference to the parent DirectoryEntry object. The following method does this functionality.

private static DirectoryEntry GetDirectoryObject(string DomainReference)
{
            DirectoryEntry oDE;
      oDE = new DirectoryEntry(ADFullPath +     DomainReference,ADAdminUser,ADAdminPassword,AuthenticationTypes.Secure);
            return oDE;
}
And then you can add a child by using the Children.Add method of a DirectoryEntry object. Finally you must commit changes to create a user.

2. Checking if User exists

The function CmdExists_Click, in the “mainform” class checks if a user exists in the Active Directory or not. It calls Userexists() method defined in DotnetAd class of the dll as:

DotnetAD.UserExists(this.TxtUsername.Text)
It returns true if the user exists and false if does’nt exists in Active Directory. The code performing this functionality is given below:

public static bool UserExists(string UserName)
{
            DirectoryEntry de = GetDirectoryObject("/" + GetLDAPDomain());
            DirectorySearcher deSearch = new DirectorySearcher(de);
            deSearch.SearchRoot =de;
            deSearch.Filter = "(&(objectCategory=user)(cn=" + UserName +"))";
           SearchResultCollection results= deSearch.FindAll();
            if(results.Count ==0)
            {
                  return false;
            }
            else
            {
                  return true;
            }
}

This method will not actually log a user in, but will perform tests to ensure that the user account exists (matched by only username). The SearchRoot accepts a DirectoryEntry object representing the search base, the Filter property is the LDAP filter string. The FindAll method is the one that invoked the search and returns an object of SearchresultConnection type. If the count of this object is not 0, it implies that there are results which match the user name passed and thus the user exists.

3. Adding a User to a group

The function CmdGroup_Click in the “mainform” adds a user to a group. It calls the AddUserToGroup() method of the DotnetAD class as:
DotnetAD.AddUserToGroup(this.TxtUsername.Text, this.TxtUserGroup.Text);
It takes two strings as parameters, one the username, and second is the group name to which the username would be added. A group could be like of administrator, or some other that you have in your active directory. 
public static void AddUserToGroup(string UserName, string GroupName)
{
      DirectoryEntry rootEntry = GetDirectoryObject("/" + GetLDAPDomain());
      DirectorySearcher Mysearcher = new DirectorySearcher(rootEntry);
      Mysearcher.SearchRoot = rootEntry;
      Mysearcher.Filter = "(&(objectCategory=group)(CN=" + GroupName + "))";
      SearchResult result = Mysearcher.FindOne();
      DirectoryEntry g = result.GetDirectoryEntry();
      Mysearcher.Filter = "(&(objectCategory=user)(CN=" + UserName + "))";
      result = Mysearcher.FindOne();
      DirectoryEntry user = result.GetDirectoryEntry();
      g.Invoke("Add",new Object[]{user.Path});
      g.CommitChanges();
                 
      g.Close();
      g.Dispose();
                 
      user.Close();
      user.Dispose();
                 
      Mysearcher.Dispose();
      rootEntry.Close();
      rootEntry.Dispose();

}

In the above code we are calling GetDirectoryObject(), to get a reference to the parent DirectoryEntry object. And then finding a DirectoryEntry object ‘g’ based on the group we have entered. We have used the DirectorySearcher class here to accomplish this task of querying. The DirectoryEntry object ‘user’ is searched from Active Directory based on the username entered in the text box. Finally the user is added to the group via the ‘invoke’ method. This method can also be used to remove a user from the group. This can be done by simply replacing the ‘Add’, with ‘Remove’ in “g.Invoke("Add",new Object[]{user.Path});”.

4. Enabling an Active Directory user

The CmdEnable_Click function in the “mainform” class calls the EnableUserAccount method as:

DotnetAD.EnableUserAccount(this.TxtUsername.Text);

public static void EnableUserAccount(string UserName)
{
      EnableUserAccount(GetUser(UserName));
}

The GetUser() method works using the Directorysearch and returns a DirectoryEntry object based on the user name passed as the parameter. Once you have queried the Active Directory using the DirectorySearch object, and found the result in a SerachResult object, add the following line of code to get the corresponding DirectoryEntry object.

return results.GetDirectoryEntry();

This returned a DirectoryEntry object and it is passed to the below given function which enables a user account.

public static void EnableUserAccount(DirectoryEntry oDE)
{
 object ent = oDE.NativeObject;
 Type type = ent.GetType();
 type.InvokeMember("AccountDisabled", BindingFlags.SetProperty,null, ent,
 new object[] { false });
 oDE.CommitChanges();
 oDE.Close();
 oDE.Dispose();
}

The user is enabled by setting the AccountDisabled property to false using InvokeMember function. We can also enable the account by resetting all the account options excluding the disable flag. The CommitChanges method is to save the changes made.

5. Disabling an Active Directory user

The CmdDisable_Click function in the “mainform” class calls the DisableUserAccount method as:

DotnetAD.DisableUserAccount(this.TxtUsername.Text);

This method is as shown below:

public static void DisableUserAccount(string UserName)
{
      DisableUserAccount(GetUser(UserName));
}

Similar to the GetUser() method called in EnableUserAccount, it is called here.

public static void DisableUserAccount(DirectoryEntry oDE)
{
      oDE.Properties["userAccountControl"][0]=DotnetAD.ADAccountOptions.UF_NORMAL_ACCOUNT| DotnetAD.ADAccountOptions.UF_DONT_EXPIRE_PASSWD|DotnetAD.ADAccountOptions.UF_ACCOUNTDISABLE;
oDE.CommitChanges();
oDE.Close();
}

The account is disabled by resetting all the default properties. Here ADAccountsOptions is an enumeration with values as shown below. These are the user flags that are used to set the properties.
public enum ADAccountOptions
{
UF_TEMP_DUPLICATE_ACCOUNT = 0x0100,
UF_NORMAL_ACCOUNT =0x0200,
UF_INTERDOMAIN_TRUST_ACCOUNT =0x0800,
UF_WORKSTATION_TRUST_ACCOUNT = 0x1000,
UF_SERVER_TRUST_ACCOUNT =0x2000,
UF_DONT_EXPIRE_PASSWD=0x10000,
UF_SCRIPT =0x0001,
UF_ACCOUNTDISABLE=0x0002,
UF_HOMEDIR_REQUIRED =0x0008,
UF_LOCKOUT=0x0010,
UF_PASSWD_NOTREQD=0x0020,
UF_PASSWD_CANT_CHANGE=0x0040,
UF_ACCOUNT_LOCKOUT=0X0010,
UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED=0X0080,
}

6. Set an Active Directory user’s password

The CmdPassword_Click function in the “mainform” class calls the SetUserPassword method as:

DotnetAD.SetUserPassword(this.TxtUsername.Text, this.TxtUserPassword.Text);

public static void SetUserPassword (string UserName, string NewPassword)
{
      string LDAPDomain="/CN="+ UserName+",CN=Users," + GetLDAPDomain() ;
      DirectoryEntry oUser=      GetDirectoryObject(LDAPDomain);              oUser.Invoke("SetPassword",new Object[]{NewPassword});
      oUser.Close();
}

Using the GetLDAPDomain()and GetDirectoryObject(), we get the reference to the user, as discussed in sections above. Earlier we had used the Invoke method to add/remove a user from a group, here we are using the invoke method to set the password, by passing the “SetPassword” as the first parameter and the new password as the last.

7. Other methods present in the dll that fulfills important utilities

So far, we have discussed all the functionalities are that implemented in the sample example but the dll contains more methods which can be used by any application.
1. Checks if a user exists based both on the user name and the password.
public static DirectoryEntry UserExists(string UserName, string Password)

2. public static DotnetAD.LoginResult  Login(string UserName, string Password)
 
This method doesn’t actually log a user in, but performs tests to ensure that the user account exists (matched by both the username and password), and also checks if the account is active. It returns a value of enum LoginResult type defined in the DotnetAd class itself.

This method functions by calling the method given below. Here the parameter passed is the      useraccountcontrol which is a converted integer value of the useraccountcontrol property of DirectoryEntry object.

int userAccountControl = Convert.ToInt32(de.Properties["userAccountControl"][0]);

public static bool IsAccountActive(int userAccountControl)
{
int userAccountControl_Disabled= Convert.ToInt32(ADAccountOptions.UF_ACCOUNTDISABLE);//this converts the Hexadecimal value of the enum member to an integer value.
      int flagExists = userAccountControl & userAccountControl_Disabled;
//if a match is found, then the disabled flag exists within the control flags
      if(flagExists >0)
      {
            return false;
      }
      else
      {
            return true;
      }
      }

Other Functions

Another method which is a basic UserName, Password check. It attempt to log in a user based on the username and password to ensure that they have been set up within the Active Directory.

public static bool IsUserValid (string UserName, string Password)

 

This is an override method which performs query based on combination of username and password.

public static DirectoryEntry GetUser(string UserName, string Password)

This is used with the login process to validate the user credentials and return a user object for further validation. This is slightly different from the other GetUser... methods as this uses both the UserName and Password supplied as the authentication to check if the user exists, if so then the users object will be queried using these credentials.

This method takes a username as a parameter and query the Active Directory for the user. When found it transforms the results from the property collection into a Dataset which can be used by the client(application).
           
public static DataSet GetUserDataSet(string UserName)

This method returns a dataset of user details based on criteria passed to the query. The criteria is in the LDAP format i.e.(cn='xxx')(sn='eee') etc
           
public static DataSet GetUsersDataSet(string Criteria)

 

This method creates a Dataset structure containing all relevant fields that match to a user.
     
private static DataSet CreateUserDataSet()

 

This method returns a DataRow object that will be added to the userdataset object. This also allows the iteration of multiple rows
           
private static DataRow PopulateUserDataSet(SearchResult userSearchResult, DataTable userTable)

 

This method queries all of the defined Active Directory groups and transforms the results into a dataset to be returned.
                  
public static DataSet GetGroups()

This method returns all users for the specified group in a dataset based on a criterion.

public static DataSet GetUsersForGroup(string GroupName)

 

This method will be used by the admin query screen, and is a method to return users based on a possible combination of lastname, email address or corporate.

public static DataSet GetUsersByNameEmailCorporate(string LastName, string EmailAddress, string Corporate)

 

This method retrieves the specified property value from the DirectoryEntry object (if the property exists)

public static string GetProperty(DirectoryEntry oDE, string PropertyName)

 

This method is an override that allows a property to be extracted directly from a searchresult object.

public static string GetProperty(SearchResult searchResult, string PropertyName)

 

This method tests the value of the propertyvalue and if empty, it will not set the property in the Active Directory for Active Directory is particular about setting blank values.
           
public static void SetProperty(DirectoryEntry oDE, string PropertyName, string PropertyValue)

 

There are overrides for GetDirectoryObject() method available which can be used as per the applications requirements.