Accessing Directory Services

WEBINAR: On-demand webcast

How to Boost Database Development Productivity on Linux, Docker, and Kubernetes with Microsoft SQL Server 2017 REGISTER >

Mark Strawmyer Presents: .NET Nuts & Bolts


Welcome to the next installment of the .NET Nuts & Bolts column. In this column, we'll explore how to access directory services from within .NET. Specifically, we'll focus on Microsoft's directory service called Active Directory. The topics covered will include how to use the Active Directory for items such as searching and providing authentication services for an application. It will involve using classes in the System.DirectoryServices namespace.

Directory Services

A directory service is a centralized data source that contains information relevant to an organization's computing environment. It provides access to network resource information without the client having to know the specifics of how or where the resource is connected. A directory service is commonly used to store information regarding computers, network devices such as printers, and information on users such as security credentials and application preferences.

Active Directory (AD)

Microsoft Active Directory, originally introduced in Windows 2000, is a directory service introduced by Microsoft to replace the aging Windows NT 4.0 domain architecture. It allows an organization to have a structure that more closely matches the company environment by allowing for a flexible hierarchy, support for a much larger number of network users and resources than what NT could allow, and a whole lot more. It serves as the central security point for controlling access to resources within the network. There are many concepts and designs involved with the AD. For this article, I'm going to assume you are already familiar with the AD object structure. If not, click here to visit Microsoft's site and learn more about the Active Directory.

Active Directory Services Interface (ADSI)

Active Directory Services Interface (ADSI) is a single interface from Microsoft that can be used to administer directory services. It abstracts the capabilities of directory services from different providers so that the same interface can be used regardless of the environment. Directory services that can be accessed through ADSI include, but are not limited to, the following:

  • Novell Netware Directory Service (NDS)
  • Netware 3 bindery
  • Windows NT 4.0 directory
  • Microsoft Active Directory
  • Microsoft directory capable server products: Exchange 5.5 and above, Internet Information Services, Microsoft Commerce Server, and more.

Click here to visit Microsoft's site and learn more about ADSI.

System.DirectoryServices Namespace

The Microsoft .NET Framework includes a System.DirectoryServices namespace contained in the System.DirectoryServices.dll that must be added as a reference in order to use it. It contains the DirectorySearcher and DirectoryEntry classes. These classes utilize the Active Directory Services Interface (ADSI) to interact with and manipulate the directory from within managed code and allow you to access any ADSI provider, including Active Directory.

Each object in a directory service is represented as a DirectoryEntry. It allows you the capability to access information about the directory item such as its name, modify its properties, or even rename or move it to another location in the directory.

The DirectorySearcher is used to execute a query against the directory service. You can search for a single object that matches or multiple matches. It returns a collection of DirectoryEntry objects that are read only.

Connecting to a Directory Service

The first step in doing anything with a directory service, much like a database, is to create a connection it. The act of connecting to a directory service is often referred to as binding. The connection string, known as a path, used to connect to the directory service is dependent upon the provider to which you are connecting. While Windows NT requires that you connect to a specific server, the Active Directory example allows you to bind to the name of the domain instead. A couple of sample paths are listed below.

  • Windows NT 4.0: Connect to the current machine
  • string path = "WinNT://" + Environment.MachineName
                             + ",computer";
  • Active Directory: Connect to a dev.codeguru.com domain
  • string path = "LDAP://CN=Users,DC=dev,DC=codeguru,DC=com";
    or
    string path = "LDAP://dev.codeguru.com";

Once the path statement is worked out for the appropriate provider, all that remains to bind to the directory is to pass it as a parameter to the constructor of a new instance of the DirectoryEntry class.

  • DirectoryEntry entry = new DirectoryEntry(path);

Authenticating

The DirectoryEntry class can be used to authenticate a user login and password against a directory service. Bind to the directory and pass the user login and password of the user you wish to authenticate. You force authentication to occur by retrieving the NativeObject property.

Active Directory Authentication Sample

string path = "LDAP://dev.codeguru.com";
DirectoryEntry entry = new DirectoryEntry( 
path,"dev.codeguru.com\\administrator", "");

try
{ 
// Bind to the native object to force authentication to happen
Object native = entry.NativeObject;
Console.WriteLine("User authenticated!");
}
catch( Exception ex )
{
throw new Exception("User not authenticated: " + ex.Message);
}

Searching the Directory

The DirectorySearcher object has two methods for searching a directory. There is the FindOne method that retrieves the first entry from the directory. There is the FindAll method that retrieves all directory entries that match the search string. By default, the results will load all of the properties for the directory entries that meet the filter criteria. For efficiency, you can restrict the properties loaded by using the PropertiesToLoad collection on the DirectorySearcher.

Active Directory Searching Sample using FindOne

The following sample code uses the FindOne method of the DirectorySearcher class to find the same user we authenticated a moment ago. We will limit the properties that are retrieved during the search.

DirectoryEntry entry = new
                       DirectoryEntry("LDAP://dev.codeguru.com");
try
{
  DirectorySearcher search = new DirectorySearcher(entry);
  search.Filter = "(SAMAccountName=administrator)";
  search.PropertiesToLoad.Add("Name");
  search.PropertiesToLoad.Add("displayName");
  SearchResult result = search.FindOne();
  if( result != null )
  {
    Console.WriteLine("User found");
    foreach( string key in result.Properties.PropertyNames )
    {
      // Each property contains a collection of its own
      // that may contain multiple values
      foreach( Object propValue in  result.Properties[key] )
      {
        Console.WriteLine(key + " = " + propValue);
      }
    }
  }
  else
  {
    Console.WriteLine("User not found");
  }
}
catch( Exception ex )
{
  throw new Exception("User not authenticated: " + ex.Message);
}
Console.ReadLine();

Active Directory Searching Sample using FindAll

The following sample uses the FindAll method of the DirectorySearcher class to retrieve and information on all of the users found in the directory. It then will iterate through the properties for each of the entries found and write them to the console. This time, we will not restrict the properties and will display them all.

DirectoryEntry entry = new
                       DirectoryEntry("LDAP://dev.codeguru.com");
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(objectClass=user)";

foreach(SearchResult result in search.FindAll())
{
DirectoryEntry dirEntry = result.GetDirectoryEntry();
foreach(string key in dirEntry.Properties.PropertyNames) 
{
    // Each property contains a collection of its own
    // that may contain multiple values
    foreach( object propVal in dirEntry.Properties[key] )
    {
      Console.WriteLine(key + " = " + propVal);
    }
  }
  Console.WriteLine("---------------");
}
Console.ReadLine();

Adding a User

Adding a user to the directory is not much more complicated than the searching we were performing before. When binding to the directory, we need to provide user credentials that have the authority to modify a user; otherwise, the update will fail. In addition, make sure that you have all of the attributes required by your AD instance; otherwise, you will get a message similar to "The server is unwilling to process the request" and not be able to add a user. Typically, only the cn (full name) and sAMAccountName are required.

There is an Invoke method that you can use to access properties through native ADSI. All user properties accessible through the DirectoryEntry class can also be set through the Invoke method. However, not all user properties are accessible through the DirectoryEntry class. It is a requirement that the user password is set using the Invoke method because it is not exposed through the DirectoryEntry class. The user must exist in the AD before the password can be set.

If you have more than one Domain Controller, please allow time for the changes to be replicated to all Domain Controllers before you will see the new user in the Active Directory Users and Computers MMC snap-in.

Adding a User Sample Code

try
{
string path = "LDAP://CN=Users,DC=dev,DC=codeguru,DC=com";
DirectoryEntry entry = new DirectoryEntry(path, 
"dev.codeguru.com\\administrator", "password");

// Create the user and set properties
DirectoryEntry user = entry.Children.Add("cn=Test User", "user");
user.Properties["sAMAccountName"].Add("testuser");
user.Properties["sn"].Add("User");
user.Properties["givenName"].Add("Test");
user.Properties["description"].Add("Test account added with code.");
user.CommitChanges();

// User has to be saved prior to this step
user.Invoke("SetPassword", new object[] {"mypassword1"} );

// Create a normal account and enable it - ADS_UF_NORMAL_ACCOUNT
user.Properties["userAccountControl"].Value = 0x200; 
user.CommitChanges();

}
catch( Exception exception )
{
Console.WriteLine( exception.Message );
}

Modifying a User

Modifying the properties of a user requires us to find an existing user, retrieve the directory entry for it, then make and commit the desired changes. When binding to the directory, we need to provide user credentials that have the authority to modify a user; otherwise, the update will fail.

If you have more than one Domain Controller, please allow time for the changes to be replicated to all Domain Controllers before you will see the new user in the Active Directory Users and Computers MMC snap-in.

Modifying a User Sample Code

try
{
  string path = "LDAP://CN=Users,DC=dev,DC=codeguru,DC=com";
  DirectoryEntry entry = new DirectoryEntry(path, 
  "dev.codeguru.com\\administrator", "password");
  DirectorySearcher search = new DirectorySearcher(entry);
  search.Filter = "(SAMAccountName=testuser)";
  SearchResult result = search.FindOne();
  DirectoryEntry user = result.GetDirectoryEntry();
  user.Properties["description"].Value = "New description for user";
  user.CommitChanges();
}
catch( Exception exception )
{
  Console.WriteLine( exception.Message );
}

Summary

You have now seen ways in which the DirectoryEntry and DirectorySearcher classes of the System.DirectoryServices namespace can be used to interact with directory services. We focused on the LDAP interface exposed by the Active Directory, but the examples can be made to work for different providers by changing the binding path.

Future Columns

The topic of the next column is yet to be determined. If you have something in particular that you would like to see explained here, you can reach me at mstrawmyer@crowechizek.com.

About the Author

Mark Strawmyer, MCSD, MCSE (NT4/W2K), MCDBA is a Senior Architect of .NET applications for large- and mid-size organizations. Mark is a technology leader with Crowe Chizek in Indianapolis, Indiana. He specializes in architecture, design, and development of Microsoft-based solutions. You can reach Mark at mstrawmyer@crowechizek.com.

# # #



About the Author

Mark Strawmyer

Mark Strawmyer is a Senior Architect of .NET applications for large and mid-size organizations. He specializes in architecture, design and development of Microsoft-based solutions. Mark was honored to be named a Microsoft MVP for application development with C# for the fifth year in a row. You can reach Mark at mark.strawmyer@crowehorwath.com.

Comments

  • Trouble when i create a directory entry

    Posted by David Serrano on 01/25/2013 08:27am

    I am using: private static DirectoryEntry ConsultarActiveDirectory(WindowsIdentity wi) { DirectoryEntry dir = new DirectoryEntry(); try { SecurityIdentifier sid = wi.User; dir = new DirectoryEntry("LDAP://"); System.Diagnostics.Trace.WriteLine("++++++++ Authenticacion in LDAP by " + (string)dir.Properties["distinguishedName"][0]); } catch (Exception ex) { System.Diagnostics.Trace.WriteLine("++++++++ Error"+ ex.Message); throw; } return dir; } it works fine in local but from a remote machine it fails and i get: 'System.DirectoryServices.DirectoryServicesCOMException' how can i fix it? Thanks.

    Reply
  • How do I disabled a user list.

    Posted by Alejandra on 05/21/2012 05:25pm

    Hello. I need to disable a user list, but not all, not just one. For example the following users disable: userTest1, userTest2, userTest3, only this 3 users. Thanks :)

    Reply
  • Problem in setting the password for a user in active directory

    Posted by AvanishG on 11/16/2006 06:44am

    I'm facing a problem when setting a password for newly cretaed user to AD. Below is th code, i'm using to set.. newuser.Invoke("SetPassword", new object[] {"password"}); The error i get is "[System.Reflection.TargetInvocationException] = {"Exception has been thrown by the target of an invocation."}" and inner exception is "InnerException = {"You were not connected because a duplicate name exists on the network. Go to System in Control Panel to change the computer name and try again. (Exception from HRESULT: 0x80070034)"}" Can anybody tell me, what/where is the problem? Thanks in advance

    • Refer this link for solution

      Posted by kanzariya_shailesh on 09/30/2008 05:06am

      http://forums.asp.net/t/968051.aspx
      
      if link does not workb&b&
      
      What I actually did to fix this is after creating the account and committing that change, I created a new DirectoryEntry  (newUserEntry) object based on the newly created account and then set the password.
      
      //This works:
      
      DirectoryEntry adEntry = new DirectoryEntry(targetPath, "administrator", "password");
      DirectoryEntry userEntry = adEntry.Children.Add(sourceNode.Name, "User");
      userEntry.CommitChanges();
      DirectoryEntry newUserEntry = new DirectoryEntry(userEntry.Path, "administrator", "password");
      string password = "password";
      newUserEntry.Invoke("SetPassword", new object[] { password });
      newUserEntry.CommitChanges();

      Reply
    • Problem in setting the password for a user in active directory

      Posted by esther.uj on 07/28/2008 12:44am

      Does anyone have the answer for this problem? I also face the same problem? Anyone have the solution? Thanks in advanced.

      Reply
    Reply
  • I can't add new user using LDAP?

    Posted by BigBird on 06/09/2006 04:58am

    My pc have been promos to DC  
    and domain name is fbf.com
    if i use WinNT Provider i can add a user like this
    
    obDirEntry = new DirectoryEntr("WinNT://fbf.com/fbftungnc");
    			
    DirectoryEntries entries = obDirEntry.Children;
    
    DirectoryEntry objUser = entries.add(strLogin,"User");
    
    entries.Comitchanges();
    
    but if i using LDAP
    
    string path = "LDAP://CN=users,DC=fbf,DC=com";
    DirectoryEntry entry = new DirectoryEntry(path,
    "fbf.com\\administrator", "tungnc");
    DirectoryEntry ouser = entry.Children.Add("FSS", "user");
    ouser.CommitChanges();//error
    
    it alway seend a error message
    "an invalid dn syntax have been specified"
    
    could you show me why??
                  Thanks and regard!

    • I can't add new user using LDAP?

      Posted by Carlos Bomtempo on 10/26/2012 12:40pm

      I know that years have passed, but maybe someone having this problems arrives here. To solve the XXX problem, change the entry.Children.Add line to be like this: DirectoryEntry ouser = entry.Children.Add("CN=FSS", "user");

      Reply
    Reply
  • How do i get my hands on the email?

    Posted by zatopek on 04/06/2006 04:17am

    I need to change a lot of email adresses. Is there an easy way of doing it using this approach? I want yo get all the aliases for the found user, and maybee to add new.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • As all sorts of data becomes available for storage, analysis and retrieval - so called 'Big Data' - there are potentially huge benefits, but equally huge challenges...
  • The agile organization needs knowledge to act on, quickly and effectively. Though many organizations are clamouring for "Big Data", not nearly as many know what to do with it...
  • Cloud-based integration solutions can be confusing. Adding to the confusion are the multiple ways IT departments can deliver such integration...

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date