Arrrrgh! Wix!

This week’s time-eater, for me, was to try to create an installer package (MSI) with custom actions that install databases on SQL Servers. Typically, I had grown to like Microsoft Visual Studio Deployment Projects, because I could easily knock out some C# code and slam it into an installer and get it to do, basically, anything I want.

A few days ago, though, all I had at my disposal was Mister Pokey, the Little Computer That Couldn’t. Since Mister Pokey is less than gifted in the processor department, all he runs is the “Express” version of Visual Studio. It’s nice for sticking together little projects, writing a little puzzle game, and things like that, but it is minus the Deployment Project Wizard.

I remember some developers telling me that VS Deployment is for girls, and the manly-man programmer type uses WIX, you know, because it’s bl**dy difficult to understand, which makes them feel all better about themselves, or something. How hard could that be? Well, it’s not entirely obvious how to use it.

To make a long story short, you’ll need to know installers inside-and-out before you can attempt to compile a WIX installer. There is no UI (well there is Votive, which I haven’t tried out), and the documentation isn’t brilliant. But on the plus side it is a gimmie and allows for incredibly fine-grained control.

If you do want to deploy an existing Windows Installer class that you had created using the VS deployment project in WIX, this is a hard-won battle, but possible. Even though the author of WIX is dead against it and every Installer Jockey at Red Gate tells me that managed installers cause grief and famine and cataclysm beyond imagination. Just to irk them, here is the code. If you want to know how to get these .ibd files, what you basically have to do is reverse-engineer one of your existing VS deployment MSIs using Orca. Actually you could use WIX (dark -x <msiname>.msi, to be specific) but I couldn’t get that going. I used Orca to export the Binary table of my old installer and plopped the whole mess into a subfolder called “Binary”.

<?

xml version=”1.0″ encoding=”utf-8″?>
<Wix xmlns=”http://schemas.microsoft.com/wix/2003/01/wi”>
<Product UpgradeCode=”79C0A2EF-7A27-4227-9323-376F8D643C42″ Name=”Packager Test” Id=”4AD025EB-922B-44E1-84B6-B5CC05F19A00″ Version=”1.0.0″ Manufacturer=”Brian Donahue” Language=”1033″>
<Package Id=”C83C3011-4205-40AA-8E0B-4106A33E9391″ Manufacturer=”Brian Donahue” InstallerVersion=”200″ Platforms=”Intel” Languages=”1033″ Compressed=”yes” SummaryCodepage=”1252″ />
<Media Id=”1″ EmbedCab=”yes” Cabinet=”Test.cab” />
<Binary Id=”InstallUtil” src=”BinaryInstallUtil.ibd” />
<Binary Id=”MSVBDPCADLL” src=”BinaryMSVBDPCADLL.ibd” />
<Binary Id=”VSDNETCFG” src=”BinaryVSDNETCFG.ibd” />

<

Directory Id=”TARGETDIR” Name=”SourceDir”>
<Directory Id=”TestDir” Name=”Test”>
   <Component Id=”TestInstaller.dllComponent” Guid=”2B6D1187-9DBC-7A6F-B9ED-E6AD9DD6D248″>
<File Id=”PkgInstaller.dllFile” Name=”SQLPAC.dll” LongName=”SqlPackagerDeployment.dll” Vital=”yes” KeyPath=”yes” DiskId=”1″ src=”SQLPackagerDeployment.dll” />
<File Id=”BaselinePackage.exeFile” Name=”SQLPAB.exe” LongName=”SQLPackagerBaseline.exe” Vital=”yes” DiskId=”1″ src=”SQLPackagerBaseline.exe” />
<File Id=”UpgradePackage.exeFile” Name=”SQLPA1.exe” LongName=”SQLPackagerUpgrade1.exe” Vital=”yes” DiskId=”1″ src=”SQLPackagerUpgrade1.exe” />
</Component>
</Directory>
</Directory>

<

Feature Id=”DefaultFeature” Level=”1″ ConfigurableDirectory=”TARGETDIR”>
   <ComponentRef Id=”TestInstaller.dllComponent” />
</Feature>

<

CustomAction Id=”SetPrereqs” BinaryKey=”MSVBDPCADLL” DllEntry=”CheckFX” />
<CustomAction Id=”TestInstaller.dllUninstall.uninstall” BinaryKey=”InstallUtil” DllEntry=”ManagedInstall” Execute=”deferred” />
<CustomAction Id=”TestInstaller.dllUninstall.uninstall.SetProperty” Property=”TestInstaller.dllUninstall.uninstall” Value=”/installtype=notransaction /action=uninstall /LogFile= &quot;[#PkgInstaller.dllFile]&quot; &quot;[VSDFxConfigFile]&quot;” />
<CustomAction Id=”TestInstaller.dllInstall.install” BinaryKey=”InstallUtil” DllEntry=”ManagedInstall” Execute=”deferred” />
<CustomAction Id=”TestInstaller.dllInstall.install.SetProperty” Property=”TestInstaller.dllInstall.install” Value=”/installtype=notransaction /action=install /LogFile= /SERVER=localhost /DATABASE=SqlPackagerExample &quot;[#PkgInstaller.dllFile]&quot; &quot;[VSDFxConfigFile]&quot;” />
<InstallExecuteSequence>

<

Custom Action=”SetPrereqs” Before=”AppSearch” />
<Custom Action=”TestInstaller.dllUninstall.uninstall.SetProperty” After=”MsiUnpublishAssemblies”>$TestInstaller.dllComponent=2</Custom>
<Custom Action=”TestInstaller.dllUninstall.uninstall” After=”TestInstaller.dllUninstall.uninstall.SetProperty”>$TestInstaller.dllComponent=2</Custom>
<Custom Action=”TestInstaller.dllInstall.install.SetProperty” After=”SetPrereqs”>$TestInstaller.dllComponent&gt;2</Custom>
<Custom Action=”TestInstaller.dllInstall.install” After=”TestInstaller.dllInstall.install.SetProperty”>$TestInstaller.dllComponent&gt;2</Custom>
</InstallExecuteSequence>

</

Product>
</Wix>

Now we can compile the WIX script using the very intuitive tools that come with it. And by “intuitive” I mean “batch file”. The file above has been named “DBInstaller.wxs”.

@Echo please ensure candle, light and csc are on the path!
del /Q output*
rmdir output
mkdir output
..candle DBInstaller.wxs /out outputDBInstaller.wixobj
..light outputDBInstaller.wixobj /out outputDBInstaller.msi
msiexec -lv* outputlog.txt -i outputDBInstaller.msi

What this WIX package does is to run a very simple installer with no UI. On Install, the SQLPackagerDeployment.dll is used with the arguments /SERVER=localhost and /DATABASE=SqlPackagerExample. You have to be very careful about the order of the arguments… whatever you do, the last two arguments to the CustomAction value have to remain intact! $TestInstaller.dllComponent=2  makes the custom action run only when uninstalling, and >2 makes the custom action run only when installing.

   Of course, since managed installers will cause a rift in the space-time continuum, I have re-implemented the whole thing in VBScript. I’m saving that story for later!