To support programming against ADSI in managed code, the .NET Framework provides the System.DirectoryServices namespace. Rather than forcing you to write legacy code to COM interfaces, which would degrade performance, the System.DirectoryServices namespace is implemented as all managed code, although COM versions of ADSI still work inside of .NET. If you need to maintain legacy applications, you should still write to the COM interfaces. However, for any new development that you want to perform using .NET, you should leverage the System.DirectoryServices namespace.
The .NET Framework SDK includes the complete documentation for this namespace. I'll cover just a subset of the functionality of this namespace next . We will look at three tasks that you will commonly perform with this namespace against Active Directory: searching, reading entries, and writing entries. Please note that the DirectoryServices namespace works with more than just the LDAP provider and Active Directory. You can also use it with IIS, NDS, and WinNT providers.
You will primarily use two classes in the DirectoryServices namespace: DirectoryEntry and DirectorySearcher . The DirectoryEntry class is for accessing a node in the directory; we'll discuss it when we look at reading and writing entries in the directory. The DirectorySearcher class is used to search the directory. The result it returns is a .NET collection, which you can iterate through.
The first thing you do with the DirectorySearcher class is to construct a new object of that class. The constructor for the class takes an optional argument ”the search base where the search will start. If you do not specify the search base, the search base will default to the domain. You can specify the search base at a later point using the SearchRoot property.
Next, you specify a search scope. The search scope can be Base (this level only), OneLevel ( first-level children), or Subtree (entire subtree below the base). You set the search scope using the SearchScope property on the instance of your DirectorySearch class.
Next you specify the LDAP filter you want to use to search the directory. Here are some examples of filters:
(objectClass=*) All objects
(sn=n*) All objects where the surname starts with n
(&(objectCategory=Person)(objectClass= user )) All users
(&(objectCategory=Person)(mail=*)) All users with e-mail addresses
((department=100)(department=200)) All objects in department 100 or 200
The next step is to specify the properties to load by using the PropertiesToLoad collection's Add method. If you do not specify the properties to load, ADSI will load all properties for the returned objects.
You can optionally specify the page size, size limit, server time limit, and other properties. The page size is useful if you believe that a large number of results will be returned. Instead of dumping all the results back, ADSI will return pages of results based on the size you specify.
The final step before getting your results is to perform the search by calling the FindAll method. This method performs the search and returns a collection of all objects that meet your criteria. The key thing about the results that are returned is that they are read-only. If you want to modify a result, you should request its associated DirectoryEntry object by using the GetDirectoryEntry method on the returned result object. You can access any of the properties that are returned in your results using the notation object.Properties("propertyname").Item(0) .
The following code, from the WinForms sample that's included with the companion files, shows how to use the DirectorySearch class:
On Error Resume Next If txtLDAPPath.Text <> "" Then Dim oRoot As New _ System.DirectoryServices.DirectoryEntry(txtLDAPPath.Text) Dim strAlias As String strAlias = txtAlias.Text Dim oSearcher As New _ System.DirectoryServices.DirectorySearcher(oRoot) 'Can change search base using SearchRoot 'oSearcher.SearchRoot = "NewSearchBase" 'Set the search to subtree to search entire tree oSearcher.SearchScope = DirectoryServices.SearchScope.Subtree oSearcher.Filter = "(mailNickname=" + strAlias + ")" oSearcher.PropertiesToLoad.Add("cn") oSearcher.PropertiesToLoad.Add("title") oSearcher.PropertiesToLoad.Add("department") oSearcher.PropertiesToLoad.Add("physicalDeliveryOfficeName") oSearcher.PropertiesToLoad.Add("telephoneNumber") Dim oResults As System.DirectoryServices.SearchResultCollection oResults = oSearcher.FindAll() Dim oResult As System.DirectoryServices.SearchResult For Each oResult In oResults MsgBox(oResult.Path) MsgBox(oResult.Properties("cn").Item(0)) MsgBox(oResult.Properties("title").Item(0)) MsgBox(oResult.Properties("department").Item(0)) MsgBox(oResult.Properties("physicalDeliveryOfficeName").Item(0)) MsgBox(oResult.Properties("telephoneNumber").Item(0)) 'You can get the Directory Entry by calling Dim oEntry As System.DirectoryServices.DirectoryEntry = _ oResult.GetDirectoryEntry MsgBox(oEntry.Path) Next oResult Else MsgBox("You must enter an LDAP path!", vbOKOnly + vbExclamation) End If
Instead of searching for entries in the directory, if you know the path of the entry you want to view, you can use the DirectoryEntry class. This class allows you to create, read, write, rename, move, enumerate, or delete children in the directory. The first thing to do when you create your DirectoryEntry instance is to either pass the path to the constructor of the directory object you want to open or pass nothing to the constructor. If you pass nothing to the constructor, the Active Directory Locator Service will help you find the right directory server to work with.
You can also navigate through the directory hierarchy by using the DirectoryEntry class. You can get the parent of the current node by using the Parent property. You can get the children by using the Children property, which returns a collection containing the children. If you want to find a specific child, you can use the Find method and pass along the directory identifier you want to use to find the specified child, such as cn=Username .
The DirectoryEntry object also provides access to the properties contained in the directory object it is associated with. You can use the Properties collection to view specific properties contained in the directory object. Because this is a collection, it can hold a single value or multiple values. When you use the Value property on the Properties collection, this is the same thing as asking for the first element in the collection, such as object.Properties.Item(0).value .
Sometimes you might want to modify a property. To do this, you just set the property to the new value. If you simply want to add a value, use the AddRange method. Be sure to call CommitChanges after modifying the properties.
To add an entry, you just call the Add method and pass the name of the new entry and the schema class name for the new entry. This method returns a DirectoryEntry object. Set the appropriate mandatory properties according to the directory service you are using, and then call CommitChanges .
To remove an entry, you just call the Remove method and pass the name of the child you want to remove. If you want to remove an entire tree, call the DeleteTree method.
The following sample shows you how to use the DirectoryEntry object to read and write directory properties:
Try Dim oDE As New _ System.DirectoryServices.DirectoryEntry(txtLDAPPath.Text) 'You could also use either of these: 'Dim oDE As New _ ' System.DirectoryServices.DirectoryEntry(txtLDAPPath.Text, _ ' "username", "password") 'Dim oDE As New _ ' System.DirectoryServices.DirectoryEntry(txtLDAPPath.Text, _ ' "", "", System.DirectoryServices.AuthenticationTypes.Secure) 'Msgbox some properties MsgBox(oDE.Properties("cn").Value) MsgBox(oDE.Properties("title").Value) 'Get the children Dim oDEChildren As System.DirectoryServices.DirectoryEntries oDEChildren = oDE.Children() Dim oDEChild As System.DirectoryServices.DirectoryEntry For Each oDEChild In oDEChildren MsgBox(oDEChild.Properties("cn").Value) Next 'You can also find specific children using Find() 'Find a user with cn=Thomas Rizzo Dim oTRDE As System.DirectoryServices.DirectoryEntry = _ oDEChildren.Find("CN=Thomas Rizzo") MsgBox(oTRDE.Properties("cn").Value) 'Modify a property oTRDE.Properties("title").Value = "King of the World!" 'Need to commit changes! oTRDE.CommitChanges() 'Example: Create a new entry Dim oBGDE As System.DirectoryServices.DirectoryEntry = _ oDEChildren.Add("CN=Bill Gates", "user") 'Set the appropriate mandatory properties defined by the 'directory service oBGDE.Properties("X").Value = "y" 'Call CommitChanges oBGDE.CommitChanges() Catch MsgBox("There was an error. " & Err.Number & " " & Err.Description) End Try
Not all ADSI functions are exposed via System.DirectoryServices . This means that sometimes you might need to call directly to the previous version of ADSI. You don't have to include both libraries in your application, however ”the DirectoryEntry class provides an Invoke method that allows you to call the nonmanaged version of ADSI to perform your function. For example, DirectoryEntry does not expose the SetPassword method, but ADSI does. Using the Invoke method, as shown in the following code, you can call the SetPassword method in ADSI:
Dim oDE As New System.DirectoryServices.DirectoryEntry(txtLDAPPath.Text) 'Need to pass a param array with the call for any parameters 'to the ADSI call Try Dim oObject(0) As Object oObject(0) = "NewPassword" oDE.Invoke("SetPassword", oObject) Catch MsgBox("Error! " & Err.Number & " " & Err.Description) End Try
When you work with the DirectoryServices namespace, connections to the directory are cached if you are accessing the same server with the same username, password, and port number. If you no longer need the connection, you should implicitly call the Dispose method to close the connection and free the resources used by the connection.
When you work with the DirectoryServices library, you might want to use .NET forms-based authentication and allow your program to pass a username and a password for authentication against Active Directory. The following C# code shows how to do this:
public bool IsUserValid(String uid, String pwd) { DirectoryEntry entry = new DirectoryEntry(_path, uid, pwd, AuthenticationTypes.Delegation); try { //Bind to the native AdsObject to force authentication. Object obj = entry.NativeObject; } catch (System.Runtime.InteropServices.COMException e) { //Something happened. You will need to write code to see if it is //an Access Denied e.ErrorCode or something else. throw new Exception(ex.Message); } return true; }