The System.DirectoryServices namespace contains the core classes a developer will need to find, access, create, and update information in Active Directory or ADAM. In this section, we will cover the main classes and key aspects that every developer should know.
SDS uses .NET's ability to interoperate with COM to provide a managed code wrapper around some (but not all) of the ADSI interfaces, while still providing a set of classes that are consistent with the overall design of the .NET Framework.
A Word about ADSI Providers
Because SDS is based on ADSI, it can use all of ADSI's various providers to access different directory services. As we already stated, we are primarily interested in LDAP in this book, but SDS is designed to support the other providers as well (at least to some extent). However, most of these other providers don't fit in as well with the design as LDAP does, and they will eventually move into other manifestations in the .NET Framework.
SDS has been part of the .NET Framework since version 1.0 and is the only directory service API available on all versions of the framework. In .NET 2.0, the SDS namespace was overhauled to include a variety of new classes and features, and even more bug fixes. However, the API changes in 2.0 have been designed to maintain backward compatibility. Code compiled in versions 1.0 and 1.1 should also continue to run on the version 2.0 runtime.
We mentioned earlier that SDS is packaged in the System.DirectoryServices.dll assembly. While this may seem obvious to many, it bears mentioning that we have to add an assembly reference to it in order to use the types in the namespace. Since no project types in Visual Studio include this assembly reference by default, we must remember to add it before proceeding.
We can divide the functionality in the System.DirectoryServices namespace into four subcategories:
In Chapter 1, we discussed the LDAP API and mentioned that the primary metaphor for LDAP is based on sending and receiving messages on a persistent connection. However, in SDS (and ADSI), the primary metaphor is based on finding and manipulating objects in the directory. When we compare a directory service to a filesystem, the metaphor seems more natural. We are mainly interested in the files in the filesystem and are not so worried about how we were connected to it. As such, it makes sense that we would be interested primarily in the objects in the directory as well. However, in order to make this metaphor work, ADSI has to jump through some interesting hoops behind the scenes. Usually we can ignore this and just go about our work. Sometimes, however, there is enough friction between the two models that we must pay more attention to what's going on under the hood.
Directory Object Access
The DirectoryEntry class is the primary class used in SDS to represent objects in the directory. It provides the ability to read all of the properties (or attributes, in LDAP terms) of a directory object, as well as to modify objects. The DirectoryEntry class is based largely on top of the IADs interface in ADSI, which is the primary interface in ADSI. This primary class uses the PropertyCollection and PropertyValueCollection classes to read and modify attribute data, as well as a few additional enumerations to control things like authentication behavior. Closely related to the DirectoryEntry class is the DirectoryEntries class, which allows for enumeration over a collection of DirectoryEntry objects and provides the means for adding and removing objects from the directory hierarchy. Most importantly, DirectoryEntry also controls the security context used to access the directory. It is essentially the core of the entire system.
Finally, a new DirectoryServicesCOMException has been added in the version 2.0 release to help developers more easily trap SDS exceptions and relay meaningful information that was previously somewhat obscured with the standard COMException. Although this new exception class alleviates some of the pains associated with exception handling in version 2.0, developers are still hamstrung to some extent because the underlying ADSI components tend to throw very generic and uninformative errors.
The DirectorySearcher class provides the mechanism for searching LDAP directories in .NET. While DirectoryEntry is the core of the SDS system, DirectorySearcher is what sets SDS apart from other APIs. The DirectorySearcher class wraps the IDirectorySearch ADSI interface and provides a straightforward class for performing both simple and complex search functions that support nearly all of the features supported by LDAP and Active Directory. The DirectorySearcher class uses a DirectoryEntry object to define the location from which to start the search in the directory tree, and to define the security context used to perform the search.
DirectorySearcher uses a variety of other classes for support, such as the SearchResult, SearchResultCollection, ResultPropertyCollection, and ResultPropertyValueCollection classes. These allow enumeration of search results and read-only access to the attribute data from each result. Additionally, some other types and enumerations are used to support things such as search scope and sorting.
The biggest changes to SDS in .NET 2.0 surround the DirectorySearcher class. We can now perform virtual list view searches, attribute-scoped queries, and searches for deleted items, and we can get extended distinguished name (DN) information, among other things. All of these are welcome new additions, and we will discuss them in more detail in Chapters 4 and 5.
Technically, we can use DirectorySearcher with any ADSI provider that supports the IDirectorySearch interface. However, in practice, this is limited to a single providerthe LDAP provider. In previous ADSI-based APIs, directory searching was a mixed bag. Automation clients such as VBScript had to use a fairly clunky ADO-based interface and only C++ clients could use IDirectorySearch. .NET brings the power and speed of IDirectorySearch to all .NET languages in an easy-to-use design.
Directory Object Security
The .NET Framework 2.0 introduces first-class support for Windows security descriptors and access controls lists (ACLs). Referred to affectionately as MACL, the Managed ACLs are now integrated into nearly every .NET object that supports a Windows ACL, such as files and synchronization objects like mutexes. Active Directory and ADAM objects are no exception to this. SDS includes a variety of new classes to support the special ACL settings that Active Directory uses to provide its granular security model.
This substantially improves the developer's ability to read and write security descriptors on Active Directory resources. Previously, this was possible only by dropping down into the ADSI using COM interop, and the experience was often slow and unpleasant. Used in conjunction with the new IdentityReference class in the System.Security.AccessControl namespace, it is now simple to modify and maintain enterprise security in .NET. We will cover managing security and permissions in more detail in Chapter 8.
Code Access Security
The .NET Framework includes a system called code access security (CAS), which provides a mechanism to limit the actions that code can perform based on the identity of the code and independent from the identity of the user running the code. SDS integrates with the CAS system by providing a DirectoryServicesPermission class and an accompanying attribute (DirectoryServicesPermissionAttribute) for declarative security. A few additional supporting classes also are included to round out CAS support. We discuss CAS in detail in Chapter 8.
ADSI was designed to be extensible. Different vendors can create providers to allow developers access to any data store using ADSI. Shipping with ADSI today are providers for the following data stores:
Since this book deals only with LDAP directories and primarily with Active Directory and ADAM, we are mainly concerned with the LDAP provider. The LDAP provider actually comes in two flavors: LDAP and GC. GC stands for global catalog, which is the component of Active Directory that contains a partial read-only replica of all of the data in the entire forest. However, the GC provider still speaks LDAP, just on different TCP/IP ports. Aside from the fact that the global catalog is read-only, it can really be considered just a special case of LDAP in Active Directory. When using a lower-level LDAP API to talk to the global catalog, we just change our TCP/IP port and keep doing whatever we were doing before.
The WinNT provider still has its place for use with older NT4 domains (although we don't cover those in this book, as we already mentioned) and local machine accounts. Because Active Directory is backward compatible with the SAM interfaces, WinNT can be used to access Active Directory in a pinch. However, it is unable to access all of Active Directory's capabilities. For this reason, we recommend avoiding the WinNT provider when working with Active Directory and sticking with LDAP. In distributed environments such as web applications, we have seen flaky behavior with this provider, though SDS does actually work fairly well with the WinNT provider in a local desktop context.
We mentioned NDS and NWCOMPAT for completeness only. In practice, these providers for Novell are not used widely and have been deprecated. Most Novell shops have converted to eDirectory by now and can speak LDAP natively anyway.
The IIS provider is interesting in that it requires many additional provider-specific ADSI interfaces to do the advanced work. This results in the need for a lot of reflection code or COM interop in .NET to access these features. Just using SDS may not be enough. These issues make using the IIS provider with SDS harder than it probably should be. While we can still use it to do what we need to do, this is a case of where we should be looking for other APIs. As such, IIS is probably the worst fit to the ADSI model of all of the providers, because it requires the use of so many of these additional interfaces. We have heard rumors that Microsoft will eventually release a domain-specific .NET API for IIS (once rumored to be called Microsoft.Iis.Metabase) that will address many of these issues and will provide a good managed-code experience for .NET developers. Until then, it is probably best to look to WMI for IIS support. We have heard that it is pretty good and that WMI has a nice .NET story with the System.Management namespace.
Other Useful ADSI Interfaces
As we already discussed, SDS provides a .NET wrapper around some but not all of the ADSI interfaces. Typically, we can do the vast majority of our work using just the few interfaces that it does wrap. However, it is occasionally useful or necessary to use some of the interfaces defined in ADSI, as there is no .NET equivalent.
When We Need to Use ADSI
The following primary categories of use cases represent instances when we will need to drop a little lower than SDS to get to ADSI and use its interfaces instead:
Handling Some Specific Data Types
Some attribute values from Active Directory and ADAM are returned from the DirectoryEntry object as type System.__ComObject rather than as something more intuitive or useful, such as String, Int32, or DateTime. Actually, these types are pointers to ADSI COM interfaces. Specifically, three Active Directory data types are marshaled back to .NET in this way: LargeInteger (syntax 22.214.171.124), DN-With-String (syntax 126.96.36.199), and DN-With-Binary (syntax 188.8.131.52). LargeInteger is by far the most common. We discuss LDAP data types in detail in Chapter 6, including a discussion of the history behind this apparently strange behavior.
Performing Specific Operations on Users or Groups
ADSI includes an IADsUser and IADsGroup interface to help make some operations on user and group objects in Active Directory easier to perform. SDS does not include a wrapper around these interfaces, so we sometimes need direct access to these interfaces to get work done. The most notable of these functions are the IADsUser SetPassword and ChangePassword methods. We cover the specific details of these two interfaces in our sections on user and group management in Chapters 10 and 11.
Managing Security on Directory Objects
We already discussed how SDS includes a set of classes for managing the security of objects in the directory with the Managed ACL or MACL classes. However, all of these classes are brand new for .NET 2.0. In .NET 1.x, the story is not as good. To accomplish the same goals, we need to use the ADSI IADsSecurityDescriptor, IADsAccessControlList, and IADsAccessControlEntry interfaces or resort to other, more complex means. As usual, we will dig into the details on this in Chapter 8 and demonstrate how to get by in both worlds.
Performing Specific Utility Functions
ADSI also includes a variety of other utility interfaces that have no .NET equivalent. They tend to vary in usefulness from "sometimes necessary" to "would never miss it." IADsNameTranslate probably ranks highest among the nice-to-have features in ADSI. IADsObjectOptions and IADsObjectOptions2 have a managed wrapper in .NET 2.0, DirectoryEntryConfiguration, but not in 1.x. These interfaces are often required for some ADAM tasks and contain a few nice-to-have features that are useful in some scenarios. We will ignore the rest for now, and will mention them later in the context in which they are useful.
How to Use ADSI When Necessary
There are two approaches to using ADSI features within .NET:
Reflection is essentially just the ability to discover information about a type at runtime and to call members on that type. There are three ways to do reflection to access ADSI features in SDS.
The most practical approach is to use the Invoke* methods on DirectoryEntry. They allow us to call any property or method defined on any COM interface that is valid for the current directory object. Note that in .NET 1.x, this is limited to method calls only, as there is only one Invoke method and it only supports methods. Property invocation requires using the second approach.
The other general approach is to use COM interop directly. Normally, we do this by creating an interop assembly using the tlbimp.exe command-line tool or by simply setting a reference to activeds.tlb in Visual Studio. It is actually possible to roll our own interop assembly by declaring the interop types manually. We cover various interop approaches in more detail in Appendix A.
Part I: Fundamentals
Introduction to LDAP and Active Directory
Introduction to .NET Directory Services Programming
Binding and CRUD Operations with DirectoryEntry
Searching with the DirectorySearcher
Advanced LDAP Searches
Reading and Writing LDAP Attributes
Active Directory and ADAM Schema
Security in Directory Services Programming
Introduction to the ActiveDirectory Namespace
Part II: Practical Applications
Part III: Appendixes
Appendix A. Three Approaches to COM Interop with ADSI
Appendix B. LDAP Tools for Programmers
Appendix C. Troubleshooting and Help