How to read/write administration.config

IIS7 configuration system understands machine.config, web.config and applicationHost.config but does not handle administration.config natively. This means reading and writing administration.config is little difficult. If you use AhAdmin directly and call GetAdminSection for a section defined in administration.config, it will throw a configuration error for all configuration paths saying that it couldn’t find the section in the configuration file. Easiest way you can work with administration.config is by using Microsoft.Web.Administration (MWA). ServerManager::GetAdministrationConfiguration gives you a Configuration object which represents administration.config. Below is a sample program which uses MWA to print UI module providers registered in administration.config.

class
Program
{
    static void Main(string[] args)
    {
        ServerManager sm = new ServerManager();

        Configuration administrationConfig = sm.GetAdministrationConfiguration();
        ConfigurationSection moduleProvidersSection = administrationConfig.GetSection("moduleProviders");
        ConfigurationElementCollection moduleProvidersCollection = moduleProvidersSection.GetCollection();

        foreach
(ConfigurationElement moduleProviderElement in moduleProvidersCollection)
        {
            Console.WriteLine(moduleProviderElement.GetAttribute("name").Value);
        }
    }
}

MWA achieves this by setting a pathMapper to map MACHINE/WEBROOT configuration path to administration.config instead of root web.config (it uses a different AdminManager for root web.config). You can write a pathMapper yourself and use AppHostAdminLibrary directly to read or write administration.config. Program below uses a simple pathMapper to map MACHINE/WEBROOT to administration.config and then prints UI module count.

using
AppHostAdminLibrary;

class Program
{
    static void Main(string[] args)
    {
        AppHostAdminManager configManager = new AppHostAdminManager();
        configManager.SetMetadata("pathMapper", new MyPathMapper());

        IAppHostElement modulesElement = configManager.GetAdminSection(
            "modules",
            "MACHINE/WEBROOT");
        IAppHostElement wmodulesElement = configManager.GetAdminSection(
            "system.webServer/modules",
            "MACHINE/WEBROOT/APPHOST");

        Console.WriteLine(modulesElement.Collection.Count);
        Console.WriteLine(wmodulesElement.Collection.Count);
    }
}

public
class MyPathMapper : IAppHostPathMapper
{
    public string MapPath(
        string bstrConfigPath,
        string bstrMappedPhysicalPath)
    {
        string physicalPath = bstrMappedPhysicalPath;

        if (bstrConfigPath.Equals("MACHINE/WEBROOT", StringComparison.OrdinalIgnoreCase))
        {
            string windir = Environment.ExpandEnvironmentVariables("%windir%");
            physicalPath = windir + @"\system32\inetsrv\config\administration.config";
        }

        return physicalPath;
    }
}

In windows server 2008 you can also implement IAppHostPathMapper2 and then set “pathMapper2” metadata on IAppHostAdminManager. IAppHostPathMapper2 allows you to return the impersonation token with the physical path mapping which is then used by the configuration system to read the configuration file. Also, native configuration system has an in-built pathMapper which maps MACHINE/WEBROOT to administration.config. Sample program below sets this in-built pathMapper and then creates an IIS manager user.

using
System;
using System.Text;
using System.Security.Cryptography;
using AppHostAdminLibrary;
 namespace AdminConfig
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                AppHostWritableAdminManager ahadmin = new AppHostWritableAdminManager();
                ahadmin.SetMetadata("pathMapper", "AdministrationConfig");
                ahadmin.CommitPath = "MACHINE/WEBROOT";

                IAppHostElement authenticationSection = ahadmin.GetAdminSection(
                    "system.webServer/management/authentication",
                    "MACHINE/WEBROOT");

                IAppHostElement credentialsElement = authenticationSection.GetElementByName("credentials");
                IAppHostElementCollection credentialsCollection = credentialsElement.Collection;
                IAppHostElement newElement = credentialsCollection.CreateNewElement("add");

                newElement.Properties["name"].Value = "newuser";

                //
                // Get SHA256 hash of password
                // This is required by UI
                // Plain text passwords won't work for IIS Manager users
                //
                SHA256 sha = SHA256.Create();
                byte[] hashBytes = sha.ComputeHash(Encoding.UTF8.GetBytes("iisrocks"));

                byte f1 = 0xf0;
                byte f2 = 0x0f;
                string hexString = "";

                foreach (byte b in hashBytes)
                {
                    int first4 = (b & f1) >> 4;
                    int second4 = (b & f2);

                    hexString = hexString + ((first4 > 9) ? (char)('A' + (first4 – 10)) : (char)('0' + first4));
                    hexString = hexString + ((second4 > 9) ? ((char)('A' + (second4 – 10))) : (char)('0' + second4));
                }

                newElement.Properties["password"].Value = hexString;

                credentialsCollection.AddElement(newElement, -1);
                ahadmin.CommitChanges();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }
    }
}

You can also use Microsoft.Web.Managerment.Server.ManagementAuthentication.CreateUser("user", "password") to create an IIS Manager user. This will also make sure that user is saved to the right provider which might or might not be administration.config.

 

-Kanwal

Working with RSCA using configuration APIs

One of the new features in IIS7 in windows server 2008 enable people to extend existing IIS configuration sections. If you have a schema file in schema folder which defines section already defined in IIS_Schema.xml, IIS configuration system will merge the schema defined in these two files. This enables you to add owner, email, phone properties to sites and have then keep this data under sites section in applicationHost.config. In addition to this, there is a new concept of dynamic properties. These are the properties which doesn’t have static values in configuration files but whose values can be supplied dynamically by some COM components. Dynamic properties can support get/set operations as other configuration properties. Other than get and set, configuration system allows you to define methods in schema which can be called using configuration APIs. These methods can accept input and return output as well. A perfect example which uses all this functionality of configuration system is part of the product itself. All RSCA data which could be earlier obtained using RSCA APIs (appcmd, UI, WMI use RSCA APIs underneath) is now exposed using dynamic properties. For this IIS has rscaext.xml in schema folder which extends existing system.applicationHost/sites and system.applicationHost/applicationPools sections defined in IIS_Schema.xml. Taking example of system.applicationHost/sites section, rscaext.xml has the following to extend it.

<sectionSchemaname="system.applicationHost/sites">
 <collectionaddElement="site">
   <attributename="state"type="enum"extension="Microsoft.ApplicationHost.RscaExtension">
     <enumname="Starting"value="0" />
     <enumname="Started"  value="1" />
     <enumname="Stopping"value="2" />
     <enumname="Stopped"  value="3" />
     <enumname="Unknown"  value="4" />
   </attribute>
   <methodname="Start"extension="Microsoft.ApplicationHost.RscaExtension" />
   <methodname="Stop"extension="Microsoft.ApplicationHost.RscaExtension" />
 </collection>
</sectionSchema>

This part of schema tells configuration system that for each collection element ‘site’, there is an additional attribute ‘state’ of type ‘enum’ and its value can be Starting|Started|Stopping|Stopped|Unknown. Additionally there are two methods which are supported for site, ‘Start’ and ‘Stop’. There is an additional property in schema in these attributes and methods which makes them dynamic and that is ‘extension’. This tells the configuration system which COM component to call when someone gets or sets the attribute or call the method defined in schema. For dealing with dynamic properties, additional interfaces which got added in AhAdmin are IAppHostMethodCollection, IAppHostMethod, IAppHostMethodInstance, IAppHostMethodSchema, IAppHostMethodExtension, IAppHostPropertyExtension, IAppHostElementExtension. A sample program which prints state of each site after calling Start and Stop method is written below.

try
{
    var ahadmin = new ActiveXObject("Microsoft.ApplicationHost.AdminManager");

    //
    // Get sites section
    //
    var sitesSection = ahadmin.GetAdminSection(
        "system.applicationHost/sites",
        "MACHINE/WEBROOT/APPHOST");

    //
    // Go through all sites
    //
    var sitesCollection = sitesSection.Collection;
    for(var i = 0; i < sitesCollection.Count; i++)
    {
        var siteElement = sitesCollection.Item(i);

        //
        // Print site name and its current state
        //
        var nameProperty = siteElement.GetPropertyByName("name");
        WScript.Echo("Site – " + nameProperty.Value);

        var stateProperty = siteElement.GetPropertyByName("state");
        var statePropertySchema = stateProperty.Schema;
        var possibleValues = statePropertySchema.PossibleValues;
        WScript.Echo(" State = " + possibleValues.Item(stateProperty.Value).Name);

        //
        // Get Start and Stop methods as in rscaext.xml
        //
        var methodsCollection = siteElement.Methods;
        var stopMethod = methodsCollection.Item("Stop");
        var startMethod = methodsCollection.Item("Start");

        //
        // Execute method Stop and print state
        //
        WScript.Echo(" Executing " + stopMethod.Name);
        stopMethod.CreateInstance().Execute();
        WScript.Echo(" State = " + possibleValues.Item(stateProperty.Value).Name);

        //
        // Execute method Start and print state
        //
        WScript.Echo(" Executing " + startMethod.Name);
        startMethod.CreateInstance().Execute();
        WScript.Echo(" State = " + possibleValues.Item(stateProperty.Value).Name);
    }
}
catch(e)
{
  WScript.Echo(e.number);
  WScript.Echo(e.description);
}

Site Start and Stop method definitions don’t specify inputElement and outputElement as these methods don’t accept any input parameters and don’t return anything. For an example of a method which accepts input and gives output, see schema of GetRequests and GetCustomData methods. Sample below shows how to call GetRequests method to prints all requests in progress with timeElapsed > 5000msec.

try
{
    var ahadmin = new ActiveXObject("Microsoft.ApplicationHost.AdminManager");

    //
    // Get applicationPools section
    //
    var appPoolsSection = ahadmin.GetAdminSection(
        "system.applicationHost/applicationPools",
         "MACHINE/WEBROOT/APPHOST");

    //
    // Go through all apppools and print
    // currently executing requests
    //
    var appPoolsCollection = appPoolsSection.Collection;
    for(var i = 0; i < appPoolsCollection.Count; i++)
    {
        var appPoolElement = appPoolsCollection.Item(i);

        //
        // Print applicationPool name and state
        //
        var appPoolName = appPoolElement.GetPropertyByName("name").Value;
        var appPoolStateProperty = appPoolElement.GetPropertyByName("state");
        var appPoolStateName = appPoolStateProperty.Schema.PossibleValues.Item(appPoolStateProperty.Value).Name;
        WScript.Echo("Application Pool – " + appPoolName + " (" + appPoolStateName + ")");

        //
        // Get worker process serving this application pool
        //
        var workerProcessesElement = appPoolElement.ChildElements.Item("workerProcesses");
        var workerProcessCollection = workerProcessesElement.Collection;

        //
        // Go through worker process collection
        // and execute GetRequests method for each
        //
        for(var j = 0; j < workerProcessCollection.Count; j++)
        {
            var workerProcess = workerProcessCollection.Item(j);
            WScript.Echo("  Worker process – " + workerProcess.GetPropertyByName("processId").Value);

            var getRequestsMethod = workerProcess.Methods.Item("GetRequests");
            var getRequestsMethodInstance = getRequestsMethod.CreateInstance();

            //
            // Set timeElapsedFilter to 5000 to get requests with
            // time elapsed > 5000 msec. To get all the currently
            // executing requests, leave default value of 0
            //
            var inputElement = getRequestsMethodInstance.Input;
            inputElement.GetPropertyByName("timeElapsedFilter").Value = 5000;

            getRequestsMethodInstance.Execute();

            var output = getRequestsMethodInstance.Output;
            var requestsCollection = output.Collection;

            for(var i = 0; i < requestsCollection.Count; i++)
            {
                WScript.Echo("    Request");
                var requestPropertiesCollection = requestsCollection.Item(i).Properties;

                for (var j = 0; j < requestPropertiesCollection.Count; j++)
                {
                    var requestProperty = requestPropertiesCollection.Item(j);
                    WScript.Echo("      " + requestProperty.Name + "=" +
                        requestProperty.StringValue);
                }
            }
        }
    }
}
catch(e)
{
    WScript.Echo(e.number);
    WScript.Echo(e.description);
}

Hope this helps.
Kanwal