Understanding the System. DirectoryServices Namespace


Understanding the System. DirectoryServices Namespace

The System.DirectoryServices namespace provides access to the data stored within Active Directory. This chapter won’t discuss the general structure of Active Directory or the utilities you can use to access it. You can find that information in the “Managing Directory Services” section of Chapter 12. However, this chapter does discuss the essentials of the System.DirectoryServices namespace and provides you with an example of how to use it (see Listing 2.4).

Tip

You can find a complete description of the System.DirectoryServices namespace at http://msdn.microsoft.com/library/en-us/cpref/html/frlrfSystemDirectoryServices.asp. Checking for changes to this namespace is important because it could change based on Microsoft changes to its server software, as well as the .NET Framework. Of course, you only need to consider this namespace if you’re using Active Directory on your network.

DirectoryServices Namespace Overview

The System.DirectoryServices namespace is simply a managed version of the Active Directory Services Interface (ADSI) interface you may have used in the unmanaged environment. It enables you to access Active Directory using managed, rather than unmanaged, techniques in many situations. You’ll run into two problems when using this namespace. First, CLR doesn’t recognize some Active Directory data types, even though it seems as if it should. Second, the DirectoryServices namespace doesn’t provide support for every Active Directory interface, so you’ll need to resort to COM to gain access to these interfaces. I’ll discuss both problems in the sample applications.

The DirectoryServices namespace includes class counterparts for the ADSI interfaces. The focus of the classes provided in the System.DirectoryServices namespace is implementation of the IADsOpenDSObject interface. (See http://msdn.microsoft.com/library/en-us/netdir/adsi/core_interfaces.asp for a list of core ADSI interfaces and http://msdn.microsoft.com/library/en-us/netdir/adsi/iadsopendsobject.asp for a list of IADsOpenDSObject interface features.) Once you have an open connection to Active Directory, you can begin to manipulate the data it contains.

You can perform most tasks using managed code. In the few cases where you need to perform a task using the COM interfaces, you can obtain a copy of the object using built-in method calls. Of course, several issues besides ease of access remain. One of the most important issues is thread safety—an important consideration in a distributed application. Generally, you’ll find that public static class members are thread safe, while instance members aren’t. This limitation means you must use class members carefully and take steps to ensure method calls occur in a thread safe manner.

DirectoryServices Namespace Example

The example application performs some essential Active Directory tasks. It accepts a general query for usernames that you use to select an individual user. The application uses this information to create a specific user query, and then displays certain information about that user including their department and job title. Active Directory also provides a note field for each user entry that you can use to make comments. The application enables you to view the current comment and modify it as needed.

Accessing Active Directory

The first task is to gain access to Active Directory generally. Listing 2.4 shows the code you’ll need to create a general query. Note that this code works with a WinNT or a LDAP path. (You can find this code in the \Chapter 02\C#\Monitor or \Chapter 02\VB\Monitor folder of the source code located on the Sybex Web site.)

Listing 2.4 Accessing Active Directory

start example
private void btnQuery_Click(object sender, System.EventArgs e) {    // Clear the previous query (if any).    lvUsers.Items.Clear();    // Add the path information to the DirectoryEntry object.    ADSIEntry.Path = txtQuery.Text;    // The query might fail, so add some error checking.    try    {       // Process each DirectoryEntry child of the root       // DirectoryEntry object.       foreach (DirectoryEntry Child in ADSIEntry.Children)       {          // Look for user objects, versus group or service objects.          if (Child.SchemaClassName.ToUpper() == "USER")          {             // Fill in the ListView object columns. Note that the             // username is available as part of the DirectoryEntry             // Name property, but that we need to obtain the             // Description using another technique.             ListViewItem lvItem  = new ListViewItem(Child.Name);             lvItem.SubItems.Add(                Child.Properties["Description"].Value.ToString());             lvUsers.Items.Add(lvItem);          }       }    }    catch (System.Runtime.InteropServices.COMException eQuery)    {       MessageBox.Show("Invalid Query\r\nMessage: " +                       eQuery.Message +                       "\r\nSource: " + eQuery.Source,                       "Query Error",                       MessageBoxButtons.OK,                       MessageBoxIcon.Error);    } } private void lvUsers_DoubleClick(object sender, System.EventArgs e) {    // Create a new copy of the Detail Form.    DetailForm ViewDetails  =       new DetailForm(lvUsers.SelectedItems[0].Text,                      lvUsers.SelectedItems[0].SubItems[1].Text,                      txtQuery.Text);    // Display it on screen.    ViewDetails.ShowDialog(this); }
end example

The application begins with the btnQuery_Click() method. It uses a ListView control to display the output of the query, so the first task is to clear the items in the ListView control. Notice that I specifically clear the items, not the entire control. This prevents corruption of settings such as the list headings.

You can configure all elements of the ADSIEntry (DirectoryEntry) control as part of the design process except the path. The application provides an example path in the txtQuery textbox that you’ll need to change to meet your specific server configuration.

The ADSIEntry.Children property is a collection of DirectoryEntry objects. The application won’t fail with a bad path until you try to access these DirectoryEntry objects, which is why you want to place the portion of the code in a try…catch block. Notice how the code uses a property string as an index into each DirectoryEntry object. Even if the property is a string, you must use the ToString() method or the compiler will complain. This is because C# views each DirectoryEntry value as an object, regardless of object type.

The output of this portion of the code can vary depending on the path string you supply. Figure 2.5 shows the output for a WinNT path while Figure 2.6 shows the output for a LDAP path. Notice that the actual DirectoryEntry value changes to match the path type. This means you can’t depend on specific DirectoryEntry values within your code, even if you’re working with the same Active Directory entry. For example, notice that the entry for George changes from WinNT to LDAP. The WinNT entry is simple, while the LDAP entry contains the user’s full name and the CN qualifier required for the path.

click to expand
Figure 2.5: The WinNT path tends to produce easy-to-read DirectoryEntry values.

click to expand
Figure 2.6: LDAP paths tend to produce copmlex DirectoryEntry values that you'll need to clean up for user displays.

Getting Detailed Active Directory Information

Once you have access to the usernames, it’s possible to gain details about a specific user. The sample application performs this task using a secondary form. When a user double-clicks on one of the names, the lvUsers_DoubleClick() method creates a new copy of the secondary form and passes everything it needs to create a detailed query. Notice that the code uses the ShowDialog() method, rather than the Show() method. This ensures that one query completes before the user creates another one.

Most of the activity for the details form occurs in the constructor. The constructor accepts the username, description, and path as inputs so it can create a detailed query for specific user information. Listing 2.5 shows the constructor code for this part of the example.

Listing 2.5 The Details Form Displays Individual User Information

start example
public DetailForm(string UserName, string Description, string Path) {    string   UserPath;   // Path to the user object.    bool     IsLDAP;     // LDAP provides more information.    // Required for Windows Form Designer support    InitializeComponent();    // Set the username and description.    lblUserName.Text = "User Name: " + UserName;    lblDescription.Text = "Description: " + Description;    // Determine the path type and create a path variable.    if (Path.Substring(0, 4) == "LDAP")    {       IsLDAP = true;       // LDAP requires some work to manipulate the path       // string.       int CNPosit = Path.IndexOf("CN");       UserPath = Path.Substring(0, CNPosit) +                  UserName + "," +                  Path.Substring(CNPosit, Path.Length - CNPosit);    }    else    {       IsLDAP = false;       // A WinNT path requires simple concatenation.       UserPath = Path + "/" + UserName;    }    // Set the ADSIUserEntry Path and get user details.    ADSIUserEntry.Path = UserPath;    ADSIUserEntry.RefreshCache();    // This information is only available using LDAP    if (IsLDAP)    {       // Get the user’s title.       if (ADSIUserEntry.Properties["Title"].Value == null)          lblTitleDept.Text = "Title (Department): No Title";       else          lblTitleDept.Text = "Title (Department): " +             ADSIUserEntry.Properties["Title"].Value.ToString();       // Get the user’s department.       if (ADSIUserEntry.Properties["Department"].Value == null)          lblTitleDept.Text = lblTitleDept.Text + " (No Department)";       else          lblTitleDept.Text = lblTitleDept.Text + " (" +             ADSIUserEntry.Properties["Department"].Value.ToString()             + ")";    }    // This information is common to both WinNT and LDAP, but uses    // slightly different names.    if (IsLDAP)    {       if (ADSIUserEntry.Properties["lastLogon"].Value == null)          lblLogOn.Text = "Last Logon: Never Logged On";       else       {          LargeInteger         Ticks;      // COM Time in Ticks.          long                 ConvTicks;  // Converted Time in Ticks.          PropertyCollection   LogOnTime;  // Logon Property Collection.          // Create a property collection.          LogOnTime = ADSIUserEntry.Properties;          // Obtain the LastLogon property value.          Ticks = (LargeInteger)LogOnTime["lastLogon"][0];          // Convert the System.__ComObject value to a managed          // value.          ConvTicks = (((long)(Ticks.HighPart) << 32) +                      (long) Ticks.LowPart);          // Release the COM ticks value.          Marshal.ReleaseComObject(Ticks);          // Display the value.          lblLogOn.Text = "Last Logon: " +             DateTime.FromFileTime(ConvTicks).ToString();       }    }    else    {       if (ADSIUserEntry.Properties["LastLogin"].Value == null)          lblLogOn.Text = "Last Logon: Never Logged On";       else          lblLogOn.Text = "Last Logon: " +             ADSIUserEntry.Properties["LastLogin"].Value.ToString();    }    // In a few cases, WinNT and LDAP use the same property names.    if (ADSIUserEntry.Properties["HomeDirectory"].Value == null)       lblHomeDirectory.Text = "Home Directory: None";    else       lblHomeDirectory.Text = "Home Directory: " +          ADSIUserEntry.Properties["HomeDirectory"].Value.ToString();    // Get the text for the user notes. Works only for LDAP.    if (IsLDAP)    {       if (ADSIUserEntry.Properties["Info"].Value != null)          txtNotes.Text =             ADSIUserEntry.Properties["Info"].Value.ToString();       // Enable the Update button.       btnUpdate.Visible = true;    }    else    {       txtNotes.Text = "Note Feature Not Available with WinNT";    } }
end example

The application requires two methods for creating the path to the user directory entry. The WinNT path is easy—just add the UserName to the existing Path. The LDAP path requires a little more work in that the username must appear as the first “CN=” value in the path string. Here’s an example of an LDAP formatting user directory entry path.

LDAP://WinServer/CN=George W. Smith,CN=Users,DC=DataCon,DC=domain

Notice that the server name appears first, then the username, followed by the group, and finally the domain. You must include the full directory entry name as presented in the ADSI Viewer utility. This differs from the presentation for a WinNT path, which includes only the user’s logon name.

The process for adding the path to the DirectoryEntry control, ADSIUserEntry, is the same as before. In this case, the control is activated using the RefreshCache() method. Calling RefreshCache() ensures the local control contains the property values for the user in question.

LDAP does provide access to a lot more properties than WinNT. The example shows just two of the additional properties in the form of the user’s title and department name. While WinNT provides access to a mere 25 properties, you’ll find that LDAP provides access to 56 or more. Notice that each property access relies on checks for null values. Active Directory uses null values when a property doesn’t have a value, rather than set it to a default value such as 0 or an empty string.

WinNT and LDAP do have some overlap in the property values they provide. In some cases, the properties don’t have precisely the same name, so you need to extract the property value depending on the type of path used to access the directory entry. Both WinNT and LDAP provide access to the user’s last logon, but WinNT uses LastLogin, while LDAP uses lastLogon.

WinNT normally provides an easy method for accessing data values that CLR can understand. In the case of the lastLogon property, LDAP presents some challenges. This is one case when you need to use the COM access method. Notice that the lastLogon property requires use of a LargeInteger (defined in the ACTIVEDS.TLB file). If you view the property value returned by the lastLogon property, you’ll see that it’s of the System.__ComObject type. This type always indicates that CLR couldn’t understand the value returned by COM. Notice that the code converts the COM value to a managed type, then releases the COM object using Marshal.ReleaseComObject(). If you don’t release the object, your application will have a memory leak—so memory allocation problems aren’t quite solved in .NET, they just don’t occur when using managed types. The final part of the conversion process is to change the number of ticks into a formatted string using the DateTime.FromFileTime() method.

As previously mentioned, the sample application shows how to present and edit one of the user properties. The Info property is only available when working with LDAP, so the code only accesses the property if you’re using an LDAP path. The code also enables an Update button when using an LDAP path so you can update the value in Active Directory. Here’s the simple code for sending a change to Active Directory.

private void btnUpdate_Click(object sender, System.EventArgs e) {    // Place the new value in the correct property.    ADSIUserEntry.Properties[“info”][0] = txtNotes.Text;    // Update the property.    ADSIUserEntry.CommitChanges(); }

The application uses a double index when accessing the property to ensure the updated text from txtNotes appears in the right place. All you need to do to make the change permanent is call CommitChanges(). Note that the change will only take place if the user has sufficient rights to make it. In most cases, COM will ignore any update errors, so you won’t know the change took place unless you actually check the entry. Figure 2.7 shows the LDAP output for the sample application.

click to expand
Figure 2.7: The LDAP output is more complete than the WinNT output, but requires more work as well.




.Net Development Security Solutions
.NET Development Security Solutions
ISBN: 0782142664
EAN: 2147483647
Year: 2003
Pages: 168

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net