Search your configuration sections in web.config files using IIS 7.0 API's

Background

In IIS 7.0 we have the great functionality to allow you to configure the Web Server settings in a distributed way, including the IIS configuration along with the ASP.NET configuration in the web.config files by using Configuration Sections. For example, the following shows a web.config adding a default document (home.aspx) to a Web Application inside my Default Web Site:

<configuration>
  <system.webServer>
    <defaultDocument>
      <add value="home.aspx" />
</defaultDocument>
  </system.webServer>
  <system.web>
    <authentication mode="Forms" />
</system.web>
</configuration>

Now, that is great but it does come with a price, specially for server administrators it means that now you need to deal with a distributed configuration environment where certain settings are applied at the server level and certain settings are applied along with the application or even folders.

Another interesting challenge is that given the nature of distributed configuration, we've added the functionality to lock certain configuration sections so that they can only be set by a server administrator. Again this is good, however before the server administrator locks any section in order to prevent breaking applications they should search configuration and see if anyone is using that configuration section underneath.

Searching Configuration

The IIS 7.0 configuration system has a not so well-known feature that allows you to "query" the configuration system to get an overview of the configuration files in the system as well as the configuration sections that are used in each of them. This feature is implemented as a magical section called configPaths, that has the following schema:

  <sectionSchema name="configPaths">
    <collection addElement="searchResult">
      <attribute name="path" type="string" />
<attribute name="locationPath" type="string" />
<attribute name="status" type="uint" />
<collection addElement="section">
        <attribute name="name" type="string" isUniqueKey="true" />
</collection> 
    </collection>
  </sectionSchema>

How to use it

One thing that is pretty cool is that you can consume this section from all of our tools, including a command line (AppCmd.exe), a script (such as vbscript or javascript using Microsoft.ApplicationHost.AdminManager), or using Managed Code (Microsoft.Web.Administration) including the ability to use it under PowerShell. For this exercise we will only use Microsoft.Web.Administration,  but the concepts are exactly the same in the other tools.

In its simplest form you can use the following function to display all the configuration files in your server as well as the sections included on them (just add a reference to Windows\System32\Inetsrv\Microsoft.Web.Administration.dll):

    /// <summary>
    /// Displays the list of configuration files as well as the
    /// sections being used in each file
    /// </summary>
    private static void DisplaySections() {
        using (ServerManager serverManager = new ServerManager()) {
            Configuration appHost = serverManager.GetApplicationHostConfiguration();
            ConfigurationSection configPaths = appHost.GetSection("configPaths");

            foreach (ConfigurationElement configPath in configPaths.GetCollection()) {
                string path = (string)configPath["path"];
                string locationPath = (string)configPath["locationPath"];

                Console.WriteLine();

                Console.WriteLine("Config Path:" + path);
                if (!String.IsNullOrEmpty(locationPath)) {
                    Console.WriteLine(" <locationPath path=" + locationPath + "'>");
                }

                foreach (ConfigurationElement section in configPath.GetCollection()) {
                    Console.WriteLine(" " + section["name"]);
                }
            }
        }
    }

An example result of running this is:

 Config Path:MACHINE/WEBROOT/APPHOST
    system.applicationHost/applicationPools
    system.webServer/defaultDocument
    system.webServer/ImageCopyright
    system.applicationHost/sites
    system.webServer/security/authentication/windowsAuthentication
    system.webServer/tracing/traceProviderDefinitions
    ...

Config Path:MACHINE/WEBROOT/APPHOST<br>  <locationPath="Default Web Site/BlogApp"> 
    system.webServer/security/authentication/windowsAuthentication

Config Path:MACHINE/WEBROOT/APPHOST<br>  <locationPath path="Default Web Site/aspnet_client"> 
    system.webServer/directoryBrowse
    system.webServer/handlers

Config Path:MACHINE/WEBROOT/APPHOST<br>  <locationPath path="Site2/aspnet_client"> 
    system.webServer/directoryBrowser
    system.webServer/handlers
    system.webServer/defaultDocument

Config Path:MACHINE/WEBROOT/APPHOST<br>  <locationPath path="Default Web Site"> 
    system.webServer/security/authentication/anonymousAuthentication
    system.webServer/directoryBrowse
    system.webServer/security/authentication/windowsAuthentication

Config Path:MACHINE/WEBROOT/APPHOST/Default Web Site
    appSettings
...

This tells us that in ApplicationHost.config we have a lot of sections begin used including applicationPools and many more.

Now, lets focus on the last two set of entries, the one with "MACHINE/WEBROOT/APPHOST" with locationPath set to "Default Web Site" tells us that anonymousAuthentication was used as well as windowAuthentication. The locationPath basically tells the configuration that even though this is set in ApplicationHost.config this configuration should only be applied to Default Web Site and its children. The next entry with path "MACHINE/WEBROOT/APPHOST/Default Web Site", basiclally tells you that in the Web.config inside the Default Web Site (in other words in c:\inetpub\wwwroot\web.config) the section appSettings is being used.

Now, what is interesting is that this is walking the entire server to find configuration files and do a lot of processing, however if you already know that you only want to search within a Site, or a particular application, then you can scope it down by using the GetWebConfiguration() method instead and this will give you only the configuration sections that apply for that site or application. Note that this will also report the sections that are specifically set for that object inside ApplicationHost.config making it much more than just a "findstr" inside the site folder and their virtual directories.

    DisplaySectionsForSite("Default Web Site");

    private static void DisplaySectionsForSite(string siteName) {
        using (ServerManager serverManager = new ServerManager()) {
            Configuration appHost = serverManager.GetWebConfiguration(siteName);
            ConfigurationSection configPaths = appHost.GetSection("configPaths");

            foreach (ConfigurationElement configPath in configPaths.GetCollection()) {
                string path = (string)configPath["path"];
                string locationPath = (string)configPath["locationPath"];

                Console.WriteLine();

                Console.WriteLine("Config Path:" + path);
                if (!String.IsNullOrEmpty(locationPath)) {
                    Console.WriteLine(" <locationPath path=" + locationPath + "'>");
                }

                foreach (ConfigurationElement section in configPath.GetCollection()) {
                    Console.WriteLine(" " + section["name"]);
                }
            }
        }
    } 

PowerShell

Now, lets look at other examples, lets consider that we are a server administrator and I want to lock the defaultDocument section, but as a good citizen I first want to see if I would be breaking any application in my entire server if I do this. Just for fun lets do this using PowerShell instead, to test this just copy the entire code below and paste in inside an elevated PowerShell window.

$sectionToLookFor = "*defaultdocument*"

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Web.Administration")
$iis = new-object Microsoft.Web.Administration.ServerManager

$configPaths = $iis.GetApplicationHostConfiguration().GetSection("configPaths").GetCollection()

foreach($path in $configPaths.GetCollection()) {
  foreach($section in $path.GetCollection()) {
    if ($section.GetAttributeValue("name") -like $sectionToLookFor) {
      write ($path.GetAttributeValue("path") + " LocationPath:" + $path.GetAttributeValue("locationPath"))
      write (" " + $section.GetAttributeValue("name"))
    }
  }
}

The result in my machine gives you something like follows:

MACHINE/WEBROOT/APPHOST
    system.webServer/defaultDocument
MACHINE/WEBROOT/APPHOST/Default Web Site/aspnet_client
    system.webServer/defaultDocument
MACHINE/WEBROOT/APPHOST/Site2/aspnet_client
    system.webServer/defaultDocument

This tells us that inside the c:\inetpub\wwwroot\aspnet_client\web.config file we are actually using that section so if we end up locking this in the server we would break that application.

Summary

The configPaths section is a very useful section that allows you to search configuration files and the configuration sections being used in each of them, making it an invaluable tool for scenarios like understanding the configuration usage as well as locking and many others.