Programmatically Start & Stop IIS Web Sites in C#

I was recently working on a web application that did business processing for a customer in a clustered environment. The customer wanted the web application to be included in the cluster, but it would be problematic if there were more than one instance of the application running due to shared network resources. The solution that I came up with was to build functionality into a Windows service that would stop and start the web site in IIS as the service is started and stopped. That Windows service can then be added to the cluster services, and everything works great!

This solution uses WMI to access IIS. I tested and verified that it works on Windows Server 2003 (IIS6), Windows Server 2008 (IIS7), and Windows 7 (IIS7.5).
IMPORTANT! For IIS7+, you must install the optional Windows component IIS 6 WMI Compatibility.

I used this CodeProject site as the basis for my solution.

I’ll present my entire solution at the bottom, but I wanted to highlight a few points individually. I developed and tested the solution in a new .NET Framework 3.5 Console Application. I had to add two references to my project: System.DirectoryServices and System.Management.

The class has properties for user, password, server, and website. The user and password can be used to specify authorized credentials, if necessary. I always included the domain in the user while testing (“MyServer\MyUser”), but that may not be necessary. User and password are both optional. Server and website are required. The default values that I’m using in my application are localhost and Default Web Site, respectively.

The helper function GetSiteIdFromWebSiteName is used to translate the website name (“Default Web Site”) to a numeric SiteID, which can then be used to get the ManagementObject that’s used to stop or start the web site.

You’ll notice that my Stop and Start functions both execute asynchronously. The reason for this is that my application hung on the mgmtScope.Connect once. I wasn’t able to figure out why, but I decided to run it on a separate thread so that I could specify a timeout.

Here is my complete solution:

public class WebSiteController
{
    public string Password { get; set; }
    public string Server { get; set; }
    public string User { get; set; }
    public string WebSite { get; set; }

    /// <summary>
    /// Returns the site ID from the specified WebSite name
    /// </summary>
    /// <returns>Site ID</returns>
    private string GetSiteIdFromWebSiteName()
    {
        var path = string.Format(@"IIS://{0}/W3SVC", Server);
        var root = new DirectoryEntry(path);

        return root.Children.Cast<DirectoryEntry>()
            .Where(x => x.SchemaClassName == "IIsWebServer"
                && x.Properties["ServerComment"].Value.ToString()
                    .Equals(WebSite, StringComparison.CurrentCultureIgnoreCase))
            .Select(x => x.Name)
            .FirstOrDefault();
    }

    /// <summary>
    /// Stops the specified WebSite
    /// </summary>
    public void Stop()
    {
        if (!ExecuteAsync("Stop").WaitOne(30000))
        {
            Console.WriteLine("Stop operation timed out");
        }
    }

    /// <summary>
    /// Starts the specified WebSite
    /// </summary>
    public void Start()
    {
        if (!ExecuteAsync("Start").WaitOne(30000))
        {
            Console.WriteLine("Start operation timed out");
        }
    }

    /// <summary>
    /// Executes the specified function asynchronously
    /// </summary>
    /// <param name="function">Function to execute</param>
    /// <returns>WaitHandle that will be set when execution completes</returns>
    private WaitHandle ExecuteAsync(string function)
    {
        var wh = new AutoResetEvent(false);
        ThreadPool.QueueUserWorkItem(x =>
        {
            Execute(function);
            wh.Set();
        });
        return wh;
    }

    /// <summary>
    /// Executes the specified function
    /// </summary>
    /// <param name="function">Function to execute</param>
    private void Execute(string function)
    {
        var options = new ConnectionOptions
        {
            Username = User,
            Password = Password,
            Authentication = AuthenticationLevel.PacketPrivacy
        };
        var mgmtPath = new ManagementPath
        {
            Server = Server,
            NamespacePath = "root/MicrosoftIISv2"
        };
        var mgmtScope = new ManagementScope(mgmtPath, options);
        mgmtScope.Connect();

        var siteId = GetSiteIdFromWebSiteName();
        if (!string.IsNullOrEmpty(siteId))
        {
            var selectQuery = new SelectQuery("SELECT * FROM IIsWebServer WHERE Name = 'W3SVC/" + siteId + "'");
            using (var managementObjectSearcher = new ManagementObjectSearcher(mgmtScope, selectQuery))
            {
                foreach (ManagementObject objMgmt in managementObjectSearcher.Get())
                {
                    objMgmt.InvokeMethod(function, new object[0]);
                }
            }
        }
    }
}

Author: Adam Prescott

I'm enthusiastic and passionate about creating intuitive, great-looking software. I strive to find the simplest solutions to complex problems, and I embrace agile principles and test-driven development.

Leave a comment