Those of you who’ve been following my blog lately will know that I’ve been writing a lot on the subject of WiX installers. Basically I just think they’re awesome! The previous set of posts focused on the basics and then some of the specific stuff you can do with website installers. This time I’m going to walk through the creation of a service installer, and introduce you to a simple technique that I’ve found loads of uses for.

I’m assuming you already know the basics, if you’ve never touched WiX and don’t know about running the Heat command from your project file take a look at the first two posts on the subject.

What Do We Want From This Installer?

A good installer will take all of the complications out of installing a product, and this one should be no different. Our installer should:

  • Install to any given directory;
  • Start automatically;
  • Pull the correct configuration information in with it.

Not a long list, so this shouldn't take long!

Creating The Service

Let’s start by creating a new windows service, mine’s called WixServiceInstallerExample but you could call yours something more boring if you like. In the Service1 code-behind remove the two overridden methods; we don’t actually need our service to do anything for this example!

Creating The Installer

OK, now that the service is created, let’s get on with the installer. Add a new WiX Setup project to the solution. You need to add a reference to your service project first, then references to the WixNetFxExtension and WixUIExtension dll files from the WiX install folder.

OK, now we’ll make a couple of changes to the Product.wxs file. For now we’re just ripping out a bit of the stuff we don’t need and telling WiX to use the WixUI_InstallDir standard user interface. Replace the Directory and Feature nodes with the following:

<Directory Id="TARGETDIR" Name="SourceDir">
  <Directory Id="INSTALLLOCATION" Name="WixServiceInstaller">
  </Directory>
</Directory>

<Feature Id="ProductFeature" Title="WixServiceInstaller" Level="1">
  <ComponentGroupRef Id="WixServiceInstallerExample_Project" />
</Feature>

<Property Id="WIXUI_INSTALLDIR">INSTALLLOCATION</Property>
<UIRef Id="WixUI_InstallDir" />

We’ll be back in here later to do some cool service-oriented stuff, but for now you can close the file.

Next we’ll do something fairly similar to what we did with heat in a previous post, with a couple of funky changes. Unload the installer project and edit the project file.  Locate the ProjectReference node which points to your referenced project and add the following as its last child:

<ContentProject>True</ContentProject>

We’ll use this to ensure we don’t end up running heat on every reference. Now add a new BeforeBuild target right before your closing </Project> tag:

<Target Name="BeforeBuild">
  <MSBuild Projects="%(ProjectReference.FullPath)" Targets="Build"
    Properties="Configuration=$(Configuration);Platform=AnyCPU"
    Condition="'%(ProjectReference.ContentProject)'=='True'" />
  <PropertyGroup>
    <DefineConstants>BasePath=%(ProjectReference.RootDir)%(ProjectReference.Directory)bin\$(Configuration);</DefineConstants>
    <LinkerBaseInputPaths>%(ProjectReference.RootDir)%(ProjectReference.Directory)bin\$(Configuration)\</LinkerBaseInputPaths>
  </PropertyGroup>
  <HeatDirectory OutputFile="%(ProjectReference.Filename)-temp.xml"
    Directory="%(ProjectReference.RootDir)%(ProjectReference.Directory)bin\$(Configuration)\"
    DirectoryRefId="INSTALLLOCATION"
    ComponentGroupName="%(ProjectReference.Filename)_Project"
    SuppressCom="true"
    SuppressFragments="true"
    SuppressRegistry="true"
    SuppressRootDirectory="true"
    AutoGenerateGuids="false"
    GenerateGuidsNow="true"
    ToolPath="$(WixToolPath)"
    PreprocessorVariable="var.BasePath"
    Condition="'%(ProjectReference.ContentProject)'=='True'" />
  <XslTransformation
    XmlInputPaths="%(ProjectReference.Filename)-temp.xml"
    XslInputPath="XslTransform.xslt"
    OutputPaths="%(ProjectReference.Filename).wxs"
    Condition="'%(ProjectReference.ContentProject)'=='True'" />
  </Target>

So what’s going on here? Firstly we build the required project to make sure it’s available for heat. Then we run heat on the built content just like we would with a published web project. Notice though that we’re outputting to a temp.xml file. This is because the xml file definitions created by heat aren’t quite what we need in this case. Instead we output to the temporary file and then in the final step we perform an xsl transformation, saving the final file as WixServiceInstallerExample.wxs to be used by the installer.

You’re probably wondering what we need to do this little transformation for. The problem we have is that we need to do some specific service oriented stuff with the actual WixServiceInstallerExample.exe file in our Product file. If we define one file in two places WiX will have a fit, so to keep the peace we’ll remove it from the auto generated file. We’re done with the project file so close it and reload the project in visual studio. Add a new XslTransform.xslt file to your project (You can do this by adding a text file but just giving it an xslt extension).

 

Add the following to the file:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0"
  xmlns:w="http://schemas.microsoft.com/wix/2006/wi">

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()" />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="w:Component[w:File/@Source='$(var.BasePath)\WixServiceInstallerExample.exe']">
    <w:Component>
      <xsl:attribute name="Id">
        <xsl:value-of select="@Id"/>
      </xsl:attribute>
      <xsl:attribute name="Guid">
        <xsl:value-of select="@Guid"/>
      </xsl:attribute>
      <xsl:attribute name="KeyPath">yes</xsl:attribute>
    </w:Component>
  </xsl:template>

</xsl:stylesheet>

It’s not really too complicated. We duplicate everything in the source file except for the exe file, which is removed. Awesome, we’re nearly there now! Build your project and you should get an error about an unresolved reference to WixServiceInstallerExample_Project. This is because it has been created but not yet added to the project. Click to view hidden files and include the file WixServiceInstallerExample.wxs in your project.

If you take a look in this file you’ll notice that the first Component doesn’t have a File in it – this is where we stripped out the main exe.

You should be able to build your project now, but it’s still not installing any service specific stuff. Open up your Product.wxs file and add the following inside the INSTALLLOCATION Directory node:

<Component Id="ServiceComponent" Guid="YOUR_GUID_HERE">
  <CreateFolder />
  <File Id="MainExecutable" KeyPath="yes"
    Source="$(var.BasePath)\WixServiceInstallerExample.exe" />
  <ServiceInstall Id="ServiceInstaller"
    Type="ownProcess"
    Vital="yes"
    Name="WixServiceInstallerExample"
    DisplayName="Wix Service Installer Example"
    Description="An example service."
    Start="auto"
    Account="LocalSystem"
    ErrorControl="ignore"
    Interactive="no" />
  <ServiceControl Id="StartService"
    Start="install"
    Stop="both"
    Remove="uninstall"
    Name="WixServiceInstallerExample"
    Wait="yes" />
</Component>

It’s that easy. We specify the file we stripped out earlier and then first install and then start the service. All that’s left is to add a reference to the component into your Feature section:

<ComponentRef Id="ServiceComponent" />

Testing

We’re done! Go ahead and build your project and then run the resulting msi file. If you watch the statuses as they flash past you’ll see that the files are copied and then the service is installed and started. Take a look at your services list and it should be visible and started!

Why This Way?

The technique I’ve shown you here of using xslt files for modifying heat output has many more uses than the one outlined above, such as applying permissions on certain files. I’ll do a quick post on how to do that sometime soon.

Adding App.config Transformations

One final little thing; if you have read my post on App.config transformations you’ll know that my favoured way of applying them is during an WiX installer build. This is actually really simple:

  1. Firstly set up the transformations in your service project;
  2. Next unload your installer project and edit the project file;
  3. In the BeforeBuild section, change the MsBuild node so that its Targets attribute is set to TransformAppConfig (or whatever you called your target in the service project);
  4. There is no step 4!

Finished

I hope this post has been useful to somebody. I’ll be following up soon with a couple of posts on using xslt to add file permissions, and how to put pre-requisites on an installer (try running your installer somewhere you don’t have local admin permissions, or somewhere with an old version of the .net framework!) so you should subscribe to my rss feed to be notified when I post them.

Source Code

Full source code is available here: WixServiceInstallerExample.zip (184.90 kb)