Wednesday, April 25, 2007

IIS Redirects by setting the HttpRedirect metabase property with WMI

In my last post I talked about how to use ISAPI_Rewrite to do redirects in IIS. I said "It looks like the only way of doing this is to use an ISAPI extension." How wrong I was! That'll teach you, dear reader, to believe everything you read in Code Rant. In fact I guy I once worked with (hello Chris) used to say, "Everything you say is wrong Mike":) What I've discovered since then is that you can do the same thing directly in IIS by changing a metabase property 'HttpRedirect' using WMI. This took a bit of digging since I'm not particularly familiar with the IIS metabase or WMI, but discovering the IIS Metabase Explorer made all the difference. It's part of the IIS 6.0 Resource Kit and you can use it to dig around the metabase and find out the name and type of the objects you need to access to get the property you're after. The main reference for the metabase is also quite comprehensive. I wanted to programatically set up redirects from C# so the first thing to dig into was the System.Management namespace which is the managed API for WMI. The documentation's not bad, but as soon as you start doing anything WMI related you're in the world of the sys admin , a world you don't want to go to where VBScript rules. You'll find plenty of examples for what you want to do, but not using System.Management. A bit of translation is required. Here's a little console app which sets up a couple of redirects on the default web site. The first redirects any requests for the 'google' directory to Google, so if you type the following into your browser (you'll have to change the server name) :
http://192.168.0.166/google/
You'll get redirected to the Google homepage. The other redirect redirects /bbc/ to the BBC News homepage.
using System;
using System.Management;

namespace WMITest
{
    public class Program
    {
        const string serverName = "192.168.0.166";
        const string userName = "Administrator"; 
        const string password = "mike";
        const string wmiPathToDefaultWebsite = "IIsWebVirtualDirSetting='W3SVC/1/ROOT'";
        const string redirectValue = 
            "*; /google/; http://www.google.com/" +
            "; /bbc/; http://news.bbc.co.uk/" +
            ", EXACT_DESTINATION";

        public static void Main()
        {
            ConnectionOptions options = new ConnectionOptions();
            options.Username = userName;
            options.Password = password;
            options.Authentication = AuthenticationLevel.PacketPrivacy;

            ManagementPath path = new ManagementPath();
            path.Server = serverName;
            path.NamespacePath = "root/MicrosoftIISv2";

            ManagementScope scope = new ManagementScope(path, options);

            using(ManagementObject obj = new ManagementObject(
                      scope, 
                      new ManagementPath(wmiPathToDefaultWebsite), null))
            {
                Console.WriteLine("{0}", obj.Path.RelativePath);
                if(obj.Properties.Count == 0)
                    Output.WriteLine("No properties found");

                obj.SetPropertyValue("HttpRedirect", redirectValue);
                obj.Put();

                string httpRedirectValue = obj.GetPropertyValue("HttpRedirect").ToString();
                Console.WriteLine("HttpRedirect='{0}'", httpRedirectValue);
            }
            Console.ReadLine();
        }
    }
}
The tricky stuff is knowing what value to put into the wmiPathToDefaultWebsite variable. The syntax seems to be ='', but it took me a while to find the correct type to get to the HttpRedirect property. The Metabase Explorer displayed 'IIsWebVirtualDir' as the type for the 'W3SVC/1/ROOT' node, but that property is actually only returned when you set the type to 'IIsWebVirtualDirSetting'. The actual path to the default web site (W3SVC/1/ROOT) is pretty easy to work out from looking at the Metabase Explorer. The WMI provider for the IIS metabase is 'root/MicrosoftIISv2' and also note that you have to call Put() on the ManagementObject after you set a property value for it to be persisted. You'll probabaly want to experiment with a virtual PC IIS, because it's easy to totally muck up your web site by playing with metabase values. The amount of information that's exposed by WMI is enormous. If you find yourself needing to do anything sys-admin-like programmatically it's often the only way to go. A lot of the main Microsoft applications now expose WMI APIs, SQL Server being one of them, so it can often be a choice for that kind of thing too. It has to remain a choice of last resort though, because it's a real pain trying to work out how to manipulate the given WMI object model. It's loosely typed, runtime discoverable stuff, so any managed API always has to be preferable to WMI.

No comments: