Pages

Wednesday, June 08, 2005

ADS Authentication in .NET

Introduction


Directory Services have gained a lot of traction over the last few years. Directories are repositories of information and can be utilized in many different ways. For example it can be a repository of users and groups or a repository of network entities like computers, printers, network shares, files, etc. A directory is nothing else then your yellow pages or white pages where you can find objects and information about each object. Whenever you require to store objects and properties about each object and need to be able to search these objects plus bind to an object and retrieve its properties then Directory Services is a very good candidate.


But what is the difference between a Directory Service and a relational database like Microsoft SQL Server? A relational database provides access to data and Directory Services provide access to objects. Take as an example users and groups to which users can belong to. In the case of a relational database you would create a User table, a Group table and a GroupAssign table where you can assign users to a group. Finding all users of a group entails querying the Group table to find the ID of the group, then querying the GroupAssign table to get all the assigned users and then querying for each user the User table to get the information about each user. You could do this as well with one query which joins these tables together. Depending on your choice, a DataReader or DataAdapter with a DataSet, you have the data in flat format available in an array or DataSet. If you want the rest of your application to interact with the data in an object orientated way, then you would write your own object wrapper, for example a User object, which then internally interacts with the underlying data.

That is exactly what directory services provide you with. Each directory comes with its own schema which allows you to define new object types and attribute (property) types. It allows you then to create new objects of this type, set the properties of the object and then store the object in a directory container. It also allows you to bind to an object via the DirectoryEntry type or search your directory via the DirectorySearcher type and then interact with these objects. You can read its properties or change its properties and then persist it back to the directory. You interact with objects and properties. And directories make it very easy to add new object types and attributes through their schema.

There are many directory services available on the market, for example the IBM Tivoli Directory Server, the Novell eDirectory or the Microsoft Active Directory. Active Directory is mostly utilized for managing your windows network infrastructure. The Active Directory schema can be extended so applications can store its objects and properties. But this is rarely used as IT managers want to protect the integrity of the network infrastructure and Active Directory is a key part to that. Microsoft addressed this issue with Windows 2003 by releasing Active Directory Application Mode.

ADAM – Active Directory Application Mode

Active Directory Application Mode is a standalone version of Active Directory and runs only on Windows 2003. It has been released after Windows 2003 has been shipped and can be downloaded from the following link. Active Directory Application Mode does not replace Active Directory. Active Directory is intended for managing your windows network infrastructure while ADAM is intended as directory service for applications, for example to store your application specific security information, etc. Active Directory runs as a system service and requires DNS while ADAM runs as a user service and does not require DNS at all. It is simple to install and to uninstall and you can run multiple instances of ADAM on one Windows 2003 server, for example one for each application. Follow the link above to download ADAM (you need the file ADAMRedistX86.exe), unzip the file and run the "adamsetup.exe" setup. On the first screen it asks whether you want to install "ADAM and ADAM administration tools" or "ADAM administration tools only". Choose the later option if you want to install the ADAM administration tools to be able to administrate an ADAM instance from a remote machine. In our case we want to install a fresh ADAM instance so we choose the first option. Next it asks whether to install "a unique instance" or "a replica of an existing instance". The second option makes it very easy to replicate an existing ADAM instance, for example you have created an ADAM instance on your development machine then extended its schema and now you want to create a separate QA or production ADAM instance. Setup will replicate all schema changes to this new instance of ADAM. We choose the first option as we are installing a new instance of ADAM. Next you give this ADAM instance a name, e.g. "My application directory". Next you enter the ports on which this ADAM instance will listen, by default 389 and 636 when using SSL. If you install multiple instances of ADAM then each one would get its own unique port to listen. Or if you want to protect access in production you might want to choose a random port. The default port 389 is the standard port used for LDAP (Lightweight Directory Access Protocol). Leave the default ports for your first ADAM instance.


Next it asks you if you want to create an application directory partition. ADAM stores directory data in a hierarchical, file-based directory store. The default location of the directory store is "Program Files\Microsoft ADAM\\Data\adamntds.dit". A directory store is organized in logical directory partitions, also called naming contexts. There are three different types of partitions, the configuration partition, the schema partition and the application partition. Each directory can only have one instance of a configuration partition and one instance of a schema partition. These two partitions are always created as part of the ADAM installation. A directory can contain one or more application partitions. Each partition is given a "distinguished name" also called DN, a unique name how to reference the partition. The three partition types are used for the following purpose:
Configuration partition – This partition contains configuration information for ADAM. This includes replication information, security information as well as a list of all partitions (under the container CN=Partitions).

Schema partition – This partition contains all the object types and attribute types defined in this directory. Through this partition you can extend the out-of-the-box schema with your own object types and attribute types. Before you are able to create new objects or attributes of this type you need to define them in the schema partition.
Application partition – This partition holds the application specific information. You can create multiple application partitions, each for a different application or for different purposes of the same application. You can create containers (like folders) and new objects in each application partition. The schema partition defines which type of containers, objects and attributes you can create.
You can create an application partition while installing ADAM (select "Yes, create an application directory partition") or you can create it later (select "No, do not create an application directory partition"). If you create an application directory, then you need to enter the distinguished name, a unique name, of the partition. The distinguished name consists of one or multiple parts, like nodes of a hierarchical tree. This allows you to build a hierarchy. Each part consists of DN attribute and a value. The following DN attributes are allowed:

DC – Domain Component
C - Country
L – Location
O – Organization
OU – Organizational Unit
CN – Common Name

There is no fixed hierarchy (order) how these DN attributes appear. But they allow you to build a hierarchy reflecting the customer's organization or your application structure. Here are a few examples:

CN=MyApplication,DC=MyCompany,DC=COM
OU=Engineering,O=MyCompany,C=US
CN=MyApplication,C=US,DC=MyCompany,DC=COM

As we are installing the first instance of ADAM, select "Yes, create an application directory partition" and enter as partition name "O=MyCompany,C=US". On the next screen you enter the location where the directory store is placed. We leave the default at "Program Files\Microsoft ADAM\My application directory\data". Next you select the windows account to use for running this ADAM instance. Each ADAM instance creates a windows service with the name of the ADAM instance, in our example "My application directory" and this account is used to run this window service. Next you need to select a windows user or group which has administrative rights to this instance of ADAM. This user or group is allowed to use the ADAM tools to view the directory objects, administrate the schema, etc. The last step allows you to import some pre-defined directory objects. Select the "MS-User.ldf" so we are able to create users in this directory.

When the installation is complete you find a new menu item under "Start All Programs ADAM". All ADAM tools are placed under the folder "Windows\ADAM", the directory store is placed under "Program Files\Microsoft ADAM\" and a new windows service has been added with the name of your ADAM instance. Go through the same process to install an additional ADAM instance. Setup will detect that another instance is running and will suggest for each ADAM instance another port, by default 50,001, etc. Each instance has also its own entry under "Add or Remove programs" so you can uninstall each ADM instance individually. Each entry is called "Adam Instance ", in our case "Adam Instance My application directory". Uninstalling will only remove the selected ADAM instance and never the ADAM tools itself located under "Windows\ADAM".
A look at how to administrate ADAM

Open the tool "ADAM ADSI Edit" through the menu "Start All Programs ADAM". ADAM ADSI Edit allows you to connect to ADAM instances and administrate each partition in the directory store as well as create new partitions. Right click on the item "ADAM ADSI Edit" in the list on the left side and select "Connect to" from the popup menu. This allows you to connect to different partitions of any available ADAM instance (remote or local). Let's first connect to the Configuration and Schema partition. Select the radio button "Well-known naming context" and select from the drop down list "Configuration". Enter the server name and port address if the ADAM instance is running on a different machine or on a different port. Click ok to navigate the containers and objects of the Configuration partition. Navigate the tree with the plus and minus sign in front of each node. The name of the top container in the Configuration naming context is "CN=Configuration,CN={GUID}". Under there you find a container called "CN=Partitions". Selecting it shows on the right side the three different partitions available in this directory store. The Schema partition, the Configuration partition (the one you are just viewing) and the application partition we created during the install. Repeat the same process to connect to the Schema partition. The top container in the Schema partition is named "CN=Schema,CN=Configuration,CN={GUID}". Selecting it shows on the right side all attribute and object types defined in this schema. You see that both the Configuration and the Schema partition have a GUID in the distinguished name to make each unique. So how can you programmatically discover these partitions without knowing the GUID? There is a third well known naming context called RootDSE. You can connect to this naming context using the DirectoryEntry using the LDAP path "To%20connect%20to%20the%20RootDSE%20naming%20context%20from%20the%20tool" all click and list the from select for enter menu. to of as our use it or we in is application three In will example this created
Let's see how we can create our own object and attribute type and then create an instance of that object in our application partition. First navigate to the Schema partition to create these new types. Right click on "CN=Schema,CN=Configuration,CN={GUID}" and select from the popup menu "New Object". The dialog shows you which object types you can create and we choose "classSchema". First you enter the common name (without the ‘CN=' prefix), for example "UserProperties". Next you need to enter the "subClassOf" value, meaning this class is a child of which other class. You can use the name of any other object type defined in this schema and therefore build an object hierarchy. The top most object type is called "top" and almost all other object types are a child of this one. Next you need to enter the "governsID", which is a unique ID for this object type. The value for application objects is always "1.2.840.113556.1.6.1.2.x", the last digit being the unique ID you give your object. You can not create the object type if there is already one with the same common name or "governsID" in the schema. When done go to the end of the list of object and attribute types (on the right side) and you will find your new object type you just created.
Next we create a new attribute type which we then assign to the newly created object type, meaning the object UserProperties is allowed to have a property of this type. Right click again on "CN=Schema,CN=Configuration,CN={GUID}" and select from the popup menu "New Object". This time choose "attributeSchema" from the available object types. Next we enter again the common name without the "CN=' prefix, for example "HomeURL". Each attribute can be of a certain type and the type defines the value of "oMSyntax" and "attributeSyntax". Go to the MSDN article Choosing a Syntax, select the type you want for your attribute and note down these two values. Enter the "oMSyntax" value, for example for a string this is "20". For the "lDAPDisplayName" use the same value as for the common name, for example "HomeURL". Next enter the value for "isSingleValued" which is true for a single value property and false if the property can have multiple values, an array of values. Next enter the "attributeSyntax" you noted down, for a string this is "2.5.5.4". Finally you enter the "attributeID" value which is a unique ID for this attribute type. The value for application attributes is always "1.2.840.113556.1.6.1.1.x", the last digit being the unique ID you give your attribute. You can not create the attribute type if there is already one with the same common name or "attributeID" in the schema or if the value of "oMSyntax" and "attributeSyntax" does not match the list of valid types. When done go to the end of the list of object and attribute types (on the right side) and you will find your new attribute type you just created.
You can also register the object and attribute ID's so no one else can reuse them. For more information go to the MSDN article Obtaining an Object Identifier from Microsoft. Next we assign the attribute type "HomeURL" to the new object type "UserProperties". This unfortunately can not be done through ADAM ADSI Edit. The logical choice would be to open the properties of the "UserProperties" object type and then add the attribute to the "allowedProperties" property. But this will give you the following error message "Modification of a constructed attribute is not allowed". This needs to be done through the "ADAM Schema" MMC snap-in. Go to "Start Run" and type in "mmc /a". This opens an empty Management Console and allows you to add different snap-ins. Go to the menu "File Add/Remove Snap-in", click on the add button and select the "ADAM Schema" snap-in. When done this shows you an entry called "ADAM Schema" in the list on the left side. Right click on it and select "Change ADAM Server" from the popup menu. Enter the ADAM server and the port address where the ADAM instance is running on, for example "localhost" and "389". When done you see two entries in the list – "Classes" and "Attributes". You can quite actually also create object and attribute types through this snap-in. You enter the same values but the user interface is different.
Now find your object type under "Classes", right click on it and select properties. Go to the Attributes tab, click the Add button, select the "HomeURL" attribute and click ok. You can add as many attributes to the optional attributes list as required but you can not add mandatory attributes. If you need mandatory attributes then create the object type in the "ADAM Schema" snap-in and select them while creating the object type itself. When done click ok. Click on the object type and see on the right side all the attributes belonging to it. You see if they are system attributes, whether it is mandatory or optional and also which class they have been added to. So this view shows you also all the inherited attributes. You see now also the attribute you just added. Going back to ADSI Edit and looking at the property "allowedAttributes" of the object type UserProperties does unfortunately not show this new attribute we just added. This shortcoming in ADSI Edit can unfortunately be rather inconvenient!
Let's finally go back and create an instance of this object type in our application partition "O=MyCompany,C=US". Navigate to the top container "O=MyCompany,C=US" in this partition, right click on it and select "New Object" from the popup menu. But our object type does not show up. Create first a container of the name "Test". Now right click on the container "CN=Test" and select "New Object" from the popup menu. This time it shows our object type and we can create an instance of it, for example a UserProperties object called Klaus. When you define an object type you also define which other object types are possible as superiors. By default this is only set to the object type "container" and this is why you can not create a new object of type UserProperties under the "O=MyCompany,C=US" container (because that one is of type "organization"). View the properties of the "O=MyCompany,C=US" container and look for the "objectClass" property. As you can see it is set to "top,organization", the right most being its type and the types to the left being its parents (so top is the parent). If you view the properties of the "CN=Test" container we created you see that the "objectClass" is set to "top,container" and new object types by default can have "container" as its parent. To change that we go back to the "ADAM Schema" snap-in and open up the properties of our UserProperties object type. Select the tab "Relationship" and click on the "Add Superior" button. Add the "organization" object type, restart the windows service for this ADAM instance, go back to ADSI Edit and now you can also create a UserProperties object under the top container "O=MyCompany,C=US".
You can also import or export existing schema definitions or directory objects using the "ldifde" tool, located in the folder "windows\adam". Here is the syntax how to import a schema definition (for example the "MS-User.ldf" file):
ldifde -i -f -s -b -c "CN=Schema,CN=Configuration,DC=X" "CN=Schema,CN=Configuration,CN={GUID}"
The option "-i" specifies a data import, the option "-f" is followed with the file name which has the schema definition to import, the option "-s" is followed with a windows credentials which has administrative access to ADAM and the option "-c" tells to replace the DN in the schema file with the proper DN of your DAM instance. See above how to find out the schema DN through ADAM ADSI Edit. Here is the syntax how to export a schema definition (for example the UserProperties object type we created):
ldifde -f -s -b -d "CN=Schema,CN=Configuration,CN={GUID}" -r "((cn=UserProperties)(cn=HomeURL))"
The default option is data export and with the option "-f" you specify the file to create, with "-d" the DN to connect to, in this example the DN to the schema partition for this directory store, and the option "-r" the filter to apply, in our case the name of the object and attribute type we created. This makes it easy to export your schema definition and then re-import at another ADAM instance. Another usage would be to export objects from one ADAM instance and then re-import it to another one.
ADAM, Active Directory, as well as most other directories, do not allow you to delete object or attribute types. You can mark types as dysfunctional by setting the "isDefunct" property to true. After restarting the window service for this ADAM instance you are no longer able to create objects or attributes of this type. Already existing objects of that type will still remain in the directory, but you will no longer see the class name but rather the object ID. So before you start changing your schema, make sure you know what changes you want to apply or if you need to be able to experiment around create a separate ADAM instance which you can delete afterwards.
You can use the tool "ldp" located in the folder "windows\adam" to create a new application partition. First you need to connect to an ADAM instance by choosing the menu "Connection Connect". Enter the server name and the port, for example "localhost" and 389. Next you need to bind to the ADAM instance, meaning authenticate so you can access the directory. Go to the menu "Connection Bind" and enter a user credential which has administrative access to the ADM instance (make sure to enter a domain name; choose the machine name if you are not part of a domain). Next you can create the partition. Go to the menu "Browse Add Child" and enter the distinguished name for the new partition, for example "O=MyCompany,C=CA". Next you need to add two attributes. Enter in the text box "Attribute" the value "objectClass" and in the "Values" text box the value for this attribute. This value depends very much on the partition DN you entered. In our case the partition name ends with "O" for organization so we enter as value "organization". If the partition DN ends with "OU" enter "organizationalUnit", if it ends with DC you enter "domainDNS" and if it ends with "CN" then enter "container". Next click the "Enter" button to add this attribute/value pair to the list. Then enter in the "Attribute" text box "instanceType" and in the "Values" text box "5" and click again the "Enter" button" to add this attribute/value pair. Now click "Run" to add this new partition. You will see in the right side pane a message saying "Added {O=MyCompany,C=CA}". Any error shown needs to be resolved. Before you can access the new partition you need to re-start ADAM ADSI Edit so that the right security context is applied when binding to this new partition. You will be able to bind to the partition but not able to create any objects before you re-start ADAM ADSI Edit. In ADAM ADSI Edit you bind to this new partition as to any other naming context (see above). If you want to delete a partition, then bind to the Configuration naming context, go to the container "CN=Partitions", right click on the partition (shown on the right side) and select "Delete" from the popup menu. Be careful, deleting a partition is unrecoverable.
IIS and WinNT directory service provider
Directory Services is the .NET wrapper for ADSI (Active Directory Services Interface). LDAP is one of many ADSI providers available. Two other well known ADSI providers are IIS and WinNT. IIS allows access through ADSI to the underlying IIS met-database. You can browse existing settings, web sites and web folders as well as create new web sites and web folders or change the IIS settings. You connect to the IIS ADSI provider through IIS://, for example "IIS://localhost". For example to list all web sites you would bind to "IIS://localhost/W3SVC. Later in the article it is explained how to create new web sites and web folders. The IIS ADSI provider has a known issue with reading and writing properties. This has been resolved with Windows 2003 SP1 and Windows XP SP2".
The WinNT ADSI provider gives access to the windows users, groups and windows services. You bind to this provider through WinNT://, for example WinNT://localhost. Later in the article it is explained how to create users, groups and windows services.
Introduction to the .NET Directory Services
The .NET framework provides access to directory services through types build on top of ADSI. You need to reference the System.DirectoryServices.dll assembly and import the System.DirectoryServices namespace in your project. You first need to bind to a directory object through the DirectoryEntry type. When instantiating an instance of that type you provide the path to the directory object you want to access. The path consists of the provider, followed by the machine where the provider is residing, optional the port the provider is listening at and then the relative path to the actual directory object – "Provider://Machine:Port/Path". The path "
LDAP://localhost:389/O=MyCompany,C=US" for example binds to the root container of the application partition we created in ADAM (see previous section).

By default DirectoryEntry uses the credentials of the windows user running the code. You can specify with the Username and Password property the user credentials to use when binding to the directory object. The Username can contain the domain name, for example "MyDomain\Administrator". The Children property returns a DirectoryEntries object which is a collection of child directory objects. It depends on the type of directory object you bind whether it can have children or not. For example if you bind to a container (CN=) or an organization (O=) then the directory can have child objects which you can access through the Children property. If you bind to an organizational person (CN=) or a user (CN=) then the Children property is an empty collection as these objects are not allowed to have children's. The following code sample shows how you can bind to a directory object and then add all its descendants to a tree view.

public void FillTreeView(string AdsiPath,TreeNodeCollection NodeCollection)

{// connect to the selected directory

pathDirectoryEntry DirEntry = new DirectoryEntry(AdsiPath);
try{

// now loop through all the childrenforeach

(DirectoryEntry ChildEntry in DirEntry.Children)

{

// add the node to the tree

viewTreeNode NewNode = NodeCollection.Add(ChildEntry.Path, ChildEntry.Name);
// add any child entries for this

entryFillTreeView(ChildEntry.Path, NewNode.Nodes);}}
// catch any exception accessing the directory

objectcatch (Exception){ }
// close the directory

objectfinally{DirEntry.Close();}}

We first instantiate a DirectoryEntry object and pass along the directory path to bind to. Then we enumerate all child objects and add them to the TreeNodeCollection. For each child object found we call the function recursively to find any child objects it might have. This will find any descendants and add them to the tree view. We catch any exception happening. And because Directory Services works with ADSI COM components it is important to call Close() in the finalizer so we free up the underlying COM object. This lists any child object but sometimes it is very useful to apply a filter. You can do that by using the DirectorySearcher type. Here is a code snippet:

public void FillFilteredTreeView(string AdsiPath,string Filter,TreeNodeCollection NodeCollection)

{

// connect to the selected directory

pathDirectoryEntry DirEntry = new DirectoryEntry(AdsiPath);
// create a directory searcher which we wrap around the

// directory object we want to

searchDirectorySearcher Searcher = new DirectorySearcher(DirEntry);
// search only the immediate

children'sSearcher.SearchScope = SearchScope.OneLevel;
try{

// set the filter to apply - is a

property=value collection

// with logical & and , e.g. ((cn=Klaus)(cn=Peter))

Searcher.Filter = Filter;
// perform the sarch and get the result collection

backSearchResultCollection ResultCollection = Searcher.FindAll();
// now loop through all the objects in the result

collectionforeach (SearchResult Result in ResultCollection)

{

// get the found directory

entryDirectoryEntry FoundEntry = Result.GetDirectoryEntry();
// add the node to the tree view

TreeNode NewNode = NodeCollection.Add(FoundEntry.Path, FoundEntry.Name);

// add any child entries for this found directory object

FillTreeView(FoundEntry.Path, NewNode.Nodes);
// close the found

entryFoundEntry.Close();

}
// dispose the search result

collectionResultCollection.Dispose();

}
// catch any exception accessing the directory
object

catch (Exception){ }
// close the directory and searcher object

finally{Searcher.Dispose();DirEntry.Close();

}

}

First instantiate a DirectoryEntry object pointing it to the directory path to search. Next instantiate a DirectorySearcher object and pass along the DirectoryEntry object, telling it this is the directory path to search in. The DirectorySearcher.SearchScope property sets the search scope and has three different values – Base, OneLevel and SubTree. Base searches only the directory path you bound to. OneLevel searches the immediate children of the directory path bound to. And SubTree searches all descendants of the directory path bound to. The code sample sets it to OneLevel to search only the immediate children of the directory path bound to. The DirectorySearcher.Filter property sets the filter to apply for the search. You can filter on any property the directory object has and also use wildcards. The syntax used is property name equals value surrounded with parenthesis, for example "(cn=Klaus)". If you filter on more then one property then you need to specify the logical "&" or "" operator. First you specify the logical operator followed by the list of property/name filters and the whole filter is again surrounded with parenthesis, for example "(&(cn=Klaus)(objectClass=user))" or "((cn=Klaus)(cn=Peter))". The first example finds any object with the name Klaus and of the type user. The second example finds any object with the name Klaus or Peter. You can use the greater, greater equal, equal, less and less then operators (>, >=, =, <, <=). Any combination is possible, for example "(&(objectClass=classSchema)((cn=organizational-unit)(cn=organization)))" searches for all object types with the name organizational-unit or organization. After setting the search scope and filter you call FindAll() to find all matching directory objects. This returns a SearchResultColletion which you can loop through and for each found directory object you get a SearchResult object. The SearchResult.Path property returns the path of the found directory object. You can also get an instance of the found directory object via SearchResult.GetDirectoryEntry(). In our code sample we add each found directory object to the tree view and call FillTreeView() to add any descendants of the directory object to the tree view. Don't forget to call Close() for every directory object we get by calling SearchResult.GetDirectoryEntry(). When done looping through the SearchResultCollection call Dispose() to free up the search result collection. At the end we call Dispose() on the DirectorySearcher object and Close() on the DirectoryEntry object used to bind to the directory path we wanted to search.

The DirectoryEntry.Properties property gives you access to all properties of a directory object. It returns a PropertyCollection, the collection of properties for this directory object. PropertyCollection.PropertyNames returns a collection of all property names and PropertyCollection[PropertyName] gives you access to the value of this property. A property value can be single valued or it can be an array of object values. So you can loop through all property values. Here is a code snippet:

public void GetPropertyList(string AdsiPath,ListBox.ObjectCollection Collection)

{// connect to the selected directory path

DirectoryEntry DirEntry = new DirectoryEntry(AdsiPath);
try{

// loop through all the properties and get the key for each

foreach (string Key in DirEntry.Properties.PropertyNames)

{

string PropertyValues = String.Empty;
// now loop through all the values in the property;

// can be a multi-value property

foreach (object Value in DirEntry.Properties[Key])

PropertyValues += Convert.ToString(Value) + ";";
// cut off the separator at the end of the value list

PropertyValues = PropertyValues.Substring(0, PropertyValues.Length - 1);
// now add the property info to the property list

Collection.Add(Key + "=" + PropertyValues);

}

}
// catch any exception accessing the directory object

catch (Exception){ }
// close the directory object

finally{DirEntry.Close();

}

}

First we instantiate a DirectoryEntry object and bind to the directory object path. Next we loop through all the property names and for each property we loop through all the values. Note that we don't know the type of each value so we use the type "object". We then convert each property value to a string and concatenate them together separated by a semicolon. Each property gets then added to the list box collection in the format property name equals value list. At the end we call again Close() on the DirectoryEntry object.

You can also programmatically find all available ADSI providers on your machine. This information is available in the registry under "HKLM\Software\Microsoft\ADs\Providers". These are typically the IIS, WinNT, LDAP, NDS and NWCOMPAT providers. NDS is the "Novell NetWare Directory Service" provider and NWCOMPAT is the "Novell Netware 3.x (compatible) Directory Service" provider. Here is a code snippet how to get a list of providers.

public static string[] GetListOfDirectoryProviders()

{

// get the HKLM registry key

RegistryKey RegKey = Registry.LocalMachine;
// open the sub-key which contains all the providers

RegistryKey ProviderKey = RegKey.OpenSubKey(ProviderRegKey);
// get the list of the sub-keys

string[] SubKeys = ProviderKey.GetSubKeyNames();
// create the string array which will hold the provider

liststring[] ListOfProviders = new string[SubKeys.Length];
// now add all providers to the array; all providers are

// pointed to the local machine

for (int Count = 0; Count <>

ListOfProviders[Count] = SubKeys[Count] + "://" + Environment.MachineName;
// return the list of providersreturn ListOfProviders;

}

First you get a reference to the HKEY_LOCAL_MACHINE registry key on your local machine by calling Registry.LocalMachine. Then you obtain a reference to the sub-key "\Software\Microsoft\ADs\Providers" which stores a list of all ADSI providers. Last you enumerate all its sub-keys by calling GetSubKeyNames() which returns the ADSI provider prefix IIS, WinNT, LDAP, NDS and NWCOMPAT and for each you add the local machine name so you have a valid directory path, e.g. "LDAP://klauslaptop". This gives you access to all IIS and WinNT directory objects. For LDAP you still need to add the local path, for example "O=MyCompany,C=US" or "RootDSE" if you want to discover all the available partitions in your LDAP directory (see above).
Creating, updating and deleting directory objects

Directory Services allows you also to add new objects and update or delete existing objects. Each directory object has a parent so you need to first bind to a parent directory path and then add a new directory object to its Children collection. If the path you bind to does not allow child objects, for example organizational person (CN=) then you will get a DirectoryServicesCOMException exception. When creating an object you also need to specify its object type, for example "organization". Refer to the provider schema to obtain a list of available object types. Then you can set the property values and invoke methods the ADSI object might expose. Refer to the ADSI provider documentation to obtain a list of properties and methods each object exposes. At the end you call CommitChanges() on the newly created directory object, which writes the changes performed in the cache back to the underlying directory store. Here is a code snippet:
public void AddDirectoryObject(string AdsiParentPath,string ObjectName,string ObjectSchemaName,object[,] Properties,object[,] MethodsToInvoke){// connect to the selected directory parent objectDirectoryEntry DirEntry = new DirectoryEntry(AdsiParentPath);
try{// creates the new directory objectDirectoryEntry NewObject = DirEntry.Children.Add(ObjectName,ObjectSchemaName);
// now loop through all the properties and set themif (Properties != null){for (int Count = 0; Count < value =" Properties[Count,1];}" count =" 0;" direntry =" new" adsiobject =" DirEntry.Children.Find(ObjectName,ObjectSchemaName);" count =" 0;" value =" Properties[Count,1];}" count =" 0;" direntry =" new" adsiobject =" DirEntry.Children.Find(ObjectName,ObjectSchemaName);elseAdsiObject" direntry =" new">mail2kaushal@gmail.com. I want to hear if you learned something new. Contact me if you have questions about this topic or article.