|
Understanding ADSI
Have you wanted to add virtual roots through VBScript? Create ISAPI server extensions that install themselves in IIS 4.0? Or script the installation of your entire web site including user permissions? You can do this and more with ADSI.
ADSI (Active Directory Services Interface) is a set of programming interfaces for accessing any data store format. Microsoft products that currently support the ADSI programming interface are Windows NT 4.0 Server, Exchange, IIS, and Site Server. In NT 5.0 ADSI will be the interface to the operating system and in a sense it is the replacement for the registry. The key to ADSI is not the underlying information store but the interface layer itself. With one interface, and one learning curve, you can access all the configuration information of the operating system. Unlike the registry APIs, you can access ADSI from all languages or environments that supports COM.
Caution
Please backup your IIS Metabase before running any of the examples in this article. Here is instruction for backing up the IIS 4.0 Metabase:
- Open MMC with the IIS Snap-in.
- Click on the machine that has the Metabase that you want to back up.
- Right click, from the drop down menu choose Backup/Restore Configuration.
- Click on Create Backup from the Configuration Backup/Restore Dialog.
- Type in a Configuration Backup Name from the Configuration Backup Dialog.
- Click on OK.
- Click on Close.
Object-oriented Databases
ADSI treats any data store as an object-oriented database. With ADSI you can manipulate the data store without the need to learn a query language like SQL. In contrast to a relational database, it is easier to quickly program an object-oriented database. In a relational database the programmer has to understand how an object is broken up and stored; however with an object oriented data store, such as ADSI, this is not true. The programmer only needs to know the name of the object in order to access it.
In order to compare these two database types, consider the information you may keep about an employee: personal information, department information, and payroll information. In a relational database, these three areas may be separated out into 3 tables. While a change to the database may only effect some of the tables, changes will have to be made by the programmer via a query language like SQL. Few programmers care about how the object is stored (except for the Database Administrators). Since ADSI treats all information as objects, the relationships between objects are part of the ADSI implementation and therefore removed from the programmer. Once you have learned to program to one ADSI data store, you can program to any ADSI data store. In comparison, with a relational database you would have to learn a query language for every data store.
Database Structure
The database structure is a hierarchical model. An object node can be a parent of other nodes, a sibling, or a child. Each child node inherits the properties of the parent property. There are two logical parts of the database. The first is the schema that defines the objects and object relationships. The second part of the database is the actual storage for instances of objects. The definition of an Employee object is kept in the schema section, the individual instances of employee objects are stored in the data section. The department object definition is also kept in the schema section. The individual instances of the departments are kept in the data section.
Getting an Object Reference
Unlike COM, you do not have to call Server.CreateObject in order to get a reference to the ADSI object. Instead you call the GetObject method with a parameter that specifies the server and the connection location. Example 1 shows two examples of GetObject. In the first case, the code is getting the Windows NT ADSI provider for 15 Seconds, and in the second example the code is getting an object reference to the IIS metabase.
Example 1
For Windows NT
Set Object = GetObject("WinNT://15seconds" )
For LDAP
Set Object = GetObject("IIS://localhost/w3svc/1")
Some Active Directory Service interfaces use a specific naming convention known as X.500 Distinguished Name. IIS is not using this naming convention but you should become familiar with the X.500 DN conventions if you plan to program to other ADSI data stores. The Distinguished Name must be as specific as necessary to find the object. This naming conventions can be found in the RFC at ftp://src.doc.ic.ac.uk/rfc/rfc1484.txt
Information Retrieval
When looking for information in the LDAP server, you must know if the information is contained in the object instance or in the object’s schema definition. For example, the object name would be contained within the object instance but a list of properties about the object will be contained within the object’s schema. The employee name, hire date, and department are kept in the instance of the employee object. The schema contains definitions of the employee attributes such as data-type and constraints of employee name. Example 2 shows both an object instance and the object’s schema definition
Example 2
Employee Instance
Name: Bob Jones
Hire Date: 1/1/98
Department: Information Technology
Employee Object Instance
Attribute "Name", single value, data-type "text", maxlength 50"
Attribute "Hire Date", single value, data-type "date"
Attribute "Department", single value, data-type "text"
For SQL programmers, the object definition is similar to a table definition. The object instance is similar to a particular row in the table. The difference between SQL and ADSI is that in SQL if the information crosses more than 1 table, the programmer is responsible for managing those relationships. In ADSI, the "interface" is responsible for those relationships.
General Properties of the Default Web Server
The first example of using ADSI will be to look at the properties of a default web server in IIS 4.0. In Example 3, the code will connect to the local machine’s default web site.
Example 3
<%
strMachineName = "localhost" 'domain name
strObjectPath = "W3SVC/1" 'object name
'construct object location in IIS
strPath = "IIS://" & strMachineName & "/" & strObjectPath
Set IISObject = GetObject (strPath) 'connect to IIS metabase
%>
Name = "<%= IISObject.Name %>"<br>
Parent= "<%= IISObject.Parent %>"<br>
SchemaLocation = "<%= IISObject.Schema %>"<br>
Class = "<%= IISObject.Class %>"<br>
Guid = "<%= IISObject.Guid %>"<br>
ADSPath = "<%= IISObject.AdsPath %>"<br>
The output of this code is:
Name = "1"
Parent= "IIS://localhost/W3SVC"
SchemaLocation = "IIS://localhost/schema/IIsWebServer"
Class = "IIsWebServer"
Guid = "{8B645280-7BA4-11CF-B03D-00AA006E0975}"
ADSPath = "IIS://localhost/W3SVC/1"
Note: If you are executing this code from an ASP page, make sure that the logged on user has administrative rights to IIS. Or if you are running anonymously the IUSR_MACHINENAME user must have administrative privileges.
The information retrieved in the last example is generic and can be used against any object, including schema objects. Each object regardless if it is an instance object or a schema object has these properties: Name, Parent,
SchemaLocation, Class, GUID, and ADSPath.
The information contains everything you need to navigate through the hierarchical structure of the database. The "name" property is the name of the object. The "parent" property gives the location of the parent object. The "schemalocation" property is the location of the object’s schema (or object definition). The "class" property is the type of object. The class is the same thing as the schema. The class contains all the attributes that an object can have and is located at the "schemalocation" location. The "GUID" property is the unique identifier for the object. The "ADSPath" is the location of the object you retrieved.
Note: The web server named "1" is the default web site.
Object Properties of the Default Web Server
Now that we know where the object is and what type of object is it, let’s look at the properties that define the default web server. In order to do that, we have to get to the schema for the default web site, find the list of properties associated with it’s class/schema, and then look at the default web server values for those properties. Example 4 shows how to do this:
Example 4
<%
strMachineName = "localhost"
strObjectPath = "W3SVC/1" 'find first web server listed in w3svc
strPath = "IIS://" & strMachineName & "/" & strObjectPath
Set IISObject = GetObject (strPath)
'find location of web server's definition
Set ClassDefinition = GetObject(IISObject.Schema)
%>
<table border=1>
<tr><th>Default Web Server Property</th><th>Default Web Server Value</th></tr>
<tr><td>Name</td><td><%= IISObject.Name %></td></tr>
<tr><td>Parent</td><td><%= IISObject.Parent %></td></tr>
<tr><td>SchemaLocation</td><td><%= IISObject.Schema %></td></tr>
<tr><td>Class</td><td><%= IISObject.Class %></td></tr>
<tr><td>Guid</td><td><%= IISObject.Guid %></td></tr>
<tr><td>ADSPath</td><td><%= IISObject.AdsPath %></td></tr>
</table><br>
<table border=1>
<tr><th>Class Property</th><th>Class Value</th></tr>
<tr><td>Name</td><td><%= ClassDefinition.Name %></td></tr>
<tr><td>Parent</td><td><%= ClassDefinition.Parent %></td></tr>
<tr><td>SchemaLocation</td><td><%= ClassDefinition.Schema %></td></tr>
<tr><td>Class</td><td><%= ClassDefinition.Class %></td></tr>
<tr><td>Guid</td><td><%= ClassDefinition.Guid %></td></tr>
<tr><td>ADSPath</td><td><%= ClassDefinition.AdsPath %></td></tr>
</table><br>
<%
on error resume next
asMustHaves = ClassDefinition.MandatoryProperties
asMayHaves = ClassDefinition.OptionalProperties
i=1
%>
<table border=1>
<tr><th>Class Must Have Property</th>
<th>Default Web Site Current Value</th></tr>
<%
For Each Thing in asMustHaves
Response.Write "<tr><td>("& Cstr(i) & ") " &_
Thing & "</td><td>" &_
IISObject.Get(Thing) & "</td></tr>"
i = i + 1
Next
%>
</table>
<br>
<table border=1>
<tr><th>Class May Have Property</th>
<th>Default Web Site Current Value</th></tr>
<%
i=1
For Each Thing in asMayHaves
Response.Write "<tr><td>("& CStr(i) & ") " &_
Thing & "</td><td>" &_
IISObject.Get(Thing) & "</td></tr>"
i = i + 1
Next
%>
</table>
In order to get the property information for the default web, you either have to know the name of the property, or get the list of properties for the class "IisWebServer". In example 4, we get all the properties and list their values. Any object can be defined to have mandatory and/or optional properties. All mandatory properties must be set in order for an object to be created.
Note: While the IisWebServer only lists optional properties, please don’t create an object instance for a new web server without consulting the IIS documentation. The ADSI implementation for IIS breaks the rule. There are some "mandatory" properties that have to be set but that are listed in the "optional" properties section. If you do create a web server without correctly setting all the necessary properties, your metabase may become corrupt.
Note: ADSI is unpredictable with errors and will through an error, even when it is just sending a message. The above script uses ‘on error resume next’ to avoid stopping since ADSI throws an error when there are no mandatory properties to enumerate. Without the ‘on error resume next’, the code would error on the "For Each" loop of the mandatory properties.
Creating a Virtual Directory
In example 5 we will create a virtual directory in the default web server. We will first list all the properties of a virtual directory then create a virtual directory. In order list all the properties of a virtual directory, we use the logic from example 3, but look for the virtual directory object instead of the web server object. In order to create a virtual directory, you have to know the mandatory properties: what web server to create it on, what the name of the virtual directory is and what its corresponding physical path is. Once you have created the virtual directory object, then you set properties on the object such as access permissions. Example 5 shows how to do this:
Example 5
<%
sComputer ="localhost"
sPhyDir = "c:\adsi"
sVirDir = "ADSITest"
'Get Default Web Site Object
set websvc = GetObject("IIS://" & sComputer & "/W3svc/1")
'Verify by printing out ServerComment
Response.Write "Comment = " & websvc.ServerComment & "<br>"
'Get root of Default Web Site
set vRoot = websvc.GetObject("IIsWebVirtualDir", "Root")
'Get Class Definition of virtual directory
Set ClassDefinition = GetObject(vRoot.Schema)
'Get list of mandatory properties
asMustHaves = ClassDefinition.MandatoryProperties
'Get list of optional properties
asMayHaves = ClassDefinition.OptionalProperties
i=1
%>
<table border=1>
<tr><th>Class Must Have Property</th>
<th>Root Virtual Directory Current Value</th></tr>
<%
on error resume next
For Each Thing in asMustHaves
Response.Write "<tr><td>("& Cstr(i) & ") " &_
Thing & "</td><td>" & vRoot.Get(Thing) &_
"</td></tr>"
i = i + 1
Next
%>
</table>
<br>
<table border=1>
<tr><th>Class May Have Property</th>
<th>Default Web Site Current Value</th></tr>
<%
i=1
For Each Thing in asMayHaves
Response.Write "<tr><td>("& CStr(i) & ") " &_
Thing & "</td><td>" & vRoot.Get(Thing) &_
"</td></tr>"
i = i + 1
Next
on error goto 0
'Create Virtual Directory
'Param 1 is class name
'Param 2 is the new object name
Set vDir = vRoot.Create("IIsWebVirtualDir",sVirDir)
'Only setting two properties
vDir.AccessRead = true
vDir.Path = sPhyDir
'Write information back to Metabase
vDir.SetInfo
%>
In this example, in order to create an object instance, the parent object is used. The object instance is created by giving the class name of the object to create and the name of the object instance to create. While this object didn’t have any mandatory properties, some objects do have mandatory objects. When creating an object, you must set all mandatory properties. If you have not, you will get an error on the "object.SetInfo" call.
In order to verify the virtual directory was created, open the IIS Manager and look for the new virtual directory in the default web site. Another way to check is to run the code again, you will get an error that the object already exists.
One of the fundamental concepts of ADSI is that the object is not written back to the data source until the "object.SetInfo" is executed. This lets you create a temporary object outside of the source and set properties without affecting the performance of other requests to the data source. Once you have set all the properties you want, you add the object to the database source once with the "object.SetInfo". This is much easier than a relational-style database where you have to add each property value with a different insert statement. ADSI is responsible for adding all the information associated with the object instead of you having to write N insert statements. The same holds true for update. You get the object once, set the properties, and then comment the updates with an "object.SetInfo" statement.
Adding a Default Document
In this example, we will add a new default doc "index.htm" to the list of default documents for the virtual directory created in Example 5 In order to make this change, we have to get the virtual directory from the data source, change the property associated with the default docs, and then write the information back to the Metabase.
Example 6
<%
sComputer ="localhost"
sPhyDir = "c:\dina\adsi"
sVirDir = "ADSITest"
'Get Default Web Site Object
set websvc = GetObject("IIS://" & sComputer & "/W3svc/1")
'Verify by printing out ServerComment
Response.Write "Comment = " & websvc.ServerComment & "<br>"
'Get root of Default Web Site
set vRoot = websvc.GetObject("IIsWebVirtualDir", "Root")
'Create Virtual Directory
Set vDir = vRoot.Create("IIsWebVirtualDir",sVirDir)
'Get local copy of object
vDir.GetInfo
Response.Write "Old Default Docs = " & vDir.DefaultDoc & "<BR>"
'Only setting two properties
vDir.DefaultDoc = vDir.DefaultDoc & ",index.htm"
'Write information back to Metabase
vDir.SetInfo
'Get Object Again
vDir.GetInfo
Response.Write "New Default Docs = " & vDir.DefaultDoc
%>
In this example, the information about the object is retrieved with the "object.GetInfo" statement. This retrieves all information in one step. After the object information is written back to the metabase with the "object.SetInfo" statement, a "fresh" copy of the object can be retrieved with another "object.GetInfo" statement.
Deleting a Virtual Directory
In this example, you will learn how to delete the virtual directory created in example 5. When creating an object, you must have the parent object. The same is true for the deletion. You must be at the parent object node in order to delete the object. Example 7 shows how this is done:
Example 7
<%
on error resume next
sComputer ="localhost"
sPhyDir = "c:\dina\adsi"
sVirDir = "ADSITest"
'Get Default Web Site Object
set websvc = GetObject("IIS://" & sComputer & "/W3svc/1")
'Verify by printing out ServerComment
Response.Write "Comment = " & websvc.ServerComment & "<br>"
'Get root of Default Web Site
set vRoot = websvc.GetObject("IIsWebVirtualDir", "Root")
'Delete Virtual Directory
Set vDir = vRoot.Delete("IIsWebVirtualDir",sVirDir)
%>
FastADSI
ADSI is a fast-read, slow write interface to databases. The idea is that the information store will be read much more than it will be written to. The Windows NT registry is a good example of this type of data store. The way ADSI is fast reading is that the entire object is brought into memory at one time. However when enumerating through a large list, bringing each object into to memory can be time-consuming.
The solution is available in the public beta of Site Server 3.0 (http://www.microsoft.com/siteserver) has another ADSI object called FASTADSI. The point of this object is to not only bring an object into memory but to bring all objects under a parent node into memory. This Site Server implementation will be faster than the ADSI discussed in this article.
Further Reading
ADSI is well covered in the MSDN subscription. It is kept in the following location Networking and Distributed Services. Look for the two Active Directory folders: one is a server-side object overview; the other is a Schema object overview.
|