To develop programs for Active Directory, you can Use the classes from either the System. DirectoryServices or the System DirectoryServices. Protocols namespaces. In the namespace System. DirectoryServices, you can find classes the. wrap ActIve Directory Service Interfaces (ADSI) COM objects to access ActIve Directory.
ADSI is a programmatic interface to directory services. It defines some COM interfaces that are implemented by ADSI providers This means that the client can use different directory services with same programmatic interfaces. The .NET Framework classes in the System. DirectoryServices namespace make use of ADSI.· .
Figure 46-8 shows some ADSI Providers (LDAP,lIS, and NDS) that implement COM interfaces such as lADs and lUnknown. The assembly System: DirectoryServices makes use of the ADSI providers.
Classes from the namespace System. DirectoryServices. Protocols make use of Directory Services Markup Language (DSML) Services or Windows. With DSML, standardized Web service interfaces are defined by the OASIS group.
To use the classes from the System. DirectoryServices namespace, you need to reference the System. DirectoryServices assembly. With the classes in this.assembly, you can query objects, view and update properties, search for objects, and move objects to their container objects. In the code segments that follow later in this section, you use a simple C# console application that demonstrates the functionality of the classes in the System. DirectoryServices namespace.
This section covers the following:
- Classes in the System. DirectoryServices namespace
- The process of connecting to Active Directory (binding)
- Getting directory entries, creating new objects, and updating existing entries.
- Searching Active Directory
Classes In System.DlrectoryServices
The following table shows the major classes in the System. DirectoryServices namespace.
To get the values of an object in Active Directory, you need to connect to the Active Directory service. This connecting process is called binding. The binding path can look like this:
With the binding process, you can specify these items:
- The protocol; this specifies the provider to be used.
- The server name of the domain controller.
- The port number of the server process.
- The distinguished name of the object; this identifies the object you want to access.
- The username and password, if the user who is allowed to access Active Directory is different from the current logged-on user.
- An authentication type, if encryption is needed..
The following subsections discuss these options in more detail.
The first part of a binding path specifies the ADSI provider. The provider is implemented as a COM server; for identification, a progID can be found in the registry directly under HKEY_CLASSES_ROOT. The providers that are available with Windows Vista are listed in the following table. .
The server name follows the protocol in the binding path. The server name is optional if you are logged on to an Active Directory domain. Without a server name, serverless binding occurs; this means that Windows Server 2008 tries to get the “best” domain controller in the domain that’s associated with the user doing the bind. If there is no server inside a site, the first domain controller that can be found will
After the server name, you can specify the port /lumber of the server process by using the syntax : xxx.The default port number for the LDAP server is port 389: LDAP://dc01.sentine1.net:389. The Exchange server uses the same port number as the LDAP server. If the Exchange server is installed on the same system – for example, as a domain controller of Active Directory – a different port can be configured.
The fourth part that you can specify in the path is the distinguished name (DN). The distinguished name is a unique name that identifies the object you want to access. With Active Directory, you can use LDAP syntax that is based on X.500 to specify the name of the object.
This is an example of a distinguished name:
CN~Christian Nagel, OU=Consultants. DC=thinktecture. DC=local
This distinguished name specifies the common name (CN) of Christian Nagel in the organizational unit (ou) called Consultants in the domain component (DC) called thinktecture of the domain thinktecture . local. The part specified to the right is the root object of the domain. The name must follow the hierarchy in the object tree.
Relative Distinguished Name
A relative distinguished name (RDN) is used to reference objects within a container object. With an RDN, the specification of OU and DC is not needed because a common name is enough. CN=Christian Nagel· is the relative distinguished name inside the organizational unit. A relative distinguished name can be used if you already have a reference to a container object and if you want to access child objects.
Default Naming Context
If a distinguished name is not specified in the path, the binding process will be made to the default naming context. You can read the default naming context with the help of root DSE. LDAP 3.0 defines root DSE as the root of a directory tree on a directory server. For example:
By enumerating all properties of the root DSE, you can get the information about the default NamingContext that will be used when no name is specified.schemaNamingContext and configurationNamingContext specify the required names to be used to access the schema and the configuration in the Active Directory store.
This program shows the default naming context (default NamingContext DC=explorer, DC=local),the context that can be used to access the schema (CN=Schema, CN=Configuration, DC=explorer, DC=local), and the naming context of the configuration (CN=Configuration, DC=explorer, DC=local), as you can see here:
Every object has a globally unique identifier (GUID). A GElID is a unique 128-bit number as you may already know from COM development. You can bind to an object using the GUID. This way, you always get to the same object, regardless of whether the object was moved to a different container. The GUID is generated at object creation and always remains the same.
You can get to a GUID string representation with Directory Entry. Native Guid. This string representation can then be used to bind to the object.
This example shows the path name for a serverless binding to bind to a specific object represented by a GUID:
If a different user from the one of the current process must be used for accessing the directory (maybe this user doesn’t have the required permissions to access Active Directory), explicit user credentials must be specified for the binding process. Active Directory has multiple ways to specify the username.
With a downlevel logon, the username can be specified with the pre-Windows 2000 domain name:
The user can also be specified by a distinguished name of a user object, for example:
CN=Administrator, CN=Users, DC=thinktecture, DC=local
User Principal Name
The user principal name (UPN) of an object is defined with the userPrincipalNarne attribute The system administrator specifies this with the logon information in the Account tab of the User properties with the Active Directory Users and Computers tool. Note that this is not the email address of the user.
This information also uniquely identifies a user and can be used for a logon:
For secure encrypted authentication, the authentication type can also be specified. The authentication can be set with the AuthenticationType property of the Directory Entry class. The value that can be assigned is one of the AuthenticationTypes enumeration values. Because the enumeration is marked with the [Flags l attribute, multiple values can be specified. Some of the possible values are where the data sent is encrypted; Readonly Server, where you specify that you need only read access; and . Secure for secure authentication.
Binding with the DlrectoryEntry Class
The System. DirectoryServices. DirectoryEntry class can be used to specify all the binding information. You can use the default constructor and define the binding information with the properties Path, Username, Password, and AuthenticationType or pass all the information in the constructor:
Even if the construction of the DirectoryEntry object is successful. this doesn’t mean that the binding was a success. Binding will happen the first time a property is read to avoid unnecessary network traffic. At the first access of the object, you can see if the object exists and if the specified user credentials are correct.
Getting Directory Entries
Now that you know how to specify the binding attributes to an object in Active Directory, you can move on to read the attributes of an object. In the following example, you read the properties of user objects.
The DirectoryEntry class has some properties to get information about the object: the Name, Guid, and SchemaClassName properties. The first time a property of the DirectoryEntry object is accessed, the binding occurs, and the cache of the under lying ADSI object is filled. (This is discussed in more detail shortly). Additional properties are read from the cache, and communication with the server isn’t necessary for data from the same object.
In the following example, the user object with the common name Christian Nagel in the organizational unit thinktecture is accessed:
To have this code running on your machine, you must change the path to the object to access including the server name.
An Active Directory object holds much more information, with the information available depending on the type of the object; the Properties property returns a PropertyCollection. Each property is a collection itself, because a single property can have multiple values; for example, the user object can have multiple phone numbers. In this case, you go through the values with an inner foreach loop. The collection returned from properties [name] is an object array. The attribute values can be strings, numbers, or other types. Here, just the ToString() method is used to display the values:
In the resulting output, you can see all attributes of the specified user object. Some properties such as otherTelephone have multiple values. With this property, many phone numbers can be defined. Some of the property values just display the type of the object, System._ComObject; for example, last Log off, last Logon, and nTSecurityDescriptor. To get the values of these attributes, vou
must use the ADSI COM interfaces directly from the classes in the System. DirectoryServices namespace.
Access a Property Directly by Name
With DirectoryEntry. Properties, you can access all properties. If a property name is known, you can access the values directly:
Objects are stored hierarchically in Active Directory .Container objects contain children. You can enumerate these child objects with the Children property of the class DirectoryEntry. In the other direction, you can get the container of an object with the Parent property.
A user object doesn’t have children, so you use an organizational unit in the follow example. Non-container objects return an empty collection with the Children property. Get all user objects from the organizational unit thinktecture in the domain explorer . local. The Children property returns a DirectoryEntries collection that collects DirectoryEntry objects. You iterate through all DirectoryEntry objects to display the name of the child objects:
When you run the program, the common names of the objects are displayed:
In this:.example, you see all the objects in the organizational unit: users, contacts, printers, shares, and others. If you want to display only some object types, you can use the SchemaFilter property of the DirectoryEntries class. The SchemaFilter property returns a SchemaNameCollection. With this SchemaNameCollection, you can use the Add() method to define the object types you want to see Here, you are just interested in seeing the user objects, so user is added to this collection:
As a result, you see only the user objects in the organizational unit:
To reduce the network transfers, ADSI uses a cache for the object properties. As mentioned earlier, the server isn’t accessed when a DirectoryEntry object is created; instead, with the first reading of a value from the directory store, all the properties are written into the cache so that a round trip to the server isn’t necessary when the next property is accessed.
Writing any changes to objects changes only the cached object; setting properties doesn’t generate network traffic. You must use DirectoryEntry. CommitChanges() to flush the cache and to transfer any changed data to the server. To get the newly written data from the directory store, you can use DirectoryEntry. RefreshCache() to read the properties. Of course, if you change some properties without calling CommitChanges() and do a RefreshCache(), all your changes will be lost, because you read t~e values from the directory service again using RefreshCache().
Creating New Objects
When you want to create new Active Directory objects – such as users, computers, printers, contacts, and so on – you can do this programmatically with the DirectoryEntries class.
To add new objects to the directory, first you have to bind to a container object, such as an organizational unit, where new objects can be inserted – you cannot use objects that are not able to contain other objects. The following example uses the container object with the distinguished name CN=Users, DC=thinktecture, DC=local:
DirectoryEntry de = new DirectoryEntry(};
de.Pat~ ..= ‘LDAP://treslunas/CN=Users, DC=explorer, DC=local’;
You can get to the DirectoryEntries object with the Children property of a DirectoryEntry:
DirectoryEntries users .= de. Children;
The class DirectoryEntries offers methods to add, remove, and find objects in the collection. Here, a new user object is created. With the Add() method, the name of the object and a type name are required. You can get to the type names directly using ADSI Edit.
DirectoryEntry user = users.Add(‘CN=John Doe’, ‘user’);
The object now has the default property values. To assign specific property values, you can add properties with the Add() method of the Properties property. Of course, all of the properties must exist in the schema for the user object. If a specified property doesn’t exist, you’ll get a COM Exception: ‘The specified directory service attribute or value doesn’t exist.
Finally, to write. the data to Active Directory, you must flush the cache:
Updating Directory Entries
Objects in the Active Directory service can be updated as easily as they can be read. After reading the object, you can change the values. To remove all values of a single property, you can call the method PropertyValueCollection. Clear(). You can add new values to a property with Add(). Remove() and RemoveAt() remove specific values from a property collection.
You can change a value simply by setting it to the specified value. The.following example uses an indexer for PropertyValueCollection to set the mobile phone number to a new value. With the indexer a value can be changed only if it exists. Therefore, you should always check with DirectoryEntry. Properties. Contains() to see if the attribute is available:
The else part in this example uses the method PropertyValueCollection. Add() to add a new property for the mobile phone number, if it doesn’t exist already. It you use the Add() method with already existing properties, the resulting effect would depend on the type of the property (single-value or multivalue property). Using the Add() method with a single-value property that already exists results in a COMException: “A cons train t violation occurred.” Using Add() with a multivalue property, however, succeeds, and an additional value is added to the property.
The mobile property for a user object is defined as a single-value property, so additional mobile phone numbers cannot be added. However, a user can have more than one mobile phone number. For multiple mobile phone numbers, the otherMobile property is available. otherMobile is a multivalue property that allows setting multiple phone numbers, and so calling Add() multiple times is allowed. Note that multivalue properties are checked for uniqueness. If.the second phone number is added to the same user object again, you get a COMException: “The specified directory service attribute or value already exists.”
Accessing Native ADSI Objects
Often, it is much easier to call methods of predefined ADSI interfaces instead of searching for the names of object properties. Some ADSI objects also support methods that cannot be used directly from the DirectoryEntry class. One example of a practical use is the IADsServiceOperations interface, which has methods to start and stop Windows services. (For more details on Windows services see Chapter 23, “Windows Services.”)
The classes of the System. DirectoryServices namespace use the underlying ADSI COM objects as mentioned earlier. The DirectoryEntry class supports calling methods of the underlying objects directly by using the Invoke() method.
The first parameter of Invoke() requires the method name that should be called in the ADSI object; the params keyword of the second parameter allows a flexible number of additional arguments that can be passed to the ADSI method:
public object Invoke(string ,methodName, params object args);
You can find the methods that can be called with the Invoke() method in the ADSI documentation. Every object in the domain supports the methods of the lADs interface. The user object that you created previously also supports the methods of the IADsUser interface.
In the following example, the method lADsUser . Set Password() changes the password of the previously· created user object:
It is also possible to use the underlying ADSI object directly instead of using Invoke(). To use these objects, choose Project Add Reference to add a reference to the Active DS Type Library (see Figure 46-9). This creates a wrapper class where you can access these objects in the namespace . ActiveDs.
The native object can be accessed with the Na tive Object property of the DirectoryEntry class. In the following example, the object de is a user object, so it can be cast to Acti veDs. IADsUser . Set Password() is a method documented in the IADsUser interface, so you can call it directly instead of using the Invoke() method. By setting theAccountDisabfed property of IADsUser to false, you can enable the account. As in the previous examples, the changes are written to the directory service by calling CommitChanges() with the DirectoryEntry object:
ActiveDs.IADsUser user = (ActiveDs.IADsUser)de.NativeObject;
user.AccountDisabled = false;
CommitChanges ( ) ;
.NET 3.5 reduces the need to invoke the native objects behind the .NET class DirectoryEntry . .NET 3.5 gives you new classes to manage users in the namespace System. DirectoryServices. AccountManagement. The classesfrom this namespace are explained later in this chapter.