White Papers Home |  White Papers | Message Board |  Search |  Products |  Purchase | News |   |
Configuring IIS via code using the IISAdmin objects
by Rick Strahl
Updated: 4/6/2001
In this day and age Web applications have become the norm. We've even come to the point where many development projects build Web applications that need to be installed on multiple servers. But even if you don't build vertical Web applications, it's useful to build a configuration utility that can re-create a configuration via code. This maybe for backup purposes, or if youre running in a high volume environment for situations such as load balancing where multiple servers need to be configured.
In order to accomplish this gracefully you need to be able to actually configure the Web server programmatically because the current crop of development tools do not provide that functionality as part of the development tools. In this article I'll demonstrate how you can configure Internet Information Server programmatically by using the Active Directory Service Interface (ADSI) and the IIS Admin objects to create an installation application. I'll show a Visual FoxPro class that provides several of the key elements needed for configuring a Web application so that you can handle these tasks via code that can be used to build set up Wizards or preset setup scripts.
What do we need to do to configure a Web server?
When you build a Web application there are many components that make up that application. You have your data engine along with its data files whether it be a SQL server of some sort, or whether it's a set of files if you're using a tool like Visual FoxPro or file based data from Access. There are the actual application binary files the EXE or DLL of the application, plus any support files. If you're building a purely script based application you won't have this aspect. In a Web based application there are of course HTML or scripted HTML (plain HTML or ASP, JSP, Cold Fusion or Web Connection script) files that need to be deployed in a Web directory.
Notice that in most Web application the HTML/script content will be separate from the binary and data content of the application. This means that the data and binary content are separate from the content in the Web directories. This means if you distribute a Web application you typically install into two locations: the application path and the Web path.
The Web path requires more work than a traditional application path, because it's sitting on the Web server and needs to be recognized as a Web path. It also needs certain permissions set so that the Web server can access the files there correctly.
A typical Web application needs to perform the following configuring tasks:
- Find the Web site the user wants to install on
IIS allows you to install multiple Web sites on the same machine. In particular ISPs will have a large number of sites on a single machine, so don't necessarily assume that the user wants to install on the default Web site.- Install and/or configure the Web site
If you're building a vertical Web application it's likely that you actually would want to install the application on a completely separate Web site as opposed to just a directory under an existing site. You'd create the Web site and then also configure the site with performance and security settings. You may also set some of these settings for non-dedicated Web sites in some cases.- Create a Virtual Directory for your application
Regardless of whether your application runs on a dedicated site or under an existing site, you'll need to set up a virtual directory for it. Virtual directories allow you to isolate your application from the rest of the site and configure it independently. Once a virtual directory is created you need to set the security settings on that directory.- Create script maps
If your application is running a custom development tool such as Web Connection for example, you may also want to set up a custom script map for your application that routes to a specific handler ISAPI DLL. The script map provides a custom extension to your application and in some cases lets you hide what tool you're using behind the scenes (which can be give you more security against hackers in some cases).
The IIS Admin Objects
Using the IIS Admin objects it's possible to configure the tasks mentioned above plus a lot more relatively painlessly. The IIS Admin objects are a COM object that let you connect to any resource that's available in the IIS metabase. The metabase is used to store all the IIS configuration settings. The IIS Admin objects are an Active Directory Service Interface that's specific to IIS and uses the common syntax used for any ADSI interface which includes a handful of method calls and properties, which are extended by the properties that the actual service in this case the IIS Admin provider exposes.
How this works is best illustrated by an example. The following code accesses the default Web site and retrieves a few of the properties available via the IIS Admin objects:
*** Connect to the Root directory of the first site
oVirtual = GETOBJECT("IIS://LOCALHOST/W3SVC/1/ROOT")
? oVirtual.Class && IIsWebVirtualDir
? oVirtual.Path && d:\inetpub\wwwroot
? oVirtual.AnonymousUserName && IUSR_<Machine>
? oVirtual.AuthBasic && Basic Authentication flag
? oVirtual.AccessExecute && Execute rights
*** Configure settings
oVirtual.Path = "d:\wwindweb"
oVirtual.AuthBasic = .T.
oVirtual.AccessExecute = .T.
oVirtual.SetInfo() && Save Settings
This short snippet connects to the Web root directory of the default Web site and reads a few settings. Most configuration settings of value are found at the virtual directory level. The hierarchy of the IIS Admin objects starts with:
IISWebService
GETOBJECT("IIS://LOCALHOST/W3SVC/")
Contains many settings similar to the virtual directory settings, but actually doesn't let you control them. This object contains many performance options however typically you'll only use this for very specific things. The Web service above is W3SVC in the GETOBJECT moniker string above.
IISWebServer
GETOBJECT("IIS://LOCALHOST/W3SVC/1")
The individual Web sites which can be enumerated. The 1 in the GETOBJECT moniker above identifies the site in question. Sites are numbered sequentially and must be enumerated in order to retrieve a friendly name (I'll show an example of that shortly). This object also serves mainly as a performance and high level settings place holder. Although it has many of the same settings as IISWebVirtualDir, many of these settings don't do anything. The performance options do however.
IISWebVirtualDir
GETOBJECT("IIS://LOCALHOST/W3SVC/1/ROOT") && Root directory
GETOBJECT("IIS://LOCALHOST/W3SVC/1/WCONNECT") && Specific Virtual
The virtual directories including the ROOT directory contain most of the important settings that you need to make to configure a Web site.
As a general rule you want to set any inherited settings at the lowest possible level. So setting execute rights should be done at the virtual level not at the Web server level. Performance settings that are available both in the WebService and WebServer objects should be made on the WebServer object. For a detailed list of properties available for each of these objects see the MSDN documentation for the IIS Admin objects a Web link to this topic is provided at the end of this article.
Looking at the example above again you can see that you can read settings and also write settings simply by assigning values to the appropriate property. Make sure that you call SetInfo() to actually write the settings into the IIS metabase. Until you do the settings are cached and don't actually change the operation of IIS. Once SetInfo() has been called the settings are written and take effect immediately.
We've got some configuring to do
Ok now that we have the basic idea for how to get our Web service let's set up a class for this and show some specific tasks that you'll be wanting to accomplish. What I will show here is a class called wwIISAdmin. Here are a few examples of how you might use it:
SET CLASSLIB TO WEBSERVER ADDIT
lcPath = "d:\westwind\CodeDemo\"
*** Some code to create a dummy directory and
*** copy an ISAPI DLL there
IF !ISDIR(lcPath)
MD (lcPath)
COPY FILE scripts\wc.dll TO (lcPath) + "wc.dll"
ENDIF
oIIS = CREATEOBJECT("wwIISAdmin")
*** Retrieve a list of all Web sites into laVirtuals
DIMENSION laVirtuals[1,3]
*** 1 - Site ID Number
*** 2 - Site Name
*** 3 - Site ADSI Path
lnCount = oIIS.aGetWebSites(@laVirtuals, "IIS://LOCALHOST/W3SVC")
*** Get the Web path to our site
lcWebPath = ""
FOR x=1 to lnCount
IF UPPER(laVirtuals[x,2]) = "WEST WIND"
lcWebPath = laVirtuals[x,3]
ENDIF
ENDFOR
IF EMPTY(lcWebPath)
RETURN
ENDIF
*** Assign the ROOT path of the Web site
oIIS.cPath = lcWebPath + "/ROOT"
*** Create the new virtual directory under the root
oIIS.CreateVirtual("CodeDemoVirtual",lcPath)
*** Set additional options on the
oIIS.oRef.AuthBasic = .F.
oIIS.oRef.AccessExecute = .T.
*** Save the setting changes on the oRef object
oIIS.Save()
*** Create a mapping for wwwc to wc.dll ISAPI DLL
oIIS.CreateScriptMap("wwwc",lcPath + "wc.dll")
This simple code demonstrates the basics of what happens in common Web installs: You create a new directory, copy some files to it, then you create a Web virtual directory and configure that directory as needed. Optionally in this example I'm also creating a script map that maps an ISAPI DLL to the WWWC extension so that any requests against this extension are routed to the specified DLL.
Figure 1 A Wizard like interface can be perfect to let the user choose the Web server and Web site.
The next step once you've figured out which site to pick is to create a virtual directory or update settings on the ROOT directory of the server. If you're working on an existing directory you can simply use GETOBJECT() directly to manipulate that object as shown in the first example earlier.
If you need to create it, you can use the wwIISAdmin object. Since I have a pretty standard procedure and set of settings I use for virtuals created in my installs I also provide a set of common settings into the CreateVirtual method of the wwIISAdmin class. Take a look:
FUNCTION CreateVirtual
LPARAMETERS lcVirtual, lcPhysical, llNoExecute, llNoAuthBasic
LOCAL lcPath, loVirtual
THIS.lError = .F.
THIS.oRef = GETOBJECT(THIS.cpath)
IF ISNULL(THIS.oRef)
THIS.cerrormsg = "Unable to connect to server root."
RETURN .F.
ENDIF
IF EMPTY(lcPhysical)
*** Delete the Virtual
THIS.oRef.DELETE("IIsWebVirtualDir",lcVirtual)
THIS.SAVE()
IF THIS.lError
RETURN .F.
ENDIF
RETURN .T.
ELSE
*** Try to create it
loVirtual = THIS.oRef.CREATE("IIsWebVirtualDir",lcVirtual)
*** If an error occurred it might exist already
IF THIS.lError OR TYPE("loVirtual") # "O"
THIS.lError=.F.
lcPath = THIS.cpath && Our current relative path
*** ADd the virtual path to it and try to connect
loVirtual = GETOBJECT(lcPath + "/" + lcVirtual)
IF THIS.lError
*** Still an error - reconnect to the original path
*** and exit
THIS.oRef = GETOBJECT(lcPath)
RETURN .F.
ENDIF
ENDIF
loVirtual.PATH = lcPhysical
loVirtual.AppCreate(.T.) && Make sure our app is In Process
loVirtual.AppFriendlyName = lcVirtual
loVirtual.AccessRead = .T.
loVirtual.AccessExecute = !llNoExecute
loVirtual.AuthBasic = !llNoAuthBasic
loVirtual.AuthNTLM = .T.
THIS.OnCreateVirtual(loVirtual)
loVirtual.SetInfo()
*** Pass out the reference for the directory
THIS.oRef=loVirtual
ENDIF
RETURN .T.
This code goes out and goes to the directory on the Web server that you want to create the virtual under first, and then tries to create it. If the virtual exists already, an error will occur internally, which is captured and sets the lError flag. This flag is checked and if .T. the assumption is that the directory already exists. In that case the code simply tries to access that directory rather than creating it. Once create or accessed again, a reference to this object exists and several default settings are applied, the most important of which is the directory that the virtual points to.
Notice that the OnCreateVirtual method is called after this process is complete to allow you to customize the virtual creation process with your on post processing behavior by overriding the class. Alternately, as shown in the example code above you can simply exit the method and use the oRef member to change any settings after the fact. The bottom line is that you don't give up any flexibility here as you still get a reference to the base ADSI object to do as you will. While the basic concept of creating a virtual is easy you can see that a fair amount of code is required to do it right. Hence this wrapper class in the first place to provide the error handling and additional functionality of presetting defaults.
In an application it tends to be a good idea to let the user pick the location for the files and name of the virtual and a typical interface is shown in Figure 2.
Figure 2 it's a good idea to let the end-user/administrator choose the directory and name of the virtual of where your files will go.
Dealing with ADSI Collections in VFP
Looking at the ADSI code here you can see that manipulation of the IIS Admin objects is pretty straight forward. However, one aspect in VFP has always been tricky and that is dealing with collections such as those used for scriptmap extensions for the Web root (or individual web directory). The following code demonstrates manipulating the scriptmaps collection for creation of a new script map:
FUNCTION CreateScriptMap
LPARAMETERS lcScriptMap, lcPath
LOCAL lnResult, x, cScriptMap, loScriptmaps
*** Fix up script map entry
IF lcScriptMap <> "."
lcScriptMap = "."+lcScriptMap
ENDIF
lcScriptMap = LOWER(lcScriptMap)
lcPath = LOWER(lcPath)
THIS.lerror = .F.
*** Get a reference to the Web path to work on
THIS.oRef=GETOBJECT(THIS.cPath)
IF ISNULL(THIS.oRef)
THIS.ErrorMsg = "Unable to access the default web root"
RETURN .F.
ENDIF
*** Make sure we're using 0 based arrays by Value
COMARRAY(THIS.oRef,0)
loScriptmaps = THIS.oRef.scriptmaps
x=0
FOR EACH cScriptMap IN loScriptmaps
x=x+1
*** Check if we need to UPDATE an existing script map
IF LOWER(LEFT(cScriptMap,LEN(lcScriptMap))) = lcScriptMap
loScriptmaps[x] = lcScriptMap + "," + lcPath + ",1"
THIS.oRef.PutEx(2,"ScriptMaps",@loScriptmaps)
THIS.oRef.SetInfo()
loScriptmaps = .NULL.
THIS.oRef = .NULL.
RETURN .T.
ENDIF
*** Just in case there's a problem
IF x > 200
EXIT
ENDIF
ENDFOR
*** Add another item
x=x+1
DIMENSION loScriptmaps[x]
loScriptmaps[x] = lcScriptMap + "," + lcPath + ",1"
THIS.oRef.PutEx(2,"ScriptMaps",@loScriptmaps)
THIS.oRef.SetInfo()
loScriptmaps = .NULL.
THIS.oRef = .NULL.
RETURN .T..
Note the use of the COMARRAY function to set the array to 0 based, which is necessary for VFP to access and write the array data. This makes the array 0 based but lets VFP continue to treat it as an array starting with item [1].
This code runs through all the existing scriptmaps to check and see whether it already exists. If it does it's overwritten rather than added again. ADSI would (invalidly) allow duplicate scriptmapts so this code is necessary. Note that in order to save any changes to the script map array, we need to save with the PutEx() ADSI method, which is passed a value of 2 (array), the key and the actual value in this case our scriptmap array passed by reference (actually because of the COMARRAY setting the array is passed by value over the COM boundary).
You can use this same type a code with any of the collections that the IIS Metabase provides such as the list of ISAPI Filters for example.
Summary
I hope this article has given you the basics you need to start building your own configuration programs for your Web applications. It's highly useful to be able to build an install script to quickly re-create a setup on a Web server or build a full-featured install for a Web application that gets installed in multiple locations.
The classes I provided let you easily do most of the work with IIS. Provided in the zip file of this class is also a more high level class called wwWebServer which provides functionality for IIS4 and 5, IIS3 (registry based), Personal Web Server on Win9x, Apache, and O'Reilly's Web site, so you can build installs that can install on any of these Web servers providing most of the functionality I've described here. IIS is by far the most flexible platform to install on but if you have a product that can run on other servers this can come in handy.
Reference:
West Wind Web Server Configuration Class
http://www.west-wind.com/presentations/WebServerConfig/WebServerConfig.zip
IIS Admin Object Reference
http://msdn.microsoft.com/library/default.asp?URL=/library/psdk/iisref/aore7zn6.htm
Rick Strahl is president of West Wind Technologies on Maui, Hawaii. The company specializes in Internet application development and tools focused on Internet Information Server, ISAPI, C++ and Visual FoxPro. Rick is author of West Wind Web Connection, a powerful and widely used Web application framework for Visual FoxPro, West Wind HTML Help Builder, co-author of Visual WebBuilder, a Microsoft Most Valuable Professional, and a frequent contributor to FoxPro magazines and books. His book "Internet Applications with Visual FoxPro 6.0", was published April 1999 by Hentzenwerke Publishing. For more information please visit: http://www.west-wind.com/.
White Papers Home |  White Papers | Message Board |  Search |  Products |  Purchase | News |   |