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);
} 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