InfiniTec - Henning Krauses Blog

Don't adjust your mind - it's reality that is malfunctioning

A high-level wrapper around System.DirectoryService.Protocols

Thanks to Joe Kaplan, I spent some time recently playing around with the System.DirectoryServices.Protocols (SDS.P)  namespace. The main advantage of this namespace is control and flexibility: The developer decides when to close a connection. What to search, with which scope. This is possible because the SDS.P classes operate at a much lower level than the DirectoryEntry or the DirectorySearcher class.

Nothing, however, comes without a price. In this case, the price is usability - There is simply no DirectoryEntry in the SDS.P namespace - one has to do a search with a scope of Base. Not quite simple. Additionally, the only datatypes supported on the properties returned from a search operation are byte[] and string. And no support for generics anywhere...

Therefore I created this wrapper around the SDS.P namespace, to make it more usable. Additionally, I included a class to translate names between the various formats used throughout Windows. This class wraps around the DsCrackNames function of the Win32 API, and makes GUID binding trivial - Just translate the security-identifier to the corresponding GUID and bind to the Active Directory object (Yes, I know, the SID can also be used to bind to the Active Directory object - but this is limited to the current domain - one cannot bind to an object outside the current domain).


The Connection class (click to enlarge)
When using this class, you will always start with the connection object. The connection class can either bind to a specific server or to a domain name. Server-less binding is also supported.

If you want to perform your own requests using this class, just call the GetSendRequestOperation() which issues the request aynchronously using my InfiniTec.Threading library. This is not trivial if you are not familiar with the library - if you need advice on this topic, drop me a note and I will post an additional article on this topic.

The code used is something like this:

    1 using (Connection connection = newConnection("entdcsub", DirectoryIdentifierType.Server, false))

    2 {

    3     // Do something interesting here...

    4 }

Binding to specific objects

Once you have connection, you can simply bind to a known object:

    1 using (Connection connection = newConnection("dcaw", DirectoryIdentifierType.Server, false))

    2 {

    3     item = newItem("CN=Doe\, John, CN=Users, DC=AdventureWorks, DC=local", connection);

    4     item.Refresh();

    5     displayName = item.Properties.GetProperty<string>("displayName").Value;

    6 }

It's as simple as this...

The Item class has the following characteristics:


But I don't know the distinguished name of the object....

... don't panic. The Searcher class will help you here. This class is by far the most complex class in this library:


The search operation which will be performed by the Searcher class can be extensively customized. Here are the main option:
Constraints - This property accepts a standard LDAP filter like "(mail=*)" or similar.
IncludeDeletedItems - If true, deleted items are returned.
NamingContextScope - This is important. You can specifiy if you want to search the current naming context only, or search the current and all subordinate contexts.
PageSize - Doing a paged search reduces the resources used during the search. This property let you specify the number of items returned per page.
PropertiesToLoad - Which properties should be populated during the search?
Scope - Do you want to search only the search root, the direct descendents of the searchroot, or all levels below the search root?
SearchRoot - Where does the search begin? If NamingContextScope is set to domain scope, a search root must be specified. Otherwise, this property can be left blank. In this case, the entire forest ist searched.
SizeLimit - This property lets you specify the maximum number of items you want to get. But you should use this sparingly - if more items are returned than specified here, an exception is thrown.
SortKeys - Very handy. This allows server-side sorting of the result set

To start a search, populate the desired fields and call FindAll or FindPage. The first method, performs the search and returns once the search is completed. The FindPage method returns once the next page of items is returned by the server.

For scalability reaonse, you should use the FindAllAsync and FindPageAsync methods - these methods return immediately and thus don't block the current thread. The FindCompleted and ProgressChanged events are fired, whenever an operation completes.

The following example performs an ambiguous name resolution and finds all entries which start with the character a:

    1 staticvoid Main(string[] args)

    2 {

    3     Searcher searcher;

    4     SearchToken token;

    5 

    6 

    7     using (Connection connection = newConnection("dcaw", DirectoryIdentifierType.Server, false))

    8     {

    9         searcher = newSearcher(connection);

   10         searcher.PageSize = 1000;

   11 

   12         searcher.Constraints = "(aNR=a*)";

   13         searcher.NamingContextScope = NamingContextScope.IncludeSubDomains;

   14         searcher.SearchRoot = Constants.WellknownDistinguishedNames.RootDse;

   15         searcher.ProgressChanged += searcher_ProgressChanged;

   16         searcher.FindCompleted += searcher_FindCompleted;

   17 

   18         _Event = newManualResetEvent(false);

   19 

   20         searcher.FindPageAsync();

   21 

   22         _Event.WaitOne();

   23 

   24         Console.ReadLine();

   25     }

   26 }

   27 

   28 staticvoid searcher_FindCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)

   29 {

   30     Console.WriteLine("Finished");

   31     if (e.Error != null) Console.WriteLine("Error: " + e.Error.ToString());

   32     _Event.Set();

   33 }

   34 

   35 staticvoid searcher_ProgressChanged(object sender, SearchProgressChangedEventArgs e)

   36 {

   37     Console.WriteLine("Found items: " + e.Items.Count);

   38     foreach (Item item in e.Items)

   39     {

   40         Console.WriteLine("\t" + item.DistinguishedName);

   41     }

   42     Console.WriteLine();

   43 }

If you want to get one page and continue the search at a later point in time (for example from an ASPX page), you can use the SearchToken class to recreate a search operation:

    1 using (Connection connection = newConnection("dcaw", DirectoryIdentifierType.Server, false))

    2 {

    3     searcher = newSearcher(connection);

    4     searcher.PageSize = 1000;

    5 

    6     // Create a searcher and perform the initial search

    7 

    8     // Now, save the current search token.

    9     token = searcher.SearchToken;

   10 

   11 

   12     // Create a new search operation from the saved token:

   13     searcher = newSearcher(connection, token);

   14     searcher.ProgressChanged += searcher_ProgressChanged;

   15     searcher.FindCompleted += searcher_FindCompleted;

   16 

   17     // Continue the search operation

   18     searcher.FindPageAsync();

   19 }

The SearchToken is marked as serializable, so it can be persisted in the viewstate of an ASPX page.

The other very interestingly looking classes...

There are a number of classes in the library I will discuss in another post... those classes can be used to translate names, or provide a stongly-typed access to properties of ActiveDirectory principals (users, groups), and a search class used to find those principals...

Limitations

I have barely touched the surface of the SDS.P namespace - the current classes are read-only, meaning that changes to the Item class cannot be written back to to directory. This will come with a later release.

License

This library is published as freeware. You may use it in you own programs, commercial or freeware at no cost. You may also modify the classes. All I ask for is that you give credit (a link or something like that) to the InfiniTec website.

Downloads

Documentation.zip (341,126 Bytes)
The documentation, as compiled help file
InfiniTec.DirectoryServices_Release.zip (110,671 Bytes)
Release binaries, signed with the InfiniTec private key
InfiniTec.DirectoryServices_Source.zip (66,783 Bytes)
The source code for this release

Technorati:

Posted by Henning Krause on Sunday, September 3, 2006 12:00 AM, last modified on Monday, November 29, 2010 7:30 PM
Permalink | Post RSSRSS comment feed

Comments (1) -

On 8/2/2009 7:41:04 PM SSG United Kingdom wrote:

SSG

Great post.  Thanks for making this available to us.  I'm off to print out the docs and start working my way through it.