Tom’s Blog

May 5, 2009

Using C# and WMI to Create and Configure IIS6 Web Sites

Filed under: C# — Tom Shelton @ 10:53 pm

Recently, I was worked on a project that required me to automate the installation and creation of web sites on IIS6.  I didn’t want to do this using VBScript or another WSH language because this was going to be used as part of a larger application.  I made some exploratory attempts using the ADSI interfaces, but did not find them satisfactory due to performance concerns.  After a little research, I discovered that since I was dealing with IIS6, that there were WMI interfaces available – so I decided to give that a whirl.  What I discovered, was that the while the performance using WMI was not what I would call spectacular, it was definitely a large improvement over using the ADSI interfaces.  The classes and code I’m sharing here are basic implementations, that accomplish only the basic tasks that I needed – so, there is definitely room for expansion on the part of the reader.  This is only meant as a starting point – I am still actively extending and refactoring these implementations my self.

What I was creating was basically a generic deployment tool – basically, this tool takes a package (a zip file) that contains the web site content and directory structure and XML document that describes the details of the deployment based on the machine name in the infrastructure.  This contains things like the name of directory to extract the package to, any web.config changes that need to be applied for the environment, and most importantly to this article a description of the IIS web site that should be created.

The first class I will share is the base class for most of the classes in my hierarchy – WmiObjectBase.

 

   1: using System.Collections.Generic;
   2: using System.Management;
   3:  
   4: namespace FireAnt.IIS.Util
   5: {
   6:     /// <summary>
   7:     /// Base object for WMI objects
   8:     /// </summary>
   9:     public abstract class WmiObjectBase
  10:     {
  11:         /// <summary>
  12:         /// Create and connect a management scope for use by subclasses
  13:         /// </summary>
  14:         /// <param name="path">The management path</param>
  15:         protected WmiObjectBase(string path)
  16:         {
  17:             ConnectionOptions connectionOptions = new ConnectionOptions ();
  18:             connectionOptions.Authentication = AuthenticationLevel.PacketPrivacy;
  19:             this.Scope = new ManagementScope (path , connectionOptions );
  20:             this.Scope.Connect ();
  21:         }
  22:  
  23:         /// <summary>
  24:         /// Allow subclasses to pass in an existing scope
  25:         /// </summary>
  26:         /// <param name="scope">A management scope object</param>
  27:         protected WmiObjectBase ( ManagementScope scope )
  28:         {
  29:             this.Scope = scope;
  30:             if ( !this.Scope.IsConnected )
  31:                 this.Scope.Connect ();
  32:         }
  33:         
  34:         /// <summary>
  35:         /// Let subclasses get their scope object
  36:         /// </summary>
  37:         protected ManagementScope Scope { get; private set; }
  38:  
  39:         /// <summary>
  40:         /// Allow subobjects to peform queries
  41:         /// </summary>
  42:         /// <param name="query">The object query to perform</param>
  43:         /// <returns>A ManagementObjectCollection containing the results of the query</returns>
  44:         protected ManagementObjectCollection this[ObjectQuery query]
  45:         {
  46:             get
  47:             {
  48:                 return new ManagementObjectSearcher ( this.Scope, query ).Get ();
  49:             }
  50:         }
  51:  
  52:  
  53:         /// <summary>
  54:         /// Apply settings to a Management object.
  55:         /// </summary>
  56:         /// <param name="target">The ManagamentObject to apply the settings to</param>
  57:         /// <param name="properties">A Dictionary object containing the properties and their value.</param>
  58:         /// <remarks>
  59:         /// The properties Dictionary contains the name of the property as the key, and the value of the property as the value.
  60:         /// </remarks>
  61:         protected void ApplySettings ( ManagementObject target, Dictionary<string, object> properties )
  62:         {
  63:             foreach ( var kv in properties )
  64:             {
  65:                 target.Properties[kv.Key].Value = kv.Value;
  66:             }
  67:             target.Put ();
  68:         }
  69:  
  70:         /// <summary>
  71:         /// Allow subclasses and easy method of creating new ManagementObject instances
  72:         /// </summary>
  73:         /// <param name="path">The management path of the object</param>
  74:         /// <returns>The instance of the object</returns>
  75:         protected ManagementObject CreateManagementObject ( string path )
  76:         {
  77:             ManagementClass template = new ManagementClass ( this.Scope, new ManagementPath ( path ), null );
  78:             return template.CreateInstance ();
  79:         }
  80:  
  81:         /// <summary>
  82:         /// Allow subclasses to get instances of existing objects
  83:         /// </summary>
  84:         /// <param name="path">the management path</param>
  85:         /// <returns>An instance of the object</returns>
  86:         protected ManagementObject GetInstance ( string path )
  87:         {
  88:             return new ManagementObject ( this.Scope, new ManagementPath ( path ), null );
  89:         }
  90:     }
  91: }

This class simply provides some common functionality for the various WMI based objects in my library.

The main class in the library, is the InternetInformationServer class.  This class holds and manages the connection to an IIS6 instance.  It allows you to create and manipulate the various server objects, such as web sites and application pools:

 

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Management;
   6: using System.Text.RegularExpressions;
   7:  
   8: namespace FireAnt.IIS.Util
   9: {
  10:     public class InternetInformationServer : WmiObjectBase
  11:     {
  12:         /// <summary>
  13:         /// Create an instance of the InternetInformationServer class that is connected to the
  14:         /// local IIS instance.
  15:         /// </summary>
  16:         public InternetInformationServer () : base ( @"\\.\root\MicrosoftIISV2" ) { }
  17:  
  18:         /// <summary>
  19:         /// Create an instance of the InternetInformationServer class that is connected to the
  20:         /// instance defined by target.
  21:         /// </summary>
  22:         /// <param name="target">The name of the machine to connect to.</param>
  23:         public InternetInformationServer ( string target ) : base ( string.Format ( @"\\{0}\root\MicrosoftIISV2", target ) ) { }
  24:  
  25:         /// <summary>
  26:         /// Create a new website on the IIS instance.
  27:         /// </summary>
  28:         /// <param name="serverComment">The server comment.</param>
  29:         /// <param name="rootPath">The path to the root virtual directory of the web site.</param>
  30:         /// <returns>A new WebSite instance.</returns>
  31:         public WebSite CreateWebSite ( string serverComment, string rootPath )
  32:         {
  33:             return this.CreateWebSite ( serverComment, rootPath, new ServerBinding[] { new ServerBinding () } );
  34:         }
  35:  
  36:         /// <summary>
  37:         /// Create a new web site on the IIS instance.
  38:         /// </summary>
  39:         /// <param name="serverComment">The server comment.</param>
  40:         /// <param name="rootPath">The path to the root virtual directory of the web site.</param>
  41:         /// <param name="serverBindings">A list of ServerBinding objects to apply to the web site.</param>
  42:         /// <returns></returns>
  43:         public WebSite CreateWebSite ( string serverComment, string rootPath, ServerBinding[] serverBindings )
  44:         {
  45:             const string METHOD = "CreateNewSite";
  46:  
  47:             // create a list of serverbindings...
  48:             List<ManagementObject> bindings = new List<ManagementObject> ();
  49:             Array.ForEach ( serverBindings, binding => bindings.Add ( CreateServerBinding ( binding ) ) );
  50:  
  51:             // create the site..
  52:             ManagementObject w3svc = new ManagementObject ( this.Scope, new ManagementPath ( @"IISWebService='W3SVC'" ), null );
  53:             ManagementBaseObject parameters = w3svc.GetMethodParameters ( METHOD );
  54:  
  55:             parameters["ServerComment"] = serverComment;
  56:             parameters["ServerBindings"] = bindings.ToArray ();
  57:             parameters["PathOfRootVirtualDir"] = rootPath;
  58:  
  59:             ManagementBaseObject result = w3svc.InvokeMethod ( METHOD, parameters, null );
  60:             Match m = Regex.Match ( (string)result.Properties["ReturnValue"].Value, "'(.*?)'" );
  61:  
  62:             return new WebSite ( this.Scope, serverComment, m.Groups[1].Value );
  63:         }
  64:  
  65:         /// <summary>
  66:         /// Create a new application pool on the IIS instance
  67:         /// </summary>
  68:         /// <param name="name">The name of the application pool.</param>
  69:         /// <returns></returns>
  70:         public ApplicationPool CreateAppliationPool ( string name )
  71:         {
  72:             ApplicationPool appPool = new ApplicationPool ( this.Scope );
  73:             appPool.Name = string.Format ( "W3SVC/AppPools/{0}", name );
  74:  
  75:             ManagementObject poolTemplate = CreateManagementObject ( "IIsApplicationPoolSetting" );
  76:             poolTemplate.Properties["Name"].Value = appPool.Name;
  77:             poolTemplate.Put ();
  78:  
  79:             return appPool;
  80:         }
  81:  
  82:         private ManagementObject CreateServerBinding ( ServerBinding binding )
  83:         {
  84:             ManagementObject managementObject = CreateManagementObject ( "ServerBinding" );
  85:             managementObject.Properties["Hostname"].Value = binding.HostName;
  86:             managementObject.Properties["IP"].Value = binding.IPAddress;
  87:             managementObject.Properties["Port"].Value = binding.Port;
  88:  
  89:             return managementObject;
  90:         }
  91:  
  92:         /// <summary>
  93:         /// Iterator of all the sites on the IIS instance.
  94:         /// </summary>
  95:         public IEnumerable<WebSite> Sites
  96:         {
  97:             get
  98:             {
  99:                 ObjectQuery query = new ObjectQuery ( "SELECT * FROM IISWebServerSetting" );
 100:                 foreach ( ManagementObject item in this[query] )
 101:                 {
 102:                     yield return new WebSite ( this.Scope, item["ServerComment"].ToString (), item["Name"].ToString () );
 103:                 }
 104:             }
 105:         }
 106:  
 107:  
 108:         /// <summary>
 109:         /// Get a reference to an appliation pool on the IIS instance.
 110:         /// </summary>
 111:         /// <param name="applicationPoolId">The id of the application pool.</param>
 112:         /// <returns>An ApplicationPool object or null if not found.</returns>
 113:         public ApplicationPool GetApplicationPool ( string applicationPoolId )
 114:         {
 115:             ObjectQuery query = new ObjectQuery ( string.Format ( "SELECT * FROM IIsApplicationPoolSetting WHERE Name='W3SVC/AppPools/{0}'", applicationPoolId ) );
 116:             foreach ( ManagementObject item in this[query] )
 117:             {
 118:                 return new ApplicationPool ( this.Scope, (string)item.Properties["Name"].Value );
 119:             }
 120:             return null;
 121:         }
 122:  
 123:  
 124:         /// <summary>
 125:         /// Get a reference to an existing web site on the IIS instance based on the server comment
 126:         /// </summary>
 127:         /// <param name="serverComment">The server comment of the site you want to get.</param>
 128:         /// <returns>A WebSite object or null if not found.</returns>
 129:         /// <remarks>
 130:         /// This method is really a special purpose method - the server comment is not required to be unique in most cases.  But,
 131:         /// in my case it is.  If you can't guarentee uniqueness then you will either need to make this return the a list, or
 132:         /// uses the site id rather then the server comment.
 133:         /// </remarks>
 134:         public WebSite GetWebSite ( string serverComment )
 135:         {
 136:             ObjectQuery query = new ObjectQuery ( "SELECT * FROM IISWebServerSetting WHERE ServerComment = '" + serverComment + "'" );
 137:             foreach ( ManagementObject item in this[query] )
 138:             {
 139:                 return new WebSite ( this.Scope, item["ServerComment"].ToString (), item["Name"].ToString () );
 140:             }
 141:  
 142:             return null;
 143:         }
 144:  
 145:     }
 146: }

Please not the comments on the GetWebSite method…  In the infrastructure I’m working in, I can guarentee that the server comment is unique for a given web site.  In many cases, this can’t be guarenteed – so, really this method should be changed either to return a list of WebSite objects, or it should be refactored to take the site id – which is unique.

There are two overloads for the CreateWebSite method – one that takes a list of ServerBindings and one that doesn’t.  You’ll notice that the overload that does not take a list of bindings simply delegates to the method that does, and passes in a default binding object.

Here are the ApplicationPool, WebSite, and ServerBinding objects as I currently have them:

WebSite:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Management;
   6:  
   7: namespace FireAnt.IIS.Util
   8: {
   9:     public class WebSite : WmiObjectBase
  10:     {
  11:         private readonly string IdentityQuery;
  12:  
  13:         internal WebSite ( ManagementScope scope, string serverComment, string siteName ) : base(scope)
  14:         {
  15:             this.ServerComment = serverComment;
  16:             this.Name = siteName;
  17:             this.IdentityQuery = string.Format ( "SELECT * FROM IISWebServer WHERE Name = '{0}'", this.Name );
  18:         }
  19:  
  20:         public string ServerComment { get; set; }
  21:         public string Name { get; set; }
  22:  
  23:         //public void Continue ()
  24:         //{
  25:         //}
  26:  
  27:         //public void Pause ()
  28:         //{
  29:         //}
  30:  
  31:         public void Start ()
  32:         {
  33:             ServerState state = CurrentState;
  34:             if ( state == ServerState.Stopped )
  35:             {
  36:                 ObjectQuery query = new ObjectQuery ( IdentityQuery );
  37:                 foreach ( ManagementObject site in this[query] )
  38:                 {
  39:                     site.InvokeMethod ( "Start", null );
  40:                 }
  41:             }
  42:  
  43:         }
  44:  
  45:         public void Stop ()
  46:         {
  47:             ServerState state = CurrentState;
  48:             if ( state == ServerState.Started )
  49:             {
  50:                 ObjectQuery query = new ObjectQuery ( IdentityQuery );
  51:                 foreach ( ManagementObject site in this[query] )
  52:                 {
  53:                     site.InvokeMethod ( "Stop", null );
  54:                 }
  55:             }
  56:         }
  57:  
  58:         public WebVirtualDirectory DirectorySettings
  59:         {
  60:             get { return new WebVirtualDirectory ( this.Scope, this.Name ); }
  61:         }
  62:  
  63:         public ServerState CurrentState
  64:         {
  65:             get
  66:             {
  67:                 ObjectQuery query = new ObjectQuery ( string.Format ( "SELECT ServerState FROM IISWebServer WHERE Name = '{0}'", this.Name ) );
  68:  
  69:                 foreach ( ManagementObject item in this[query] )
  70:                 {
  71:                     return (ServerState)Convert.ToInt32 ( item["ServerState"] );
  72:                 }
  73:                 throw new Exception ( "Can't determine server's current state" );
  74:             }
  75:         }
  76:  
  77:         public override string ToString ()
  78:         {
  79:             return string.Format ( "{0} ({1})", ServerComment, Name );
  80:         }
  81:     }
  82:  
  83:     public enum ServerState
  84:     {
  85:         Starting = 1,
  86:         Started = 2,
  87:         Stopping = 3,
  88:         Stopped = 4,
  89:         Pausing = 5,
  90:         Paused = 6,
  91:         Continuing = 7,
  92:     }
  93: }

ApplicationPool:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Management;
   6:  
   7: namespace FireAnt.IIS.Util
   8: {
   9:     public class ApplicationPool : WmiObjectBase
  10:     {
  11:         internal ApplicationPool ( ManagementScope scope )
  12:             : base ( scope )
  13:         {
  14:         }
  15:  
  16:         internal ApplicationPool ( ManagementScope scope, string name )
  17:             : base ( scope )
  18:         {
  19:             this.Name = name;
  20:         }
  21:  
  22:         public string Name { get; internal set; }
  23:     }
  24: }

ServerBinding:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: namespace FireAnt.IIS.Util
   7: {
   8:     public class ServerBinding
   9:     {
  10:         public ServerBinding () : this ( string.Empty, string.Empty, "80" ) { }
  11:         public ServerBinding ( string hostName, string ipAddress, string port )
  12:         {
  13:             this.HostName = hostName;
  14:             this.IPAddress = ipAddress;
  15:             this.Port = port;
  16:         }
  17:  
  18:         public string HostName { get; set; }
  19:         public string IPAddress { get; set; }
  20:         public string Port { get; set; }
  21:     }
  22: }

Again these are relatively simple classes – but, again they have room to expand.

WebSite does have some properties and methods that maybe of interest.  It allows you to start and stop the individual site – so you don’t have to bring down the entire IIS server, just to configure your site.  It also allows you to get a reference to it’s root virtual directory so you can change settings – such as the framework version the site should use.  Other properties of the root directory can be set via a dictionary of name value pairs.  This was for me the most convenient method, since I was using xml files to store my configuration settings:

 

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Management;
   6: using System.Text.RegularExpressions;
   7:  
   8: namespace FireAnt.IIS.Util
   9: {
  10:     public class WebVirtualDirectory : WmiObjectBase
  11:     {
  12:         internal WebVirtualDirectory ( ManagementScope scope, string siteName ) : base(scope)
  13:         {
  14:             this.Path = string.Format ( "IIsWebVirtualDirSetting='{0}/ROOT'", siteName );
  15:         }
  16:  
  17:         private string Path { get; set; }
  18:  
  19:         public void SetFrameworkVersion ( string version )
  20:         {
  21:             ManagementObject root = this.GetInstance ( this.Path );
  22:             foreach ( PropertyData property in root.Properties )
  23:             {
  24:                 if ( property.Name == "ScriptMaps" )
  25:                 {
  26:                     ManagementBaseObject[] scriptMaps = (ManagementBaseObject[])property.Value;
  27:                     foreach ( ManagementBaseObject scriptMap in scriptMaps )
  28:                     {
  29:                         string value = (string)scriptMap["ScriptProcessor"];
  30:                         if ( value.ToLower ().Contains ( "framework" ) )
  31:                         {
  32:                             if ( !value.Contains ( version ) )
  33:                             {
  34:                                 string currentVersion = Regex.Match ( value, @"(v\d+\.\d+\.\d+)" ).Value;
  35:                                 value = value.Replace ( currentVersion, version );
  36:                                 scriptMap.SetPropertyValue ( "ScriptProcessor", value );
  37:                             }
  38:                             else
  39:                             {
  40:                                 return;
  41:                             }
  42:                         }
  43:                     }
  44:  
  45:                     property.Value = scriptMaps;
  46:                     root.Put ();
  47:                     break;
  48:                 }
  49:             }
  50:         }
  51:  
  52:         public void ApplySettings ( Dictionary<string, object> properties )
  53:         {
  54:             ManagementObject root = this.GetInstance ( this.Path );
  55:             ApplySettings ( root, properties );
  56:         }
  57:     }
  58: }

Here is some code that demonstrates how you might use the above library (Air-Code Warning!):

 

   1: static class Program
   2: {
   3:     internal static string EnvironmentName { get; set; }
   4:     static int Main ( string[] args )
   5:     {
   6:         EnvironmentName = args[0];
   7:         InternetInformationServer iis = new InternetInformationServer ( Program.EnvironmentName );
   8:         WebSite webSite = iis.GetWebSite ( ServerComment );
   9:         ShutdownSite ( webSite );
  10:  
  11:         // DO SOME STUFF
  12:  
  13:         // create the website
  14:          if ( webSite == null )
  15:          {
  16:                 ApplicationPool pool = iis.GetApplicationPool ( poolName );
  17:                  if ( pool == null )
  18:                      iis.CreateAppliationPool ( poolName );
  19:         
  20:              webSite = iis.CreateWebSite ( ServerComment, webRootPath );
  21:                 
  22:              // apply any virtual root properties here
  23:              WebVirtualDirectory virtualDirectory = webSite.DirectorySettings;
  24:              virtualDirectory.SetFrameworkVersion ( "v2.0.50727" );         }
  25:         
  26:          // start up the website...
  27:          webSite.Start ();
  28:     }
  29:  
  30:     private static void ShutdownSite ( WebSite webSite )
  31:      {
  32:          if ( webSite != null )
  33:          {
  34:              webSite.Stop ();
  35:          }
  36:      }
  37: }

Anyway,  I apologize if the level of detail here is less then expected – but I had to throw this together fairly quickly.  I wanted to get it out there in the hopes that someone might find some of this useful.

5 Comments »

  1. Hi Tom,

    This is really cool – and just what I needed :-)

    But…. Its really hard work to copy your code from the site, removing all the line numbers and html/css etc. – would it be possible for you to provide a zip or similar of the code ?

    Kind regards
    Morten
    Copenhagen / Denmark

    Comment by Morten Steensgaard — August 4, 2009 @ 1:50 pm

  2. Tom – this is fantastic stuff. Even if it doesn’t help me solve my immediate problem, it’s an excellent insight into WMI IIS manipulation, as well as an example of how code *should* be written and structured.

    I’ll be referring back to this page many times in the future – many thanks indeed.

    Comment by rposbo — August 14, 2009 @ 12:56 am

  3. Tom-Very well done. thats one of the best code styles I have seen and also it gave me complete knowledge about using WMI.
    I am also writing a code for creating website. Can you please tell me from where we can get the values for servercomment, poolname and webrootpath. can u please give me an example

    Comment by spruce — March 19, 2010 @ 5:19 am

  4. Sorry – I have been really busy and haven’t been keeping up with the blog so much lately. I believe all of these are in the complete code. I will try and attach a zip of the actual code in the next couple of days.

    Comment by Tom Shelton — April 23, 2010 @ 10:30 am

  5. can u please share teh code

    Comment by Ravindra — November 22, 2013 @ 10:03 pm

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress