Using the WMI Provider to Configure IIS 6.0 - A Data Driven Approach
Many of us use scripts to setup our web servers, a lot of the work is boilerplate - create sites, vroots.... To ease maintanence work over the last year i've moved to a data driven approach to setting up web sites. I use an XML configuration file to describe the web site and a driver program that uses WMI to configure IIS based on the XML. Some of the benefits of this are : you only need to make fixes to the single driver program ; anyone can edit/create the configuration XML; operations has to deal with a single way to setup all your sites; updates to the schema are available to all sites (e.g: you add support to set content expiration). I spent some time today looking at support for WMI in C# and thought it may be useful to folks who're just starting out. In the rest of this post i'll show you a simplified example to give you a better feel for what i'm talking about.
You can find the WMI IIS 6.0 provider described here.
The Scripting Guys have a great introduction to WMI.
You can find some WMI related tools on MSDN.
Sample Configuration:
<?xml version="1.0" encoding="utf-8" ?>
<webSettings>
<webSites>
<webSite ServerId="1234" ServerComment="My Test WebSite" PathOfDefaultVroot="c:\inetpub\wwwroot\Test" >
<siteSettings>
<propertyList>
<property name="AllowKeepAlive" value="True"/>
<property name="ConnectionTimeout" value="90"/>
</propertyList>
<serverBinding Hostname="" IP="" Port="80"/>
</siteSettings>
</webSite>
</webSites>
</webSettings>
The property names used in the configuration match the names used by WMI, to make it easier to relate to the documentation. I've left out a lot in the sample such as ... templates to store common settings (e.g. custom error pages, supported mime types, log settings); server settings (e.g. AppPool creation); vroot creation ...
The driver program (a console app) uses a helper class to load and apply the configuration to the target server. The WMI classes are in the System.Management namespace (that you need to add a reference to). This is just a first stab at using the WMI classes, do let me know if i'm missing something.
class App
{
[STAThread]
static int Main (string[] args)
{
if (args.Length != 2)
{
StdOut ("Usage: Xml2IIs <targetServer> <configPath>");
StdOut (" e.g: Xml2IIs localhost mywebsites.xml");
return 1;
}
string target = args[0];
string configPath = args[1];
XmlConfig config = new XmlConfig (configPath);
if (config.ApplySettings (target))
return 0;
else
return 1;
}
public static void LogException (Exception ex)
{
Console.Error.WriteLine ("EXCEPTION!> " + ex.Source);
Console.Error.WriteLine ("EXCEPTION!> " + ex.Message);
Console.Error.WriteLine ("EXCEPTION!> " + ex.StackTrace);
}
public static void StdOut (string msg)
{
Console.WriteLine (">{0}", msg);
}
}
XmlConfig is the helper class that processes the XML configuration, it moves thru' the different sections and uses a WMI wrapper class to apply the settings to the IIS Metabase
public class XmlConfig
{
public XmlConfig (string configPath)
{
Load (configPath);
}
public bool ApplySettings (string target)
{
if (_doc == null) return false;
// connect to WMI on target server
_wmi = new WmiIIS ();
_wmi.Connect (target);
if (!_wmi.IsConnected ()) return false;
// process website nodes
XmlNodeList siteNodes = _doc.SelectNodes (@"webSettings/webSites/webSite");
foreach (XmlNode siteNode in siteNodes)
{
HandleWebSite (siteNode);
}
return true;
}
private bool HandleWebSite (XmlNode siteNode)
{
try
{
XmlNode settingsNode = siteNode.SelectSingleNode ("siteSettings");
XmlNodeList bindingNodes = settingsNode.SelectNodes ("serverBinding");
ManagementBaseObject[] serverBindings = new ManagementBaseObject[bindingNodes.Count];
int i = 0;
foreach (XmlNode bindingNode in bindingNodes)
{
serverBindings[i++] =
_wmi.CreateServerBinding (bindingNode.Attributes["Hostname"].Value,
bindingNode.Attributes["IP"].Value,
bindingNode.Attributes["Port"].Value);
}
string wmiPath = _wmi.CreateWebSite (siteNode.Attributes["ServerId"].Value,
siteNode.Attributes["ServerComment"].Value,
siteNode.Attributes["PathOfDefaultVroot"].Value,
serverBindings);
ManagementObject oSite = _wmi.GetInstance (wmiPath.Replace("IIsWebServer","IIsWebServerSetting"));
App.StdOut("Connected To..." + oSite.Path);
XmlNode propertyNodes = settingsNode.SelectSingleNode ("propertyList");
if (propertyNodes != null && propertyNodes.ChildNodes.Count > 0)
{
Hashtable tableProperty = new Hashtable ();
foreach (XmlNode propertyNode in propertyNodes.ChildNodes)
{
string name, value;
name = propertyNode.Attributes["name"].Value;
value = propertyNode.Attributes["value"].Value;
tableProperty.Add (name, value);
}
_wmi.ApplyPropertyList (oSite, tableProperty);
}
return true;
}
catch (Exception ex)
{
App.LogException (ex);
return false;
}
}
private bool Load (string configPath)
{
try
{
_doc = new XmlDocument ();
_doc.Load (configPath);
return true;
}
catch (Exception ex)
{
App.LogException (ex);
_doc = null;
return false;
}
}
private XmlDocument _doc = null;
private WmiIIS _wmi = null;
}
Finally we have a WMI wrapper class that interfaces with WMI to actually update the IIS metabase
public class WmiIIS
{
public WmiIIS ()
{
}
public bool IsConnected()
{
if (_target == null || _connection == null || _scope == null) return false;
return _scope.IsConnected;
}
public bool Connect (string target)
{
if (target == null) return false;
try
{
_target = target;
_connection = new ConnectionOptions ();
_scope = new ManagementScope (@"\\" + target + @"\root\MicrosoftIISV2", _connection);
_scope.Connect ();
App.StdOut ("Connected To... " + target);
}
catch (Exception ex)
{
App.LogException (ex);
return false;
}
return IsConnected ();
}
public ManagementObject CreateServerBinding (string HostName, string IP, string Port)
{
try
{
ManagementClass classBinding = new ManagementClass (_scope, new ManagementPath ("ServerBinding"), null);
ManagementObject serverBinding = classBinding.CreateInstance ();
serverBinding.Properties["Hostname"].Value = HostName;
serverBinding.Properties["IP"].Value = IP;
serverBinding.Properties["Port"].Value = Port;
serverBinding.Put ();
return serverBinding;
}
catch (Exception ex)
{
App.LogException (ex);
return null;
}
}
public string CreateWebSite (string serverID, string serverComment, string defaultVrootPath, ManagementBaseObject[] serverBinding)
{
if (serverID == null || serverID.Length == 0) return null;
if (defaultVrootPath == null || defaultVrootPath.Length == 0) return null;
if (serverComment == null || serverComment.Length == 0) return null;
if (serverBinding == null) return null;
try
{
ManagementObject oW3SVC = new ManagementObject (_scope, new ManagementPath (@"IIsWebService='W3SVC'"), null);
App.StdOut("Connected To..." + oW3SVC.Path);
if (IsWebSiteExists (serverID))
{
App.StdOut ("Site Already Exists..." + serverID);
DeleteSite (serverID);
}
ManagementBaseObject inputParameters = oW3SVC.GetMethodParameters ("CreateNewSite");
inputParameters["ServerComment"] = serverComment;
inputParameters["ServerBindings"] = serverBinding;
inputParameters["PathOfRootVirtualDir"] = defaultVrootPath;
inputParameters["ServerId"] = serverID;
ManagementBaseObject outParameter = null;
outParameter = oW3SVC.InvokeMethod ("CreateNewSite", inputParameters, null);
return (string)outParameter.Properties["ReturnValue"].Value;
}
catch (Exception ex)
{
App.LogException (ex);
return null;
}
}
public bool ApplyPropertyList(ManagementObject managementObject, Hashtable tableProperty)
{
try
{
App.StdOut ("Setting Properties On..." + managementObject.Path);
foreach (string key in tableProperty.Keys)
{
Console.WriteLine ("Setting...{0}={1}", key, tableProperty[key]);
managementObject.Properties[key].Value = tableProperty[key];
}
managementObject.Put ();
return true;
}
catch (Exception ex)
{
App.LogException (ex);
return false;
}
}
public ManagementObject GetInstance (string wmiPath)
{
try
{
return new ManagementObject(_scope, new ManagementPath(wmiPath),null);
}
catch (Exception ex)
{
App.LogException (ex);
return null;
}
}
public bool IsWebSiteExists (string serverID)
{
try
{
string siteName = "W3SVC/" + serverID;
ManagementObjectSearcher searcher = new ManagementObjectSearcher (_scope, new ObjectQuery ("SELECT * FROM IIsWebServer"), null);
ManagementObjectCollection webSites = searcher.Get ();
foreach (ManagementObject webSite in webSites)
{
if ((string)webSite.Properties["Name"].Value == siteName)
return true;
}
return false;
}
catch (Exception ex)
{
App.LogException (ex);
return false;
}
}
public bool DeleteSite (string serverID)
{
try
{
string serverName = "W3SVC/" + serverID;
ManagementObject webSite = new ManagementObject (_scope, new ManagementPath (@"IIsWebServer='" + serverName + "'"), null);
App.StdOut ("Stopping..." + webSite.Path);
webSite.InvokeMethod ("Stop", null);
App.StdOut ("Deleting..." + webSite.Path);
webSite.Delete();
webSite = null;
return true;
}
catch (Exception ex)
{
App.LogException (ex);
return false;
}
}
string _target = null;
ManagementScope _scope = null;
ConnectionOptions _connection = null;
}
Bye for now
- ramesh