Property and Method Overview
Before we start diving into details on DirectoryEntry, let's introduce all of the class members.
Constructors
DirectoryEntry contains five public constructors that allow us to initialize the class state with varying default values. The constructors allow us to initialize the Path, Username, Password, and AuthenticationTypes properties directly. We can also change these values after constructing the object by setting the corresponding properties.
In general, we recommend using the full constructor that takes all four of these parameters:
DirectoryEntry entry = new DirectoryEntry( "LDAP://rootDSE", null, null, AuthenticationTypes.Secure);
The advantage is that we are more explicit in our intentions and it takes less code to set all of the values than it would to set the properties individually. There are also some subtle differences between how .NET 1.x and 2.0 set default values that could lead to potential migration surprises (see Chapter 8), so being explicit can help mitigate those risks.
The fifth constructor allows us to pass in an existing object derived from IADs to initialize a new DirectoryEntry. While we rarely use this constructor in practice, it is nice to have it there if we need it.
Properties
DirectoryEntry contains 16 properties that are used for a variety of purposes. We'll be using these properties extensively throughout the book, so it is important to explain them now.
AuthenticationTypes
This is a read-write property that allows us to change the way the object behaves when it connects to the directory. It can also be initialized via one of the constructors. Understanding what all of the AuthenticationTypes values do is critical to our success with SDS. We cover them in detail later in this chapter, in the section AuthenticationTypes Explained.
Children
This is a read-only property that provides a way to enumerate the children of DirectoryEntry using a DirectoryEntries object. This property is used for adding and removing objects from the directory tree as well as for enumerating children.
Guid
This is a read-only property that provides direct access to the objectGUID attribute from Active Directory or ADAM as a System.Guid struct.
Name
This is a read-only property that provides the relative distinguished name (RDN) attribute value for this entry in the LDAP directory as a string. This value is supplied by the underlying IADs ADSI object.
NativeGuid
This is a read-only property that provides direct access to the objectGUID attribute from Active Directory or ADAM as a string using the octet string format actually returned by the LDAP API. The Guid property is actually based on this value, which is derived directly from the underlying IADs ADSI object. We discuss Guid and NativeGuid in more detail in the upcoming section on GUID binding.
NativeObject
This is a read-only property that provides direct access to the underlying IADs ADSI object that DirectoryEntry wraps. It is primarily used in COM interop scenarios.
ObjectSecurity (New in 2.0)
This new read-write property in .NET 2.0 allows access to the Active Directory and ADAM security descriptor using the new .NET 2.0 managed access control list (MACL) classes. These new classes provide a unified system for modifying Windows security descriptors across the entire framework. We discuss Active Directory and ADAM security in Chapter 8.
Warning: Keep DirectoryEntry Alive When Using NativeObject!
When using NativeObject, we need to be sure to keep the DirectoryEntry object alive. Otherwise, the .NET garbage collector may collect the DirectoryEntry object and close the underlying COM object that NativeObject represents. This can lead to some very subtle bugs at runtime! An explicit call to Dispose on DirectoryEntry after we are done using NativeObject should suffice. We cover the proper use of Dispose later in this chapter.
Users of .NET 1.x may still read and modify the security descriptor, but they must use a less desirable COM interop scenario to achieve the same goal. We also discuss this in detail in Chapter 8.
Options (New in 2.0)
Also new in .NET 2.0, this read-only property returns an ActiveDirectoryConfiguration object that we can use to read and write specific configuration options. In .NET 1.x, COM interop is once again required to achieve the same results. Chapters 8 and 10 provide some examples of using this class.
Parent
This is a read-only property that returns a new DirectoryEntry object representing the parent of the current DirectoryEntry object in the directory tree if it is not a root node. The parent object inherits its security context and settings from the child that created it.
Note: Properties Return New DirectoryEntry Instances
A new object is returned each time the property is accessed. The DirectoryEntry object does not maintain a copy internally. If we want to access the same object represented by the Parent property multiple times, we should create a local variable to reference it and use that. Since a new object is allocated each time, we are responsible for cleaning it up as well, through proper use of the Dispose method.
Password
This property allows us to set the password that will be used when binding to the directory, if supplied. In .NET 1.x, this property is read-write, but it has been changed to write-only in .NET 2.0 for security purposes. It can also be initialized via two of the constructors.
Note: Password Property Cannot Change a User's Password
This property is not related to the password value of a user object in the directory and we cannot use it to change that. See Chapter 10 for more details on managing user passwords.
Path
This is a read-write property specifying the path to the object as a string. Most of the constructors allow us to initialize this property directly. Paths are the fundamental way we locate servers and objects in the directory, and we devote an entire section to them in this chapter.
Properties
This is a read-only property that provides access to the attributes on the underlying object in the directory as a PropertyCollection object.
SchemaClassName
This is a read-only property that provides a string containing the name of the schema class for the directory object. This value is provided by the underlying IADs ADSI object.
SchemaEntry
This is a read-only property that provides a DirectoryEntry object representing the schema object upon which the DirectoryEntry object is based. This property is much like the Parent property in that it uses the same security context and settings as the current object and generates a new instance on each access.
UsePropertyCache
This read-write Boolean property allows us to enable and disable the ADSI property cache. The property cache helps with performance by batching together LDAP search and modify operations and generating less network traffic and overhead. We recommend always leaving this in the default state of "true". There is no good reason to turn this off.
Username
This is a read-write property showing the username used to authenticate with the directory. It can also be initialized via two of the constructors. As with the Password property, it is not used to read or change the username on a user object in the directory. The section Username Syntaxes in Active Directory and ADAM, later in this chapter, focuses on the available options.
Methods
In addition to its properties, the DirectoryEntry object implements 11 methods that we will also use throughout the rest of the book.
Close
Close is used to clean up underlying resources used by ADSI. We generally recommend using the Dispose method rather than the Close method. For more information, please refer to the sidebar titled Close or Dispose?
CommitChanges
If we have made modifications to the attribute values in the object and are using the property cache (which is on by default and should always be left on!), then we must call this method to have our changes flushed back to the directory. See Chapter 6 for details.
CopyTo (Overloaded)
LDAP does not support these methods, and since LDAP is all we are talking about here, we will ignore them from now on.
DeleteTree
This method deletes this entry from the directory, as well as every entry under it in the tree. It uses the IADsDeleteOps ADSI interface under the hood to do this.
Dispose
This method is similar to Close, but it supports the IDisposable interface. In general, we recommend using Dispose rather than Close and we explain why in the upcoming sidebar, Close or Dispose?
Exists
This static method attempts to tell us whether an object exists in the directory.
Note: Limitations of the Exists Method
This method works well only when we are not supplying credentials to the DirectoryEntry object. Since it does not allow us to specify the security context used to make this determination, it may not work as desired and may even throw an exception instead of returning false. It is useful in some situations, but it is important to know its limitations. We recommend implementing a custom existence checking method when supplying credentials.
Invoke
This method is a handy wrapper that allows us to invoke methods exposed by the various IADs* interfaces via late binding using the .NET reflection system under the hood. It is most commonly used for Active Directory and ADAM password management, but it useful whenever we need to access an ADSI method that has no direct .NET counterpart.
InvokeGet (New in .NET 2.0)
This method is nearly identical to the Invoke method, except that it allows us to get the value of an ADSI property. The original .NET 1.x implementation only allowed calling ADSI methods via Invoke, so reading properties required more verbose reflection code. This was likely a simple oversight, and it has been corrected with the addition of this method.
InvokeSet (New in .NET 2.0)
This method is just like InvokeGet, except that it sets ADSI property values.
MoveTo
This method allows us to change the location of an object in the directory tree. The overload also allows us to rename an object in the directory. It uses the IADsContainer.MoveHere method under the hood to perform the operation.
RefreshCache
RefreshCache is used to load the ADSI property cache from the LDAP directory. Loading the property cache reads the attribute values for the corresponding directory object and allows us to read and modify them with PropertyCollection and PropertyValueCollection.
The overloaded version is useful for loading specific attribute values into the cache. This is required for attributes such as constructed attributes in Active Directory and ADAM that are not returned by default. It can also be used to limit the list of attributes we wish to read in order to increase performance.
Note that calling this method will erase any changes we have made in the local property cache unless we have already saved them with CommitChanges, so we must be careful with it.
Rename
This method is similar to the MoveTo method that allows us to rename the object in the directory, except that the object is not also moved. Like MoveTo, it also uses the IADsContainer.MoveHere method under the hood to perform the operation.
Warning: Dispose Is Not Optional in .NET 1.x
It is especially important to ensure that Dispose is being called consistently if we are using older versions of the .NET Framework. As of this writing, .NET 1.x contains a number of bugs related to how the finalization was implemented in SDS that caused the underlying COM object never to be released if we failed to call Dispose explicitly! Essentially, the Finalize method called by the finalizer thread does not release the underlying COM object. This caused significant memory leaks in long-running applications with substantial SDS use. These bugs are fixed as of .NET 2.0, but we really do not want to be relying on the garbage collector to clean up our objects anyway, so we should do it ourselves.
Remember also that all of the properties and methods in SDS that return a DirectoryEntry, such as Parent and SchemaEntry, actually return a new instance of a DirectoryEntry object, not a value held in the class' internal state. Therefore, not only is it OK to call Dispose on these objects without fear of corrupting the containing object's internal state, but it is actually our responsibility! Make sure to wrap all of those objects in a using block as well.