WiX[1] is a toolset that allows you to build setups for Windows Installer. Basically you write one or more XML files and then compile them using the compiler and linker from WiX. One of the reasons for using WiX is that it allows you to customize everything about the install. E.g.: when installing a plugin or update you need to find the folder on the target system that contains the app that needs to be extended but the unfortunately using the standard Setup and Deployment project in Visual Studio will only allow you to execute Custom Action (like searching for a registry key) AFTER copying the executable or dll to the target machine. Not with WiX.
Let me show you how I created a setup that checks for the existence of .NET 2.0 and uses a Custom Action to determine the location of Windows Live Writer[2] as specified by the SDK by calling AssocQueryString[3] for the .wpost extension to install a plugin.
Calling AssocQueryString can only be done from within running code so you’ll have to create a dll that exports a function that does precisely that. Here’s my code:
// WLWPluginSetupCustomAction.cpp : Defines the entry point for the DLL application.
//#include “stdafx.h”
#ifdef _MANAGED
#pragma managed(push, off)
#endifBOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
return TRUE;
}#ifdef _MANAGED
#pragma managed(pop)
#endifHRESULT GetPluginFolder(LPWSTR folder, DWORD size)
{
ASSOCF flags = 0;
ASSOCSTR str = ASSOCSTR_EXECUTABLE;
WCHAR szOut[MAX_PATH];
DWORD dwOut = ARRAYSIZE(szOut); HRESULT r;
if(SUCCEEDED(r = AssocQueryString(flags, str, TEXT(“.wpost”), NULL, szOut, &dwOut)))
{
// find last backslash
WCHAR *slash = _tcsrchr(szOut, TEXT(‘\’)); // remove windowslivewriter.exe from the path
slash[1] = TEXT(”); // concatenate the name of the Plugins folder
if(SUCCEEDED(r = StringCbCat(szOut, MAX_PATH, TEXT(“Plugins”))))
{
size_t l;
if(SUCCEEDED(r = StringCbLength(szOut, MAX_PATH, &l)))
{
r = StringCbCopy(folder, l + sizeof(TCHAR), szOut);
}
}
}
return r;
}UINT __stdcall GetWLWPluginFolder( MSIHANDLE hModule )
{
WCHAR folder[MAX_PATH];
HRESULT r;
if(SUCCEEDED(r = GetPluginFolder(folder, MAX_PATH)))
{
if(SUCCEEDED(r = MsiSetProperty(hModule, TEXT(“WLWPluginsFolder”), folder)))
{
return ERROR_SUCCESS;
}
}
return ERROR_INSTALL_FAILURE;
}
To inject this dll into the MSI setup file there are a couple of special tags you can use in a WiX file:
<?xml version=”1.0″ encoding=”UTF-8″?>
<Wix http://schemas.microsoft.com/wix/2006/wi"”>http://schemas.microsoft.com/wix/2006/wi”> <Product Manufacturer=”Airknow” Version=”2.0″ Name=”Community Server Gallery Plugin for Windows Live
Writer”
UpgradeCode=”YOURGUID-B5B9-425d-BDDE-708D651F67B2″
Id=”YOURGUID-7FE5-4406-AAE1-C5E407751335″ Language=”1033″> <Package Comments=”Windows Live Writer and .NET 2.0 are required.” Compressed=”yes”
InstallerVersion=”200″ ShortNames=”no”
Description=”This plugin enables Windows Live Writer to access the Community Server image
galleries. Images can be
uploaded and inserted into posts written in Windows Live Writer.” Manufacturer=”Airknow”/> <Binary Id=’GETWLWFOLDER’
SourceFile=’..componentsWLWPluginSetupCustomActionWLWPluginSetupCustomAction.dll’/>
<CustomAction Id=’FindWLW’ BinaryKey=’GETWLWFOLDER’ DllEntry=’GetWLWPluginFolder’
Execute=’immediate’ />
<Media Id=’1′ Cabinet=’product.cab’ EmbedCab=’yes’ /> <Condition Message=’To use this plugin .NET 2.0 must be installed first.’ >
<![CDATA[MsiNetAssemblySupport >= “2.0.50727”]]>
</Condition>
<Condition Message=’To use this plugin Windows Live Writer must be installed first.’ >
<![CDATA[WLWPluginsFolder <> “”]]>
</Condition> <Directory Id=”TARGETDIR” Name=”SourceDir”>
<Directory Id=”WLWPluginsFolder”>
<Component DiskId=”1″ Id=”CSGalleryPlugin” Guid=”YOURGUID-A977-4290-8ADE-81E45F0ABA2F”>
<File Id=”PluginFile” Name=”CSGalleryPlugin.dll”
Source=”..componentsCSGalleryPluginCSGalleryPlugin.dll”/>
</Component>
</Directory>
</Directory> <Feature Id=’MyFeature’ Title=’The plugin’ Level=’1′>
<ComponentRef Id=’CSGalleryPlugin’ />
</Feature> <InstallUISequence>
<Custom Action=’FindWLW’ Before=’LaunchConditions’ />
</InstallUISequence>
<InstallExecuteSequence>
<Custom Action=’FindWLW’ Before=’LaunchConditions’ />
</InstallExecuteSequence>
</Product>
</Wix>
I’m still not sure why I need to add both InstallUISequence AND InstallExecuteSequence; if one of them is absent it doesn’t install but if both are present the custom action runs twice…
BTW: I used WiX 3.0, I couldn’t get my custom action to fire with WiX 2
If you want the full sources go to CodePlex[4]
[1] WiX
[2] Windows Live Writer
[3] AssocQueryString
[4] http://www.codeplex.com/Wiki/View.aspx?ProjectName…
5 comments
Erno,
I can tell you why you need both InstallUISequence AND InstallExecuteSequence.
InstallUISequence is used when the MSI is run with the user interface. This is what most people do. Every MSI must also support the so called quiet mode. When a MSI is run in quiet mode the UI sequence is skipped, all the information normally gathered through the UI must be supplied by other means e.g. running msiexec with the /qn option and supplying all the properties on the commandline.
Guido van Loon
Guido, I knew this but I do not understand why the custom action is triggered twice in UI mode…
ernow
Ok, I see where you are going now… I must say I don’t know. But if you ever gonna find out I want to hear about it.
Guido van Loon
I’m not sure how Wix implements it but MSI Custom Actions have an Execution Scheduling property to specify whether the action should run every time it is encountered, or only the first time ir is ecountered.
This tip was written in my Install Shield Training book I’m going over again to refresh my Install Shield knowledge… I’ve also looked it up for you:
Execution Scheduling Options
Guido van Loon
I found it, thanks Guido! It is is the execute attribute of the CustomAction element. It should/could be set to firstSequence or oncePerProcess. I’ll try it! Thanks again!
ernow