I’ve just created my second CodePlex project (the first one being my PushNotification Lister on http://exchangenotification.codeplex.com/). It’s the DirectoryServices.Protocols wrapper I created a while ago. The last version was still based on my old InfiniTec.Threading library which is seriously broken. The new one runs rather flawlessly and is very stable.

The Save and SaveAsync methods both accept one boolean parameter. If set to true, the save operation will return before the changes have been committed to disk by the directory server (known as lazy commit). This improves performance, but increases the likelihood of data loss.

ChangeLog

Changes made from version 1.0 to version 1.1:

Added a static Open method to the ActiveDirectoryPrinicipal object, which either returns an ActiveDirectoryUser or an ActiveDirectoryGroup object

Added the ability to save changes back to server.

Fixed a bug with integrated authentication when using the Translator class

Fixed a bug which occured when an ActiveDirectoryEntry was refreshed a second time.

Fixed a bug with the ActiveDirectoryGroup class

Fixed a bug when an DirectoryOperation threw an exception.

Downloads

Release.zip (117,601 Bytes)Binaries of version 1.1 signed with the InfiniTec private key

This class wraps around the DsCrackNames function of the Win32 Directory Services API. Basically, you can translate between these name formats:

DistinguishedName

NT4AccountName

DisplayName

UniqueId

CanonicalName

UserPrincipalName

CanonicalNameEx

ServicePrincipalName

SidOrSidHistory

DnsDomainName

ListNamingContexts

Not every nameformat can be translated to every other. You will get a TranslationStatus.NoMapping error in this case.

The last entry, ListNamingContext, can be used to enumerate all naming contexts in the forest. To use this, set the InputFormat to ListNamingContext. Then, call the Translate method with at least one name (content is completely irrelevant):

If the InputFormat is set to NameFormat.Unknown, the directory server tries to determine the format of the name(s) to translate. This causes some performance degration - If you know the format, you should supply it.

Unleashing the power of ambiguous name resolution...

Outlook has a handy feature called ambiguous name resolution: You can type only a part of a name, and Outlook resolves the given name to a complete name, if possible. Active Directory also implements this feature, and it's possible to use it with a special LDAP query: (anr=jo*) will find all items in the Active Directory with a special set of properties (per default, givenName, surname, displayName, legacyExchangeDN, msExchMailNickname, RDN, physicalDeliveryOfficeName, , proxyAddress, sAMAccountName) matches the specified filter.

The PrincipalResolver encapsulates this feature and extends it with an additional feature: If the name being searched for is exactly two characters long, the filter is set to (|(anr=value)(&(givenName=value[0]*)(sn=value[1]*))), which effectively resolves initials. Here an example:

If the domaincontroller, to which the connection object is bound is a global catalog, the entire forest will be searched. To search only a part of the forest, specify these settings:

1 resolver.ResolveScope = ResolveScope.Domain;

2 resolver.SearchRoot = Searcher.RootDomain;

The first line restricts the search to the specified domain, while the second line sets the domain for the search. Two default values are available: Searcher.RootDomain, which searches the root domain of the forest, and Searcher.DefaultDomain, which searches the default domain of the domain controller the current connection is bound to.

The PrincipalSearcher can search either for users, groups or both types. Note, that users do include contacts as well.

Speaking of users and groups

To simplify the handling of users and groups, there are two classes to handle these to types: The ActiveDirectoryUser and the ActiveDirectoryGroup:

The base class for both classes is the ActiveDirectoryEntry, which contains some properties and methods for handling Active Directory entries. Based on the ActiveDirectoryEntry is the ActiveDirectoryPrincipal, which contains some properties regarding group membership and SIDs.

Both, the ActiveDirectoryUser and the ActiveDirectoryGroup inherit from this class: The ActiveDirectoryUser exposes mst the properties available on user objects. The same is true fro the ActiveDirectoryGrup.

For performance reasons, the group memberships are only exposed in SID form (the group memberships are stored in this way). The TranslateSids method can be used to translate those sids to a more readable form.

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.

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:

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.