Configuration Extensibility
The configuration system in IIS 7.0 and above is based on distributed xml files that contain the configuration for IIS, ASP.NET and other components; flexibility in the configuration system also allows for configuration to be set at a number of levels including at the server, the site and the application level. Configuration at the site and application level coexists alongside ASP.NET configuration in web.config files.
One aspect of the new configuration system is the ease with which configuration can be extended. It is possible, with only a few simple changes, to integrate custom configuration sections directly into the configuration system and manipulate these settings using the existing administration APIs. Configuration is extended by simply defining a new configuration section in an XML schema file which is then dropped in the IIS schema directory %windir%\system32\inetsrv\config\schema
. Finally, the new configuration section has to be registered in the IIS global configuration file.
This article walks through an example which uses basic configuration extensibility and some of the more advanced scenarios. Though the example used is contrived, it should be sufficient to demonstrate the power of configuration extensibility.
Prerequisites
There are a number of prerequisites for this article. They include:
- Having a default install of IIS 7.0 or above. If IIS is not installed, install it by opening the Server Manager and adding the Web Server (IIS) role.
- Making sure that you have the .NET Framework SDK installed. If you do not have the SDK installed, get it from https://www.microsoft.com/downloads
- Using a number of tools in the bin directory of the SDK. Either use the SDK Command Prompt from your start menu or add the bin directory to your path (e.g.
%systemdrive%\Program Files\Microsoft.NET\SDK\v2.0\Bin
) - Running all the commands from a command prompt with elevated privileges. Right click on "SDK Command Prompt" (or "Command Prompt") in the start menu and then select "Run as administrator".
Configuration Extensibility - The Basics
Overview
In order to demonstrate some of the basic configuration extensibility features, we use the contrived example of a custom logging module. The module itself is not all that special and simply handles a built in IIS logging event - LogRequest - and writes a log entry to a file on disk; think of it as a basic version of IIS logging.
Configuration extensibility comes into play because the module needs to know where to log information. The module must have a custom configuration section that stores its configuration - in this case, the logfile location.
Step 1 - The Schema File
The first step in adding a new configuration section is defining the section. Define the section schema in xml and drop the file into the %windir%\system32\inetsrv\config\schema
directory.
Create an xml file called simpleLogging_Schema.xml and put the following into it:
<configSchema>
<sectionSchema name="system.webServer/simpleLogging">
<attribute name="logfileDirectory" type="string"
defaultValue="%systemdrive%\inetpub\logs\simpleLogs" expanded="true" encrypted="false" />
</sectionSchema>
</configSchema>
The schema above does two things. First, it defines a new configuration section named "simpleLogging" using the <sectionSchema> element. Second, it defines an attribute of that new configuration section called "logfileDirectory".
You can see from the schema file that the attribute is a string and the configuration system will not encrypt it. The expanded="true" tells the configuration system to automatically expand environment variables when used. If you did not create the file in the %windir%\system32\inetsrv\config\schema
directory, move it there now.
Next, create the default directory specified for "logfileDirectory", as it probably does not exist on your machine. Run the following command from the command line to create the directory:
md %systemdrive%\inetpub\logs\simpleLogs
The Windows group IIS_IUSRS must have write permissions to the directory so that the SimpleLogging module we create in Step 4 can write log files to it. Run the following command at the command line:
icacls %systemdrive%\inetpub\logs\simpleLogs /grant BUILTIN\IIS_IUSRS:RW
More about the schema
Although Step 1 is complete in terms of our example, it is appropriate to discuss the schema files. In the schema above, we simply created a new configuration section simpleLogging that exists under system.webServer and specified a custom attribute. However, you can easily create more complex custom configuration with collections, elements and attributes. The following list shows some examples, but the best way to learn is to look at the schema file for the IIS configuration. Find it at %windir%\system32\inetsrv\config\schema\IIS\_schema.xml
.
attribute
Schema information:
<attribute name="" [String, Required] [XML name of the attribute] type="" [bool|enum|flags|uint|int|int64|string|timeSpan, Required] [Runtime type] required="false" [bool] [Indicates if it must be set] isUniqueKey="false" [bool] [Serves as the collection key] isCombinedKey="false" [bool] [Part of a multi-attribute key] defaultValue="" [String] [Default value or comma-delimited flags] encrypted="false" [bool] [Indicates if the value persisted is encrypted] expanded="false" [bool] [Environment variables are expanded when read] allowInfinite="false" [bool] [Indicates if "Infinite" can be set] timeSpanFormat="string" [string|seconds|minutes] [hh:mm:ss or number] validationType="" [See validation below] validationParameter="" [See validation below] />
Example:
<configSchema> <sectionSchema name="system.webServer/simpleLogging"> <attribute name="logfileDirectory" type="string" /> </sectionSchema> </configSchema>
element
Schema information:
<element name="" [String, Required] [XML name of the element] isCollectionDefault="false" [bool] [Indicates if default values are held for other elements in this collection] />
Example:
<configSchema> <sectionSchema name="system.webServer/simpleLogging"> <element name="logfile"> <attribute name="fileLocation" type="string" /> </element> </sectionSchema> </configSchema>
collection
Schema information:
<collection addElement="" [String] [Name of Add directive, if supported] removeElement="" [String] [Name of Remove directive, if supported] clearElement="" [String] [Name of Clear directive, if supported] defaultElement="" [applicationDefaults|applicationPoolDefaults|siteDefaults|virtualDirectoryDefaults] mergeAppend="true" [bool] [Indicates whether or not deepest set values are appended] allowDuplicates="false" [bool] [Indicates if multiple elements may have the same key] allowUnrecognizedAttributes="false" [bool] [Indicates if non-schema attributes are ok] />
Example:
<configSchema> <sectionSchema name="system.webServer/simpleLogging"> <collection addElement="add"> <attribute name="logfileDirectory" type="string" /> </collection> </sectionSchema> </configSchema>
Step 2 – Registering the New Section
Now that a new section has been defined, tell the configuration system about the section. Register the new section in the %windir%\system32\inetsrv\config\applicationHost.config
file. Open the file and register the simpleLogging section as below:
<configSections>
...
<sectionGroup name="system.webServer">
<section name="simpleLogging"/>
...
</sectionGroup>
</configSections>
This step is complete. The section is defined and registered.
To check that the section has registered correctly, run the following command from the command line:
%windir%\system32\inetsrv\appcmd list config –section:system.webServer/simpleLogging
If everything has gone well up to now, then the configuration section displays and you see something like this:
<system.webServer>
<simpleLogging />
</system.webServer>
Step 3 – Setting the Configuration
Now that the section has been registered, set the configuration as you would any other configuration using a web.config file, or set it using the appcmd.exe tool in the %windir%\system32\inetsrv\
directory. You can also set the configuration using any of the configuration APIs. One more option is to set the configuration via the new IIS administration UI by creating a UI module and calling the configuration APIs to set the configuration.
For the time being, set the configuration by adding it to a new web.config file for the default IIS website (the one installed at %systemdrive%\inetpub\wwwroot\
and named "Default Web Site" in the default IIS configuration). Create a file called web.config and add the following:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<simpleLogging logfileDirectory="%systemdrive%\inetpub\logs\simpleLogs" />
</system.webServer>
</configuration>
You can achieve the same thing from the command line using:
%windir%\system32\inetsrv\appcmd set config "Default Web Site" –section:system.webServer/simpleLogging
/logfileDirectory:""%"systemdrive"%"\inetpub\logs\simpleLogs"
Run the following command to list the configuration for the "Default Web Site":
%windir%\system32\inetsrv\appcmd list config "Default Web Site" –section:system.webServer/simpleLogging
The output is something like this:
<system.webServer>
<simpleLogging logfileDirectory="%systemdrive%\inetpub\logs\simpleLogs" />
</system.webServer>
Note
If the directory specified for "logfileDirectory" does not exist, create it now. The Windows group IIS_IUSRS must have write permissions to the directory so that the SimpleLogging module we create in the next step can write log files to it. Step 1 above showed the command line command used to set the correct permissions on the default directory. Use a similar command if you have created a different directory.
Step 4 – The SimpleLogging Module
At this stage, we have extended the configuration system with our customer simpleLogging configuration section. We round off the look at basic configuration extensibility by creating the module and showing how to use the custom configuration from it.
We will create a strongly named .NET assembly that all websites in IIS can use. We must use some tools from the .NET SDK for this section; if it is not installed, download it from the www.microsoft.com/downloads website.
The required steps include:
Create a directory in which to work and open it.
Create a SimpleLoggingModule.cs file and add the following code to it using a text editor:
using System; using System.Web; using System.Web.Hosting; using System.IO; using Microsoft.Web.Administration; namespace ConfigurationExtensibility { public class SimpleLoggingModule : IHttpModule { private string GetlogfileDirectory(HttpContext context) { ConfigurationSection section = WebConfigurationManager.GetSection( context, "system.webServer/simpleLogging"); return (string)section["logfileDirectory"]; } public void Init(HttpApplication context) { context.LogRequest += new EventHandler(LogRequest_EventHandler); } private void LogRequest_EventHandler(object sender, EventArgs e) { HttpApplication application = (HttpApplication)sender; LogRequest(application.Context); } private void LogRequest(HttpContext context) { string logfileDirectory = GetlogfileDirectory(context); if (Directory.Exists(logfileDirectory) == false) { Directory.CreateDirectory(logfileDirectory); } string logfile = Path.Combine(logfileDirectory, DateTime.Now.ToString("yyyyMMdd") + ".log"); string ogline = string.Format("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}\t{7}", DateTime.Now, context.Request.HttpMethod, context.Request.UserHostAddress, context.Request.Url.ToString(), context.Request.ServerVariables["LOGON_USER"], context.Request.UserAgent, context.Response.StatusCode, HostingEnvironment.SiteName); File.AppendAllText(logfile, ogline + Environment.NewLine); } public void Dispose() { } } }
We must make this a strongly named module in order for IIS to use it as a global module for all sites. First, create a strong name key file. Open a command prompt and change the directory to the one containing the SimpleLoggingModule.cs file. Then, run the following command (make sure that the bin directory of your .NET framework SDK is in your path):
sn.exe /k keyFile.snk
If this worked correctly, the output of sn.exe says something like "Key pair written to keyFile.snk"
Now compile the file and create a DLL. Run the following command from the command prompt:
%windir%\Microsoft.NET\Framework\v2.0.50727\csc.exe /t:library SimpleLoggingModule.cs /r:System.Web.dll /r:%windir%\system32\inetsrv\Microsoft.Web.Administration.dll /keyfile:keyFile.snk
Next, place the compiled assembly (SimpleLoggingModule.dll) into the Global Assembly Cache. Run the following command from the command prompt:
gacutil.exe /i SimpleLoggingModule.dll
Now we must add our module to the list of modules that IIS can use. Before that, however, we must get the full name of the assembly just created. Run the following at the command line:
gacutil.exe /l SimpleLoggingModule
This outputs something like this:
SimpleLoggingModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=652a8d02f48e4288, processorArchitecture=MSIL
Add the module to the list of modules that IIS can use. Run the command below. However, ensure that you replace the variables with the output of the last command.
%windir%\system32\inetsrv\appcmd add module /name:"SimpleLoggingModule" /type:"ConfigurationExtensibility.SimpleLoggingModule, SimpleLoggingModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=652a8d02f48e4288"
This adds the necessary configuration entry to the applicationHost.config file - IIS's global configuration file.
The process is complete. The custom module, which uses custom configuration, has been setup. All that remains is to test it. Initiate the browser and navigate to http://localhost/. You see the following:
If you get an error, make sure that you have given the IIS_IUSRS group permissions to write to the directory.
Open %systemdrive%\inetpub\logs\simpleLogs
(or whatever directory you used in the configuration) and you have a .log file named with today's date. Open the file and you see something like this:
Experiment with the configuration to ensure that it is working. Try removing the simpleLogging section from your web.config file and check to see if the logs go to the default location (just ensure that the ASPNET user has the correct permissions).
Note
The module we just created is for demonstration purposes only and should not be used in a production environment. It will fail if there are multiple requests trying to write a log entry at the same time.
Configuration Extensibility - More Advanced Scenarios
Overview
The previous section examined the basics of configuration extensibility - simply extending the configuration using schema. However, there is more power available when extending configuration.
First, there is the ability to extend configuration to use COM objects for retrieving configuration, allowing you to store the configuration information wherever and however you want without worrying that the configuration APIs cannot read it.
Second, there is the ability to define methods to manipulate and operate on your configuration. These methods can then be called using the existing configuration APIs. Together these two features provide powerful support for building custom configuration extensions.
This section first looks at modifying the simpleLogging custom configuration from the first part of this article to retrieve configuration values using a COM component. Then it looks at adding a configuration method backed by a COM component that performs actions.
Extending Configuration - An Attribute Backed by COM
This section extends the schema to have an attribute called "logfileCount". This configuration attribute is backed by a .NET assembly (a managed dll - programmed in C#), which counts the number of log files in the log directory; once again, this is a contrived scenario but one that some might find useful.
Note
You do not have to create a .NET component - any valid COM component will do.
Step 1 - Creating and Registering the .NET COM Component
First, to create the .NET COM component. Create a directory to store the files we will create and the component we will build and open it.
The component we create must implement some interfaces exposed via COM by the IIS configuration system. In order to use the COM interface from the .NET component, we must create an interop dll - this allows the IIS configuration system to talk to the component when it must get a value for the logfileCount attribute. To create the interop dll, use a tool from the .NET Framework SDK called tlbimp.exe. Having Visual Studio or the .NET SDK installed is a prerequisite for this article. If it is not installed, download it from www.microsoft.com/downloads.
Here are the steps to create the .NET COM component:
Open a command line prompt and change to the directory you created to store the files. Make sure that the bin directory of the .NET Framework is in your path and then run the following command at the command line:
tlbimp %windir%\system32\inetsrv\nativerd.dll /keyfile:keyFile.snk
The tlbimp.exe tool will have created a file called AppHostAdminLibrary.dll – this is the interop dll that we need.
Create a ConfigurationExtensibility.cs file in the directory you created earlier and copy the following C# code into the file using a text editor:
using System; using System.IO; using System.Runtime.InteropServices; using AppHostAdminLibrary; namespace ConfigurationExtensibility { [ComVisible(true)] public class SimpleLoggingExtension : IAppHostPropertyExtension { public void ProvideGetProperty(IAppHostElement pElement, IAppHostProperty pProperty) { switch(pProperty.Name) { case "logfileCount": string logDirectory = (string) pElement.Properties["logfileDirectory"].Value; if(Directory.Exists(logDirectory)) pProperty.Value = Directory.GetFiles(logDirectory, "????????.log").Length; else pProperty.Value = 0; break; } } } }
Note
We have a class which is implementing the IAppHostPropertyExtension interface. The code itself only reads the logfileDirectory attribute to get the logfile directory and then counts all the files that match the filename pattern for the log files that SimpleLoggingModule created.
Build the component by running the following command from the commandline:
%windir%\Microsoft.NET\Framework\v2.0.50727\csc.exe /t:library /r:AppHostAdminLibrary.dll ConfigurationExtensibility.cs /keyfile:keyFile.snk
You now have your .NET COM component – ConfigurationExtensibility.dll.
Register the managed COM component you just created. Run the following command at the command prompt:
%windir%\Microsoft.NET\Framework\v2.0.50727\regasm.exe /register /codebase ConfigurationExtensibility.dll
This registers the COM component in the registry. You have created and registered a .NET COM component that the configuration system can use.
Step 2 - Updating the Schema File
Next, modify the simpleLogging_Schema.xml file created earlier. Open the file and change it to look like the following (the new attribute is bolded):
<configSchema>
<sectionSchema name="system.webServer/simpleLogging">
<attribute name="logfileDirectory" type="string"
defaultValue="%systemdrive%\inetpub\logs\simpleLogs\" expanded="true" encrypted="false" />
<attribute name="logfileCount" type="int" extension="ConfigurationExtensibility.SimpleLoggingExtension" />
</sectionSchema>
</configSchema>
Step 3 - Testing
Everything should be working properly now-- all that remains is testing. To test the extension, use a simple script. Create a SimpleLoggingTest.vbs file and enter the following text:
Dim adminManager, section
Set adminManager = WScript.Createobject("Microsoft.ApplicationHost.AdminManager")
Set section = adminManager.GetAdminSection("system.webServer/simpleLogging",
"MACHINE/WEBROOT/APPHOST/Default Web Site")
WScript.Echo(section.Properties.Item("logfileCount").Value)
At this stage, there should be a single log file from testing the SimpleLoggingModule earlier. Run the script from the command line. You see an output of 1.
Extending Configuration - A Method Backed by COM
Finally, this article examines extending configuration with a method. Configuration methods are operations that the configuration system can call to do work such as modifying a configuration or deleting log files--which is what this method will do. For this example, we add a method to delete all the log files created by the SimpleLoggingModule.
Step 1 - The Code
First,add the necessary code for the method. Open up the ConfigurationExtensibility.cs file created earlier and update it to look as follows (new code bolded):
using System; using System.IO; using System.Runtime.InteropServices; using AppHostAdminLibrary; namespace ConfigurationExtensibility { [ComVisible(true)] public class SimpleLoggingExtension : IAppHostPropertyExtension, IAppHostMethodExtension { public void ProvideGetProperty(IappHostElement pElement, IappHostProperty pProperty) { switch(pProperty.Name) { case "logfileCount": string logDirectory = (string) pElement.Properties["logfileDirectory"].Value; if(Directory.Exists(logDirectory)) pProperty.Value = Directory.GetFiles(logDirectory, "????????.log").Length; else pProperty.Value = 0; break; } } public void ProvideMethod(IappHostMethod pMethod, IappHostMethodInstance pMethodInstance, IappHostElement pElement) { switch(pMethod.Name) { case "deleteLogs": string logDirectory = (string) pElement.Properties["logfileDirectory"].Value; if(Directory.Exists(logDirectory)) { foreach(string logFile in Directory.GetFiles(logDirectory, "????????.log")) { File.Delete(logFile); } } break; } } } }
Note
We have implemented the IAppHostMethodExtension interface. This interface has a single method called ProvideMethod which logically provides the method. When someone calls the method (see Step 3 for how to do this), the configuration system calls ProvideMethod and passes parameters, one of which has the name of the method being called; in the code above, we only handle a method called "deleteLogs".
Build the project again using:
%windir%\Microsoft.NET\Framework\v2.0.50727\csc.exe /t:library /r:AppHostAdminLibrary.dll ConfigurationExtensibility.cs /keyfile:keyFile.snk
Step 2 - Updating the Schema
Next, tell the schema about our new method. At this stage, you should be fairly familiar with your simpleLogging_Schema.xml file, so open it up again and change it to look like the following:
<configSchema>
<sectionSchema name="system.webServer/simpleLogging">
<attribute name="logfileDirectory" type="string"
defaultValue="c:\inetpub\logs\simpleLogs\" expanded="true" encrypted="false" />
<attribute name="logfileCount" type="int" extension="ConfigurationExtensibility.SimpleLoggingExtension" />
<method name="deleteLogs" extension="ConfigurationExtensibility.SimpleLoggingExtension" />
</sectionSchema>
</configSchema>
The change defined a new method called "deleteLogs" and tells the configuration where to look for the method.
Step 3 - Testing
Finally, check to see if the method is working. A quick and easy way to do this is to write a simple VB script. Below is an example script that outputs the logfileCount, then calls our method and outputs the logfileCount. Simply update the SimpleLoggingTest.vbs file you created earlier and enter the following:
Dim adminManager, section
Set adminManager = WScript.Createobject("Microsoft.ApplicationHost.AdminManager")
Set section = adminManager.GetAdminSection("system.webServer/simpleLogging", "MACHINE/WEBROOT/APPHOST/Default Web Site")
WScript.Echo(section.Properties.Item("logfileCount").Value)
section.Methods.Item("deleteLogs").CreateInstance().Execute()
WScript.Echo(section.Properties.Item("logfileCount").Value)
Run the script from a command line and you get an output of:
1
0
The above was a quick overview of how to provide new configuration and configuration methods backed by COM components. As you probably found, extending configuration using this method is very powerful.
Configuration Extensibility - Extending Existing Configuration
The final aspect of configuration extensibility is the ability to extend existing configuration sections such as the system.webServer/sites section, or to extend the system.webServer/simpleLogging section created in the previous two sections.
Extending an existing configuration section is as easy as creating a new one. Simply define the schema as xml and place the schema file in the %windir%\system32\inetsrv\config\schema\
directory. This should sound familiar, as we have done this previously more than once.
Extending the "sites" configuration
To better show how to extend an existing configuration section, we extend the system.applicationHost/sites section - the configuration section used to define sites. We extend the sites section by adding an "owner" attribute and an "ownerEmail" attribute. Attributes like these are useful when you host multiple sites on a single box and want to keep track of who owns the different sites.
First, create a new schema file. Create a siteExtension_schema.xml file in the %windir%\system32\inetsrv\config\schema\
directory and enter the text below:
<configSchema>
<sectionSchema name="system.applicationHost/sites">
<collection addElement="site">
<attribute name="owner" type="string" />
<attribute name="ownerEmail" type="string" />
</collection>
</sectionSchema>
</configSchema>
When extending the schema of an existing section, simply create a <sectionSchema>
element and set the name attribute to be the same as an existing section. In the schema file above, we have defined a <sectionSchema> with a name of "system.applicationHost/sites" - this is the same as the sectionSchema name in the IIS_Schema.xml file in the Schema directory.
Test our modifications by adding values for the "owner" and "ownerEmail" attributes and then check the configuration file to see the changes. Simply run the following command from the command line:
%windir%\system32\inetsrv\appcmd set site "Default Web Site" /owner:"John Contoso" /ownerEmail:"john@contoso.com"
To see if the configuration was applied, run the following command and check the output:
%windir%\system32\inetsrv\appcmd list site "Default Web Site" /config
The output should be something like the following:
<system.applicationHost>
<sites>
...
<site name="Default Web Site" id="1" siteOwner="John Contoso" siteOwnerEmail="john@contoso.com">
...
...
</site>
</sites>
</system.applicationHost>
Note
If you now browse to http://localhost/
, you may receive a server 500.19 error message. This is a known issue and will be resolved in a later build of IIS. To get around this issue, run "iisreset" from the command line.
That concludes the look at configuration extensibility. Hopefully, you can use configuration extensibility in an interesting way after reading through the previous examples.