Whilst trying to host multiple WCF Services in IIS, each within it’s own web site, I discovered an issue with secure host headers and IIS6. The requirements were to securely install each WCF service inside it’s own web site, with its own application and application pool instance. In order to setup multiple sites I needed either: multiple IP addresses, use different ports on the one IP address or use ‘host headers’. The chosen solution was ‘host headers’ and I set these up in IIS using IIS Manager (inetmgr). This is covered here:
However the UI only supports setting up unsecure bindings and not secure ones. After checking on TechNet I found this:
It turns out that whilst IIS6 does support the use of host headers this feature was not added in time for the IIS Admin UI to reflect this. The article informs us to use the handy IIS admin script “adsutil.vbs” which updates the IIS Metabase XML file, as detailed here:
The problem with this approach though is that the script rather annoyingly requires the Site Identifier for the web site you wish to update via the script. This is easy to obtain interactively by checking out IIS Manager and clicking on ‘web sites’, but I needed to set up my site via an MSI (via Wix). The installation script will not know what the identifier of the site is as it could vary on each install. Therefore I need to be able to resolve a web site name to an identifier before calling adsutil.vbs. After some Googling I found this excellent post by David Wang:
Here he explains how to enumerate through IIS entities to find the one you want. If you check out the comments there is a post by ‘Dave’ where he has included a modified script. This script iterates over the web sites and finds the one matching the right name, then calls the “adsutil.vbs” script to update the metabase using the correct site identifier. I copied this script and trimmed it down for my own purposes and now had a viable script for my installer.
Using a VB Script within an MSI is not recognised best practice, a more robust solution would be to code a Custom Action in native code (there are hidden dangers of Custom Actions written in Managed Code), however it is a viable option and in this instance it fitted my requirements perfectly (for many reasons outside the scope of this article).
Now I’m a fan of Wix and am always impressed with how much it can do out the box without customisation. In this case however I discovered that it is not that easy to just run a command or script during install. Don’t get me wrong it is possible and there are a huge number of options as to when and how to run custom actions, but I was expecting it to be easier than it turned out to be. There are many decent blog posts on the subject of Custom Actions in Wix and I’m not going to go into much detail here, however in the end my solution looked like this:
<!--Set up IIS Bindings--> <installexecutesequence> <custom action='CAIISSecureBindingsServiceX' before='InstallFinalize'> NOT Installed </custom> <custom action='CAIISSecureBindingsServiceY' before='InstallFinalize'> NOT Installed </custom> </installexecutesequence>
This slots our Custom Actions into the running order of an MSI installation sequence of events. It asks for the actions be run just before the installation has completed. The ‘Not Installed’ argument ensures that the actions only fire on installation and not an uninstall
We then define the Property for the path to the exe we want to run, in this case the Cscript.exe application (to run our VBS Script). After that we need to define each custom action. The ExeCommand tells it what to pass to the Cscript executable as parameters. The Execute=’deferred’ option is important as it means that the scripts will not run on the first pass through of the MSI installation. The MSI installation process involves running through all steps but not committing them, if that runs ok it then does the steps for real. If we run the script before the web sites have been committed to IIS the script will fail. Setting it to ‘deferred’ means it will be left out of the initial run through and committed in the actual “doing ” stage. For more information on the MSI sequence of events check this out (http://www.advancedinstaller.com/user-guide/standard-actions.html). I found that impersonation needs to be set to Yes for this to work correctly.
<!-- Define all the custom actions and the properties required --> <Property Id='ScriptEngine'>C:\Windows\System32\CScript.exe</Property> <CustomAction Id='CAIISSecureBindingsServiceX' Property='ScriptEngine' ExeCommand='[INSTALLLOCATION]UpdateIISBindings.vbs ServiceXv1Site X.v1.default' Execute='deferred' Impersonate='yes' Return='ignore'/> <CustomAction Id='CAIISSecureBindingsServiceY' Property='ScriptEngine' ExeCommand='[INSTALLLOCATION]UpdateIISBindings.vbs ServiceYv1Site Y.v1.default' Execute='deferred' Impersonate='yes' Return='check'/>
I have set up multiple Custom Actions although only one is really needed. In my implementation I have made the VBS file merely a helper script that uses the information passed in as parameters. I then have multiple custom actions that each call the script separately passing in different parameters (web site name and host header value). It would be neater to have the VBS file include all the logic for looping all sites and setting up the bindings for each one and then only one Custom Action would be needed to run that script. The reason I have not done that here is that I didn’t want the IIS implementation details leaking out of the Wix project. With the multiple actions approach it means that the website names and host header names are not stored in the script file and are held only in this WIX project.