The .NET Framework System.DirectoryServices namespace is
a robust and flexible API for querying and manipulating objects in an
LDAP directory, such as Microsoft's Active Directory. The System.DirectoryServices
namespace contains several classes, many of which are built on top of
ADSI (Active Directory Service Interfaces). If you are already familiar
with ADSI, the learning curve for understanding the System.DirectoryServices
classes will be pretty minimal.
The two main classes within System.DirectoryServices are
DirectoryEntry and DirectorySearcher. The
DirectoryEntry class represents an object in the directory
and can be used to create new objects and manage existing ones. The
DirectorySearcher class is the primary interface for searching
the directory. It is a simple interface that contains properties for
all the typical search options you need when performing LDAP queries.
|
Related Reading Active Directory |
In Part 1 of this article, I'll cover the DirectoryEntry
class and show examples of how to iterate over the attributes of an
object. Next week in Part 2, I'll cover the DirectorySearcher class
and show examples of how you can modify objects. I'll touch on some
of the other System.DirectoryServices classes, but these
are the two main ones you should become familiar with. For the complete
list of System.DirectoryServices classes, check out the
.NET section of
MSDN.
DirectoryEntry BasicsThe DirectoryEntry class contains several properties to
access the attributes of LDAP objects. The following code shows how
to display the currentTime attribute of the RootDSE:
Dim objRootDSE As New DirectoryEntry("LDAP://RootDSE")
Console.WriteLine(objRootDSE.Properties("currentTime")(0))
Once you instantiate the DirectoryEntry object, you can
access the currentTime attribute by passing it to the Properties
property. Properties actually returns a collection of values
for the attribute in the form of a PropertyCollection class,
which is why you needed to specify an index of 0 to get at the first
value. If the currentTime attribute was multi-valued, you
can get at the other values by incrementing the index to 1 and so on.
Now let's look at how to display all of the values for the attributes
of an object. In this example, I will target the RootDSE.
Dim objRootDSE As New DirectoryEntry("LDAP://RootDSE")
Dim strAttrName As String
Dim objValue As Object
For Each strAttrName In objRootDSE.Properties.PropertyNames
For Each objValue In objRootDSE.Properties(strAttrName)
Console.WriteLine(strAttrName & " : " & objValue.ToString)
Next objValue
Next strAttrName
As you can see, Properties, which returns a PropertyCollection,
has a PropertyNames property that returns a collection
of attribute names for the object. I looped over each attribute and
then looped over each value for that attribute to ensure all values
were printed for single and multi-valued attributes. The ToString
property converts whatever value is stored in the attribute to a printable
string.
There are several properties available in the DirectoryEntry
class. Table 1 contains a list of them.
Table 1. DirectoryEntry properties
| Property Name | Description |
|---|---|
| AuthenticationType | Gets or sets the type of authentication to use when accessing the directory. |
| Children | Gets a DirectoryEntries class which contains the child objects of this object. |
| Guid | Gets the GUID for the object (e.g. in Active Directory the objectGUID attribute). |
| Name | Gets the relative distinguished name of the object. |
| NativeGuid | Gets the GUID of the object as returned by the provider. |
| NativeObject | Gets the native ADSI object. |
| Parent | Gets the object's parent in Active Directory. |
| Password | Gets or sets the password to use when authenticating. |
| Path | Gets or sets the ADsPath for the object. |
| Properties | Gets a PropertyCollection class containing the
attributes on the object. |
| SchemaClassName | Gets the objectclass of the object. |
| SchemaEntry | Gets DirectoryEntry class of the object's objectclass.
|
| UsePropertyCache | Gets or sets the flag indicating if the property cache should be committed after each operation. |
| Username | Gets or sets the username to use when authenticating. |
One interesting property to note is Children, which returns
a DirectoryEntries collection containing each child object.
Using the Children property you can quickly traverse a
directory tree. The following code prints out the entire directory tree
rooted at dc=rallencorp,dc=com:
Sub Main()
Dim objADObject As New DirectoryEntry("LDAP://dc=rallencorp,dc=com")
DisplayChildren(objADObject, " ")
End Sub
Sub DisplayChildren(ByVal objADObject As DirectoryEntry, _
ByVal strSpaces As String)
Console.WriteLine(strSpaces & objADObject.Name)
Dim objChild As New DirectoryEntry()
For Each objChild In objADObject.Children
DisplayChildren(objChild, strSpaces & " ")
Next objChild
End Sub
The DisplayChildren subroutine is recursive. For each child
that is found, DisplayChildren is called and so on until childless nodes are found. The strSpaces variable is used
to indent each child so that you can see the hierarchy when printed
out.
Now, let's say that I wanted to traverse the tree, but only print out
the Organizational Units (OU). To do that, I can use the SchemaClassName
property for each object and print out only the entry if it's SchemaClassName equals organizationalUnit, which is the objectClass
value for OUs.
Sub Main()
Dim objADObject As New DirectoryEntry("LDAP://dc=rallencorp,dc=com")
DisplayChildren(objADObject, " ")
End Sub
Sub DisplayChildren(ByVal objADObject As DirectoryEntry, _
ByVal strSpaces As String)
If objADObject.SchemaClassName = "organizationalUnit" Then
Console.WriteLine(strSpaces & objADObject.Name)
End If
Dim objChild As New DirectoryEntry()
For Each objChild In objADObject.Children
DisplayChildren(objChild, strSpaces & " ")
Next objChild
End Sub
I'm now going to take many of the concepts described so far and make
a fully functional program. Let's expand on the first example I covered,
which printed the attributes and values of the RootDSE.
I'm going to turn it into a program that can accept a command-line argument,
which should be the ADsPath of an object, and then display
all of the attributes and values for that object. Here is the code:
Imports System
Imports System.DirectoryServices
Module Module1
Sub Main()
Dim cmd As String
' Read the commandline and get the number of arguments passed
Dim intArgs As Integer
Try
intArgs = Environment.GetCommandLineArgs().Length()
Catch exp As Exception
' Set intArgs to 0 if no arguments were passed
intArgs = 0
End Try
' If an argument was specified on the commandline, set
' strADsPath to that, if not default to query the RootDSE
Dim strADsPath As String
If intArgs > 1 Then
strADsPath = Environment.GetCommandLineArgs()(1)
Else
strADsPath = "LDAP://RootDSE"
End If
' I need to see if the object in strADsPath exists
' and if not, print an error and return
Dim objADObject As New DirectoryEntry()
Try
If objADObject.Exists(strADsPath) = False Then
Throw (New Exception("Object does not exist"))
End If
Catch exp As Exception
Console.WriteLine("Error retrieving object: " & strADsPath)
Return
End Try
' Iterate over each attribute of the object and print its values
Dim strAttrName As String
Dim objValue As Object
Try
objADObject.Path = strADsPath
Console.WriteLine("Displaying " & objADObject.Path)
For Each strAttrName In objADObject.Properties.PropertyNames
For Each objValue In objADObject.Properties(strAttrName)
Console.WriteLine(strAttrName & " : " & objValue.ToString)
Next objValue
Next strAttrName
Catch exp As Exception
Console.WriteLine("Fatal error accessing: " & strADsPath)
Return
End Try
End Sub
End Module
The first two lines, which use the Imports keyword, allow you to specify class names contained within those namespaces without fully qualifying them. For example, by using Imports I can use the following code:
New DirectoryEntry()
instead of:
New System.DirectoryServices.DirectoryEntry()
For simplicity, I put the rest of the code directly in the Main subroutine.
The first part of the code attempts to read the command line using the
System.Environment namespace to see if a parameter was
specified. A Try Catch statement was used because the call
to Environment.GetCommandLineArgs.Length will throw an
exception if no parameters are passed on the command line.
Note that the intArgs variable will contain the number of
arguments passed to the script including the script name as the first
argument. To see if the user actually passed the ADsPath,
I have to check if intArgs > 1. Next I set the strADsPath
variable to the value specified on the command line, and if one wasn't
specified, default to the RootDSE. Next I use the Exists
method to determine if the object specified in strADsPath
actually exists.
The DirectoryEntry class contains a host
of methods in addition to the properties I showed earlier. Table 2 contains
a list of all the DirectoryEntry methods.
Table 2. DirectoryEntry methods
| Method Name | Description |
|---|---|
| Close | Closes the DirectoryEntry and releases any system
resources associated with the component |
| CommitChanges | Saves any changes to the object in Active Directory (similar to SetInfo) |
| CopyTo | Creates a copy of the object |
| DeleteTree | Delete the object and any children |
| Equals | Determines if two objects are the same |
| Exists | Determines if the object exists in Active Directory |
| Invoke | Allows you to invoke a native ADSI method |
| MoveTo | Move an object to a different location |
| RefreshCache | Refresh the property cache for the object |
| Rename | Rename the relative distinguished name of the object |
| ToString | String representation of the object |
If the Exists check fails, I generate an exception using
Throw. If the object exists, I proceed to iterate over
each attribute, printing the values for it. To turn the code into an
executable, you can compile the program by selecting Build -> Build
Solution from the VS.NET menu. If any errors were found they would be
displayed in the bottom pane. If none were found, you can then execute
the program. If I named the project EntryQuery, an example command
line would look like the following:
D:\VSProjs\EntryQuery\EntryQuery\bin> entryquery.exe LDAP://dc=rallencorp,dc=com
Next week, in Part 2 of this series, I'll show how to use the DirectorySearcher
class and how to modify attributes.
Robbie Allen is the coauthor of Active Directory, 2nd Edition and the author of the Active Directory Cookbook.
O'Reilly & Associates recently released (April 2003) Active Directory, 2nd Edition.
Sample Chapter 14, Upgrading to Windows Server 2003, is available free online.
You can also look at the Table of Contents, the Index, and the Full Description of the book.
For more information, or to order the book, click here.
Return to ONDotnet.com
Copyright © 2009 O'Reilly Media, Inc.