Hello, and welcome back to my series on deployment with WiX. Its been a couple of weeks since the last instalment which has been for a couple of reasons: I took my lovely girlfriend on a trip to Amsterdam (I fully recommend the zoo), and we've been getting ready for the version one release of the application we've been developing since September. This is the largest development we've done with our current team and everyone is understandably on edge - we have to coordinate the installation of 10 web services, 2 web applications, a windows service and 5 SQL databases across a total of 6 servers. The WiX installers have played a large part in keeping me calm though - we've reached the point where they aren't just a nicety but a major time and complication saver - hopefully the live install will take around an hour in total (I'll keep you posted).

Anyway, enough rambling, this post will be dealing with one of the cooler features of WiX; the ability to create your own custom dialog screens to perform any functionality you like. It leans heavily on the work we did in the previous post on IIS Integration so you should really go work through that one first, alternatively you could just start at the beginning.

At the end of the last post we had a working installer which set up all our IIS settings for us, what we'd like to do now though is allow the user to define couple of those settings; namely the virtual directory name, website and application pool. To do this we'll create a new dialog called IisSettingsDlg.

In your installer project, create a new file called IisSettingsDlg.wxs and put the following inside the Fragment node:

<EnsureTable Id="ComboBox"/>
<Property Id="VIRTUALDIRECTORYVALUE" Value="WixInstallerExampleWeb"/>
<UI>
  <Dialog Id="IisSettings" Title="WiX Installer Example" Width="370" Height="270">
    <Control Id="Next" Type="PushButton" Default="yes" Text="Next" X="236" Y="243" Width="56" Height="17">
      <Condition Action="disable">WEBSITEVALUE = "" OR APPPOOLVALUE = "" OR VIRTUALDIRECTORYVALUE = ""</Condition>
      <Condition Action="enable"><![CDATA[WEBSITEVALUE <> "" AND APPPOOLVALUE <> "" AND VIRTUALDIRECTORYVALUE <> ""]]></Condition>
    </Control>
        
    <Control Id="Back" Type="PushButton" Text="Back" X="180" Y="243" Width="56" Height="17" />
    <Control Id="Cancel" Type="PushButton" Cancel="yes" Text="Cancel" X="304" Y="243" Width="56" Height="17">
      <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
    </Control>

    <Control Id="Title" Type="Text" Transparent="yes" NoPrefix="yes" Text="IIS Settings" X="15" Y="6" Width="219" Height="28" />

    <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
    <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />

    <Control Type="Text" Id="WebSiteLabel" Text="Web Site:" Width="290" Height="10" X="20" Y="105" />
    <Control Id="WebSiteCombo" Type="ComboBox" Property="WEBSITEVALUE" Sorted="yes" ComboList="yes" X="20" Y="117" Width="250" Height="16" />

    <Control Type="Text" Id="AppPoolLabel" Text="Application Pool:" Width="290" Height="10" X="20" Y="140" />
    <Control Id="AppPoolCombo" Type="ComboBox" Property="APPPOOLVALUE" Sorted="yes" ComboList="yes" X="20" Y="152" Width="250" Height="16" />

    <Control Type="Text" Id="VirtualDirectoryLabel" Text="Virtual Directory:" Width="290" Height="10" X="20" Y="175" />
    <Control Type="Edit" Id="VirtualDirectoryTextbox" Property="VIRTUALDIRECTORYVALUE" Width="250" Height="15" X="20" Y="187" />
  </Dialog>
</UI>

There is your custom dialog! So we'll work our way through this. Firstly we have an EnsureTable node, which is making sure we have data for our combo boxes, more on this later. Next up is a property called VIRTUALDIRECTORYVALUE; this will hold the user's choice of virtual directory name and by default is set to WixInstallerExampleWeb.

Gotcha: When using properties they must be defined in all UPPER case. Otherwise you won't get errors, just empty values which can be a pain to debug.

While we're on the subject of properties, we'll be dealing with 3 properties here: VIRTUALDIRECTORYVALUE, WEBSITEVALUE and APPPOOLVALUE. I'll let you take a wild stab of what is held in each!

Up next is the UI itself. We've called our dialog IisSetting to match the filename and inside we have a series of Control nodes which will be pretty easy to figure out if you've ever used html (you have used html right?). The only thing to watch out for is that all these controls are absolutely positioned; their order in the code is not important at all. So what have we got?

  • A next button. This is either enabled or disabled depending on the values of our 3 properties;
  • A back button;
  • A cancel button which will spawn a cancellation dialog when pushed;
  • A title and a couple of banner lines;
  • A web site combo box which stores its selected value in WEBSITEVALUE;
  • An app pool combo box which stores its value in APPPOOLVALUE;
  • A text box to modify the default VIRTUALDIRECTORYVALUE if required.

Not exactly the most taxing exercise ever. The nice thing is that this dialog can now be used for any application installed on IIS, be it a web app or web service.

Next let's include this dialog in the install sequence, if you've been following the sequence this should be easy. In your UserInterface file between the LicenceAgreementDlg and InstallDirDlg sections add this:

<Publish Dialog="IisSettings" Control="Back" Event="NewDialog" Value="LicenseAgreementDlg" Order="1">1</Publish>
<Publish Dialog="IisSettings" Control="Next" Event="NewDialog" Value="InstallDirDlg" Order="1">1</Publish>

This wires up the back and next buttons on the IIS dialog to navigate to the correct screens. You will also need to change the Value attribute of the Next/Back buttons on the LicenceAgreementDlg and InstallDirDlg to point to the new IisSettings dialog. Following OK?

Alrighty, so we have a new dialog and have included it in the dialog flow, but how do we populate the combo boxes with the available sites and app pools? Welcome back screen left, your Custom Action assembly! This already has code for querying sites and app pools so we just need to add a little code to let it communicate with the installer. Open the CustomAction.cs file and add this custom action method:

[CustomAction]
public static ActionResult GetWebSites(Session session)
{
  try
  {
    if (session == null) { throw new ArgumentNullException("session"); }

    View comboBoxView = session.Database.OpenView("select * from ComboBox");

    int order = 1;

    foreach (IisWebSite site in IisManager.GetIisWebSites())
    {
      Record newComboRecord = new Record("WEBSITEVALUE", order++, site.ID, site.Name);
      comboBoxView.Modify(ViewModifyMode.InsertTemporary, newComboRecord);
    }

    return ActionResult.Success;
  }
  catch (Exception ex)
  {
    if (session != null)
      session.Log("Custom Action Exception: " + ex);

    return ActionResult.Failure;
  }
}

What we're doing here is plugging into our GetIisWebSites method and using this to push web site data to the combo box associated with the WEBSITEVALUE property. Easy! We do almost exactly the same with the app pools:

[CustomAction]
public static ActionResult GetAppPools(Session session)
{
  try
  {
    if (session == null) { throw new ArgumentNullException("session"); }

    View comboBoxView = session.Database.OpenView("select * from ComboBox");

    int order = 1;

    foreach (string appPool in IisManager.GetIisAppPools())
    {
      Record newComboRecord = new Record("APPPOOLVALUE", order++, appPool);
      comboBoxView.Modify(ViewModifyMode.InsertTemporary, newComboRecord);
    }

    return ActionResult.Success;
  }
  catch (Exception e)
  {
    if (session != null)
      session.Log("Custom Action Exception " + e);
  }

  return ActionResult.Failure;
}

Again, notice this time we're targeting the APPPOOLVALUE property.

Now to wire the two together. Firstly you'll need the custom action in your installer so go ahead and build it. If you look in the bin folder you should see a file called IisManager.CA.dll; this needs to be added into the root of your installer project (as a file NOT a reference!).

Finally, in your Product.wxs file you need a couple of new sections. After the Media node add:

<Binary Id="IisManager" SourceFile="IisManager.CA.dll" />

This lets us reference the actions in the DLL. Finally, put this after the MapVirtualDirectory CustomAction node:

<CustomAction Id="GetIISWebSites" BinaryKey="IisManager" DllEntry="GetWebSites" Execute="immediate" Return="check" />
<CustomAction Id="GetIISAppPools" BinaryKey="IisManager" DllEntry="GetAppPools" Execute="immediate" Return="check" />

<InstallUISequence>
  <Custom Action="GetIISWebSites" After="CostFinalize" Overridable="yes">NOT Installed</Custom>
  <Custom Action="GetIISAppPools" After="CostFinalize" Overridable="yes">NOT Installed</Custom>
</InstallUISequence>

The first two lines get a reference to the custom action methods, and the InstallUISequence section causes them to be run when the installer is launched.

OK, you can build and run your installer now. You should see your new custom dialog and the combos should be filled with your sites and app pools. Awesome! Only one problem. If you finish the installation you'll notice that your choices have been completely ignored! We've got to map the values back to the entries in the Product.wxs file.

If you remember from earlier, the way to slot a property value in somewhere is using square brackets. Here is a list of the things in Product.wxs you need to change (working from the top):

  • iis:WebSite node: change the SideId attribute to "[WEBSITEVALUE]";
  • iis:WebAppPool node: change the Name attribute to "[APPPOOLVALUE]";
  • iis:WebVirtualDir node: change the Alias attribute to "[VIRTUALDIRECTORYVALUE]";
  • iis:WebApplication node: change the Name attribute to "[VIRTUALDIRECTORYVALUE]";
  • The MapVirtualDirectory CustomAction node: change the ExeCommand to '[ASPNETREGIIS] -norestart -s "W3SVC/[WEBSITEVALUE]/ROOT/[VIRTUALDIRECTORYVALUE]"'.

Phew! This drops the values we've set on the custom dialog into our install process. You're all done now so go have a play! This is the end of the current sequence of posts, but I'll be writing soon about some of the topics mentioned earlier such as file permissions and service installation, so you should subscribe to my feed to be notified when they are posted.

Source Code

The complete source code for this example is available here: WixInstallerExample-05.zip (2.16 mb)