Using ahadmin to read/write IIS configuration – Part 2

 Continuing my ahadmin drill down, lets see how to use available interfaces to work with section groups, section definitions, locations and metadata. IAppHostConfigFile interface Working with section groups, section definitions and locations require you to get an instance IAppHostConfigFile which is obtained using IAppHostConfigManager.GetConfigFile(). Following code gets IAppHostConfigFile instances for machine.config, root web.config, applicationHost.config and prints file path of each.

var ahwrite = new ActiveXObject("Microsoft.ApplicationHost.WritableAdminManager"); 

// Get ConfigManager using IAppHostAdminManager.ConfigManager get property
var configManager = ahwrite.ConfigManager;

// Get IAppHostConfigFile objects for machine.config, root web.config
// and applicationHost.config

var machineConfig = configManager.GetConfigFile("MACHINE");
var rootWebConfig = configManager.GetConfigFile("MACHINE/WEBROOT");
var appHostConfig = configManager.GetConfigFile("MACHINE/WEBROOT/APPHOST"); 

// Use FilePath get property to print paths
WScript.Echo(machineConfig.FilePath);
WScript.Echo(machineConfig.FilePath);
WScript.Echo(machineConfig.FilePath); 

I get the following output on my machine.

 \\?\C:\Windows\Microsoft.NET\Framework\v2.0.50727\config\machine.config\\?\C:\Windows\Microsoft.NET\Framework\v2.0.50727\config\web.config\\?\C:\Windows\system32\inetsrv\config\applicationHost.config

Working with section groups and definitions 

You can use AhAdmin to get an instance of IAppHostSectionGroup corresponding to a section group defined in a config file. A section group can further contain multiple sections or section groups. IAppHostSectionGroup.Sections gives collection of sections defined in a section group and IAppHostSectionGroup.Count and IAppHostSectionGroup.Item() can be used to get count and sub section groups in a section group. Following code prints all the sections and section groups declared in machine.config. 

// Get IAppHostSectionGroup for root section group corresponding
// to <configSections>…</configSections>
var rootSectionGroup = machineConfig.RootSectionGroup;
PrintSectionGroup(rootSectionGroup, 0); 

function PrintSectionGroup(rootSectionGroup, level)
{
    var sectionGroupName = rootSectionGroup.Name;
    var sectionGroupType = rootSectionGroup.Type;

    PrintWithIndent("START – sectionGroup name=" + sectionGroupName +
        " type=" + sectionGroupType, level);

    for(var i = 0; i < rootSectionGroup.Count; i++)
    {
        // Call PrintSectionGroup for all the section
        // groups defined under this section group
        PrintSectionGroup(rootSectionGroup.Item(i), level + 1);
    }

    // Now print all the sections in this sectionGroup
    var sections = rootSectionGroup.Sections;
    if(sections != null)
    {
        for(var i = 0; i < sections.Count; i++)
        {
            var sectionName = sections.Item(i).Name;
            var sectionType = sections.Item(i).Type;
            var omDefault = sections.Item(i).OverrideModeDefault;
            var allowDefinition = sections.Item(i).AllowDefinition;
            var allowLocation = sections.Item(i).AllowLocation;

            PrintWithIndent(
                "section name=" + sectionName + " type=" + sectionType +
                    " overrideModeDefault=" + omDefault + " allowDefinition=" +
                    allowDefinition + " allowLocation=" + allowLocation,
                level + 1);
        }
    }
    
    PrintWithIndent("END", level);
}

function PrintWithIndent(str, indent)
{
    var prefix = "";
    
for(var i = 0; i < indent; i++)
    {
        prefix += "-";
    }

    
WScript.Echo(prefix + str);
}

Code above will print all the section groups and sections defined in a config file. Due to a bug in Vista RTM, type information for sections is always returned blank. This has been fixed for LH server. Item method both in IAppHostSectionGroup and IAppHostSectionDefinitionCollection takes either index or name of section group or section. Also both these interface provides methods to add/delete section group or sections under a section group. Lets write a simple script to define a new section group named system.myServer in applicationHost.config and add a section for MyAuthenticationModule.

var rootSectionGroup = appHostConfig.RootSectionGroup;
var newSectionGroup = rootSectionGroup.AddSectionGroup("system.myServer");

// Set the type if required using newSectionGroup.Type = ""; 
// Create a new section definition under the new section group
var newSection = newSectionGroup.Sections.AddSection("MyAuthenticationModule");

// Set newSection properties
newSection.OverrideModeDefault = "Allow";
newSection.AllowDefinition          = "MachineToApplication";
newSection.AllowLocation           = "true";

// Set type if required using newSection.Type; 
ahwrite.CommitChanges(); 

This writes the following under <configSections> in applicationHost.config. 

<sectionGroup name="system.myServer">
    <section name="MyAuthenticationModule" overrideModeDefault="Allow" allowDefinition="MachineToApplication" allowLocation="true" />
</sectionGroup>

Code to delete this added section and section group looks like following.

// You can use index or name of sectionGroup and section
rootSectionGroup.Item("system.myServer").Sections.DeleteSection("MyAuthenticationModule");
rootSectionGroup.DeleteSectionGroup("system.myServer");

Working with location tags

I have following contents in my "Default Web Site" root web.config. Code samples below play with this web.config. Make sure you have httpErrors section unlocked in applicationHost.config before trying these samples. 

<configuration>
    <system.webServer>
        <httpErrorserrorMode="Custom" />
    </system.webServer>
     <locationpath="iisstart.htm">
        <system.webServer>
            <httpErrorserrorMode="Detailed" />
        </system.webServer>
    </location>
 </configuration>

Program to set system.webServer/httpErrors errorMode to "Detailed" for test subfolder will look like following.

// Very first thing you should do is set the CommitPath.
// Setting CommitPath after getting IAppHostConfigFile won’t work

var filePath            = "MACHINE/WEBROOT/APPHOST/Default Web Site"; 
ahwrite.CommitPath = filePath;

// Get IAppHostConfigFile object corresponding to this config
// file and print web.config file path.

var siteConfig = configManager.GetConfigFile(filePath);
WScript.Echo(siteConfig.FilePath);

var locations = siteConfig.Locations;
WScript.Echo("Location count = " + locations.Count);

for
(var i = 0; i < locations.Count; i++)
{
    WScript.Echo(i + ". Path=" + locations.Item(i).Path);
} 


Above code will print location count = 2. One location path will be "" and other "iisstart.htm". Location with path blank contains all sections outside any <location> tag in web.config. So how can we write code to add the following to web.config?

<locationpath="test">
    <system.webServer>
        <httpErrorserrorMode="Detailed" />
    </system.webServer>
</location>

There are many ways to do this. One way is to use IAppHostAdminManager.GetAdminSection or IAppHostConfigFile.GetAdminSection for MACHINE/WEBROOT/APPHOST/Default Web Site/test config path, set the property value and commit (CommitPath is already set to "MACHINE/WEBROOT/APPHOST/Default Web Site"). Other option is to add the location tag with path "test", add section system.webServer/httpErrors using IAppHostConfigLocation.AddConfigSection, set the property value and commit to Default Web Site commit path. Following code shows both these ways. 

// using IAppHostConfigFile.GetAdminSection.
// Using IAppHostAdminManager.GetAdminSection gives same results
var testHttpErrorsSection =

    siteConfig.GetAdminSection(
        "system.webServer/httpErrors",

          "MACHINE/WEBROOT/APPHOST/Default Web Site/test");
testHttpErrorsSection.Properties.Item("errorMode").Value = "Detailed"; 

// using IAppHostConfigLocation.AddConfigSection

var httpErrorsSection =
   siteConfig.Locations.AddLocation(

        "test").AddConfigSection("system.webServer/httpErrors");
httpErrorsSection.Properties.Item("errorMode").Value = "Detailed"; 

// Commit changes using IAppHostWritableAdminManager.CommitChanges
ahwrite.CommitChanges;

Metadata 

IAppHostAdminManager metadata available 

Metadata-Name Type R/W     
pathMapper IAppHostPathMapper R/W 
changeHandler IAppHostChangeHandler R/W
ignoreInvalidAttributes Bool R/W
ignoreInvalidRanges Bool R/W   
ignoreInvalidDecryption Bool R/W
expandEnvironmentStrings Bool R/W
availableSections String R
mappingExtension IAppHostMappingExtension R

 IAppHostElement metadata available 

Metadata-Name Type R/W     
overrideMode String R/W  
effectiveOverrideMode String R    
deepestPathSet String R    
deepestFileNameSet String R    
deepestFileLineNumberSet uint32 R    
configSource String R/W  
isPresent Bool R          
lockItem Bool R/W
lockAllElementsExcept String R/W  
lockElements String R/W  
lockAllAttributesExcept String R/W  
lockAttributes String R/W 
isLocked Bool R

 IAppHostProperty metadata available 

Metadata-Name Type R/W  
encryptionProvider String R/W    
isPropertyEncrypted Bool R      
isDefaultValue Bool R      
isInheritedFromDefault Bool R     
isLocked Bool R

 

Not all metadata properties are available everywhere and not all are persisted to disk as well. These work if they make sense. Following samples illustrates how to get/set metadata attached to various IAppHost objects.

// Get comma separated list available sections
WScript.Echo(ahwrite.GetMetadata("availableSections"));

// Get expandEnvironmentStrings and set to true.
// Change is not persisted to disk.
WScript.Echo(ahwrite.GetMetadata("expandEnvironmentStrings");
ahwrite.SetMetadata("expandEnvironmentStrings", true); 

// Lock a collection item under system.webServer/modules
var modulesSection =
    ahwrite.GetAdminSection("system.webServer/modules", "MACHINE/WEBROOT/APPHOST");
modulesSection.Collection.Item(0).SetMetadata("lockItem", false);
ahwrite.CommitChanges(); 

// Get IAppHostMappingExtension interface and use it to get site element
var mappingExtension = ahwrite.GetMetadata("mappingExtension");
var defaultSiteElement = mappingExtension.GetSiteElementFromSiteId(1);
WScript.Echo(defaultSiteElement.GetElementByName(
                       "bindings").Collection.Item(0).GetPropertyByName("bindingInformation").Value);

// Check if the effective value is defaultValue for
// doDynamicCompression property
var urlCompSection = ahwrite.GetAdminSection("system.webServer/urlCompression", "MACHINE/WEBROOT/APPHOST");
WScript.Echo(urlCompSection.GetPropertyByName("doDynamicCompression").GetMetadata("isDefaultValue")); 

// Get encryption provider for apppool password
var apppoolsSection = ahwrite.GetAdminSection("system.applicationHost/applicationPools", "MACHINE/WEBROOT/APPHOST");
var apProcessModel = apppoolsSection.Collection.Item(2).GetElementByName("processModel");
WScript.Echo(apProcessModel.GetPropertyByName("password").GetMetadata("encryptionProvider"));

Together with my earlier post, you should be able to do about anything using ahadmin.

Good luck.
Kanwal

Using ahadmin to read/write IIS configuration – Part 1

The Application Host Administration API (ahadmin.idl) interface library can be accessed using native code and any COM-interop means like script or managed code. This blog post details how to use this interface in scripts (all samples are in javascript) to read/write IIS7 configuration information.

Getting/Setting property values

You start by getting an instance of IAppHostAdminManager if you want to only read the config. If you want to write to config as well, you will need an instance of IAppHostWritableAdminManager which derives from IAppHostAdminManager. IAppHostWritableAdminManager has additionally CommitChanges method to commit changes to disk and CommitPath get/set property which dictates where the configuration settings are written. Lets create an instance of these.

var ahread = new ActiveXObject("Microsoft.ApplicationHost.AdminManager");
var ahwrite = new ActiveXObject("Microsoft.ApplicationHost.WritableAdminManager"); 


We will work with ahwrite in this post. You should always use an instance of AdminManager to read the config which always give you effective config for a config path. WritableAdminManager might give wrong values which can vary depending on value of CommitPath. If you are not planning to write to IIS config files, you can just replace ahwrite with ahread in the samples. CommitChanges(), CommitPath get/set calls need ahwrite. Lets read system.webServer/urlCompression doStaticCompression, doDynamicCompression property values for server root using IAppHostAdminManager. You do this by getting IAppHostElement instance corresponding to urlCompression section using GetAdminSection. GetAdminSection take two parameters. First parameter is the section name and second parameter is config path. MACHINE/WEBROOT/APPHOST is the path corresponding to server root. This can be changed to MACHINE/WEBROOT/APPHOST/<SITENAME>/<APPNAME>/<VDIR>/<FOLDER>/<FILENAME>.

var urlCompressionSection = ahwrite.GetAdminSection("system.webServer/urlCompression", "MACHINE/WEBROOT/APPHOST");

// Get properties collection for section/element
var urlCompressionProperties = urlCompressionSection.Properties;

IAppHostElement.Properties return in instance of IAppHostPropertyCollection. This interface has Count property which gives you number of properties in the collection and each individual property item can be obtained using Item method. Each individual property item is an instance of IAppHostProperty which has a get/set Value property. Following lines will print the values of doStaticCompression and doDynamicCompression properties.

WScript.Echo(urlCompressionProperties.Item("doStaticCompression").Value);
WScript.Echo(urlCompressionProperties.Item("doDynamicCompression").Value);

Item can be used with indexes as well. Following code gives you the same output as above.

WScript.Echo(urlCompressionProperties.Item(0).Value);
WScript.Echo(urlCompressionProperties.Item(1).Value);

Instead of getting all the properties collection and picking one property item, IAppHostElement interface provides a utility method GetPropertyByName which can be used directly to get IAppHostProperty interface for an attribute. So above code can be written as

WScript.Echo(urlCompressionSection.GetPropertyByName("doStaticCompression").Value);
WScript.Echo(urlCompressionSection.GetPropertyByName("doDynamicCompression").Value);

IAppHostProperty.Value setter can be used to change the value as well. Following code sets doDynamicCompression to true in memory. Changes are not written to disk until you call IAppHostWritableAdminManager.CommitChanges.

urlCompressionProperties.Item("doDynamicCompression").Value = true;
ahwrite.CommitChanges();

You should see doDyanmicCompression set to true in applicationHost.config. What if you wanted to enable doDynamicCompression only for Default web site. This requires you to get urlCompression section for that site as written below.

var urlCompressionSection = ahwrite.GetAdminSection("system.webServer/urlCompression", "MACHINE/WEBROOT/APPHOST/Default Web Site");

If you follow the same steps as written above, you will see doDynamicCompression set to true in applicationHost.config under a location tag for Default Web Site. What about writing these settings in Default Web Site root web.config? If you get the default commit path using ahwrite.CommitPath, it will return you MACHINE/WEBROOT/APPHOST which indicates that if you make any changes using ahadmin, changes will be persisted in applicationHost.config. Lets change CommitPath to write to Default Web Site root web.config.

ahwrite.CommitPath = "MACHINE/WEBROOT/APPHOST/Default Web Site");
urlCompressionSection.Properties.Item("doDynamicCompression").Value = true;
ahwrite.CommitChanges();

Running code above will give you the following in site root web.config.

<configuration>
    <system.webServer>
        <urlCompressiondoDynamicCompression="true" />
    </system.webServer>
</configuration>

Lets use ahadmin to work with default collections. Default collections are collections which are not contained in a child element in a section. There can be only one default collection under a section. Mime types collection under system.webServer/staticContent is one such example. Lets get list of mimeTypes defined in applicationHost.config.

var staticContentSection = ahwrite.GetAdminSection("system.webServer/staticContent", "MACHINE/WEBROOT/APPHOST");

// Get IAppHostElementCollection which points to the entire collection of elements
var mimeTypesCollection = staticContentSection.Collection;

IAppHostElementCollection interface can be used to get collection count, add/delete/clear and to get individual collection elements. Each collection element is again an instance of IAppHostElement and can further have default collections. Lets play with it a little.

// Print the number of mimeType entries in the collection
WScript.Echo("Count=" + mimeTypesCollection.Count + "\n");

// Print properties of first 10 collection elements
for(var i = 0; i < 10; i++)
{
    var mimeElement = mimeTypesCollection.Item(i);

    
// Print its properties
    var mimeElementProperties = mimeElement.Properties;
    var printString = "";

    for(var j = 0; j < mimeElementProperties.Count; j++)
    {
        // Get the value of each property using index
        printString += mimeElementProperties.Item(j).Value;
        if(j < mimeElementProperties.Count – 1)
        {
            printString += ", ";
        }
    }

    WScript.Echo(printString);
}

I get the following on my machine.

Count=338 .323, text/h323
.aaf, application/octet-stream
.aca, application/octet-stream
.accdb, application/msaccess
.accde, application/msaccess
.accdt, application/msaccess
.acx, application/internet-property-stream
.afm, application/octet-stream
.ai, application/postscript
.aif, audio/x-aiff

What if I want to print the name of the attributes with their values as well. But I dont remember the schema of mimeType element. Lets first get property names from schema.

var mimeElementPropertiesCollection = mimeTypesCollection.Item(0).Properties;
var mimeElementSProperties = new Array();

for(var i = 0; i < mimeElementPropertiesCollection.Count; i++)

{
    // Get IAppHostElementSchema by calling IAppHostElement.SchemamimeElementSProperties[i] = mimeElementPropertiesCollection.Item(i).Schema.Name;

    WScript.Echo(mimeElementSProperties[i]);
}

Now that we have attribute names as well, we can modify the sample code above to print attribute names as well. Also mimeElementProperties.Item calls can be changed to take attribute name in place of index like following.

printString += mimeElementSProperties[j] + "=\"" + mimeElementProperties.Item(mimeElementSProperties[j]).Value + "\"";

 
What about child elements?

Child elements are elements under sections/elements and are treated little differently in ahadmin. Example of child elements under system.webServer/httpCompression section are staticTypes and dynamicTypes. IAppHostElement interface which is returned by GetAdminSection for a section can be used to get IAppHostElement for a child element. Lets write code to get all child elements present under httpCompression section.

// Get IAppHostElement for httpCompression section
var httpCompressionSection = ahwrite.GetAdminSection(
                                 "system.webServer/httpCompression",
                                 "MACHINE/WEBROOT/APPHOST");

// Get IAppHostChildElementCollection from IAppHostElement
var hcChildElementCollection = httpCompressionSection.ChildElements;

IAppHostChildElementCollection has Count property and Item method which can be used to get IAppHostElement instance of each child element. Following code get each child element by index and then print the count of collection elements contained in the collection under this element.

for(var i = 0; i < hcChildElementCollection.Count; i++)
{
    var childElement = hcChildElementCollection.Item(i);
    WScript.Echo(childElement.Name);

    
// Also print how many entries are there in the collection contained in this childElement
    WScript.Echo(childElement.Collection.Count);
}

Child elements can again have child elements which can be obtained using IAppHostElement.ChildElements.Individual child elements can also be obtained using element names from IAppHostChildElementCollection as in the code below.

var staticTypesChildElement   = hcChildElementCollection.Item("staticTypes");
var dynamicTypesChild Element = hcChildElementCollection.Item("dynamicTypes");

Similar to GetPropertyByName, IAppHostElement includes another utility function to get the child element by name directly. Above code can be written as 

var staticTypesChildElement = httpCompressionSection.GetElementByName("staticTypes");

var dynamicTypesChildElement = httpCompressionSection.GetElementByName("dynamicTypes");

Code below prints all sites, their binding information and applications/vdirs under each site.

var sitesSection    = ahwrite.GetAdminSection("system.applicationHost/sites", "MACHINE/WEBROOT/APPHOST");
var sitesCollection = sitesSection.Collection;

for(var i = 0; i < sitesCollection.Count; i++)
{
    var siteElement = sitesCollection.Item(i);

    
// We can use siteElement.Properties.Item("name").Value as well.
    var siteName = siteElement.GetPropertyByName("name").Value;
    var siteId   = siteElement.GetPropertyByName("id").Value;
    WScript.Echo("Site Name=" + siteName + " Id=" + siteId);

    // Also print bindings information
    // Bindings is a childElement under site element

    var bindingsElement = siteElement.ChildElements.Item("bindings");

    // We could use siteElement.GetElementByName("bindings"); as well
    var bindingsCollection = bindingsElement.Collection;
    for(var j = 0; j < bindingsCollection.Count; j++)
    {
        var binding     = bindingsCollection.Item(j);
        var protocol    = binding.GetPropertyByName("protocol").Value;
        var bindingInfo = binding.GetPropertyByName("bindingInformation").Value;

        WScript.Echo("Binding protocol=" + protocol +
                            " bindingInformation=" + bindingInfo);
    }

    var applications = siteElement.Collection;
    for(var j = 0; j < applications.Count; j++)
    {
        var appElement = applications.Item(j);
        var appPath    = appElement.Properties.Item("path").Value;
        WScript.Echo("- Application with path=" + appPath);

        
// Each application element has a default collection
        // for virtual directories
        var vdirs = appElement.Collection;

        
for(var k = 0; k < vdirs.Count; k++)
        {
            var vdirElement      = vdirs.Item(k);
            var vdirPath         = vdirElement.Properties.Item("path").Value;
            var vdirPhysicalPath = vdirElement.GetPropertyByName("physicalPath").Value;

            
WScript.Echo("– VDir path=" + vdirPath +
                                      " physicalPath=" + vdirPhysicalPath);
        }
    }

    WScript.Echo();
}   

 

    // Each siteElement has a default collection for applications

IAppHostElementCollection.CreateNewElement and AddNewElement can be used to create new collection elements.

Following code can be used to create a new site.

var sitesSection = ahwrite.GetAdminSection("system.applicationHost/sites", "MACHINE/WEBROOT/APPHOST"); 
// Create a new site element
var newSiteElement = sitesSection.Collection.CreateNewElement(); // Set site name and site idnewSiteElement.Properties.Item("name").Value = "My New Site";
newSiteElement.Properties.Item("id").Value   = 3;

// Create a new app under this site and set its path to /
var newAppElement = newSiteElement.Collection.CreateNewElement();
newAppElement.Properties.Item("path").Value = "/";

// Create new vdir and set its path and physical path
var newVdirElement = newAppElement.Collection.CreateNewElement();
newVdirElement.Properties.Item("path").Value         = "/";
newVdirElement.Properties.Item("physicalPath").Value = "%systemdrive%\\inetpub\\MyNewSite";

// Add vdir to app and then app to site
newAppElement.Collection.AddElement(newVdirElement);
newSiteElement.Collection.AddElement(newAppElement);

// Create a new binding element under childElement bindings under siteElement
var newBindingsElement = newSiteElement.ChildElements.Item("bindings").Collection.CreateNewElement(); 

// Set protocol and binding information
newBindingsElement.GetPropertyByName("protocol").Value = "http";
newBindingsElement.GetPropertyByName("bindingInformation").Value = "*:80:MyNewSite"; 

// Add new binding element to collection under child element bindings
newSiteElement.GetElementByName("bindings").Collection.AddElement(newBindingsElement); 

// Add new site element to default collection of sites section
sitesSection.Collection.AddElement(newSiteElement); 

// Commit changes to disk
ahwrite.CommitChanges();

This will write the following in system.applicationHost/sites section. 

<sitename="My New Site"id="3">
    <applicationpath="/">
        <virtualDirectorypath="/"physicalPath="%systemdrive%\inetpub\MyNewSite" />
    </application>
    <bindings>
        <bindingprotocol="http"bindingInformation="*:80:MyNewSite" />
    </bindings>
</site> 

Lets quickly write code to create an apppool as well. 

// Get the applicationPools section and its default collection
var apppoolsSection    = ahwrite.GetAdminSection("system.applicationHost/applicationPools, "MACHINE/WEBROOT/APPHOST");
var apppoolsCollection = apppoolsSection.Collection; 

// Create a new apppool element and set its name
var newAppPool = apppoolsCollection.CreateNewElement();
newAppPool.Properties.Item("name").Value = apppoolName; 

// Set process model properties under processModel child element
var processModel = newAppPool.GetElementByName("processModel");
processModel.Properties.Item("identityType").Value = "SpecificUser";
processModel.Properties.Item("userName").Value     = "newapppool";
processModel.Properties.Item("password").Value     = "newapppool"; 

// Add to app pools collection and commit changes to disk
apppoolsCollection.AddElement(newAppPool);
ahwrite.CommitChanges();

How about writing a script to put </clear> for this site to clear mimeTypes collection?

var staticContentSection = ahwrite.GetAdminSection("system.webServer/staticContent", "MACHINE/WEBROOT/APPHOST/My New Site");
var mimeTypesCollection = staticContentSection.Collection; 

// blank corresponds to addElement of collection.
// You can specify strings corresponding to removeElement and clearElement.
var clearElement = mimeTypesCollection.CreateNewElement("clear");
mimeTypesCollection.AddElement(clearElement);
ahwrite.CommitChanges();

This code writes following in applicationHost.config.

<locationpath="My New Site">
    <system.webServer>
        <staticContent>
            <clear />
        </staticContent>
    </system.webServer>
</location>   

Again, setting the CommitPath to "MACHINE/WEBROOT/APPHOST/My New Site" will make CommitChanges write to web.config of site root. I will cover working with section groups, section definitions, locations and metadata in my next post (available now).

Kanwal