Bad User Experience The biggest reason you should not use Wix to create your website is that you are not a professional web designer. There is a detailed process creating a website including building out user flows, CTAs, prototyping, etc.
With Wix websites, you are not allowed to have unlimited bandwidth or storage on any plan. This even applies to the business and commercial plans that are sold at premium rates. 3. Drag and drop designs can limit your creativity.
Keep variables in a separate wxi
include file. Enables re-use, variables are faster to find and (if needed) allows for easier manipulation by an external tool.
Define Platform variables for x86 and x64 builds
<!-- Product name as you want it to appear in Add/Remove Programs-->
<?if $(var.Platform) = x64 ?>
<?define ProductName = "Product Name (64 bit)" ?>
<?define Win64 = "yes" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else ?>
<?define ProductName = "Product Name" ?>
<?define Win64 = "no" ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif ?>
Store the installation location in the registry, enabling upgrades to find the correct location. For example, if a user sets custom install directory.
<Property Id="INSTALLLOCATION">
<RegistrySearch Id="RegistrySearch" Type="raw" Root="HKLM" Win64="$(var.Win64)"
Key="Software\Company\Product" Name="InstallLocation" />
</Property>
Note: WiX guru Rob Mensching has posted an excellent blog entry which goes into more detail and fixes an edge case when properties are set from the command line.
Examples using 1. 2. and 3.
<?include $(sys.CURRENTDIR)\Config.wxi?>
<Product ... >
<Package InstallerVersion="200" InstallPrivileges="elevated"
InstallScope="perMachine" Platform="$(var.Platform)"
Compressed="yes" Description="$(var.ProductName)" />
and
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="$(var.PlatformProgramFilesFolder)">
<Directory Id="INSTALLLOCATION" Name="$(var.InstallName)">
The simplest approach is always do major upgrades, since it allows both new installs and upgrades in the single MSI. UpgradeCode is fixed to a unique Guid and will never change, unless we don't want to upgrade existing product.
Note: In WiX 3.5 there is a new MajorUpgrade element which makes life even easier!
Creating an icon in Add/Remove Programs
<Icon Id="Company.ico" SourceFile="..\Tools\Company\Images\Company.ico" />
<Property Id="ARPPRODUCTICON" Value="Company.ico" />
<Property Id="ARPHELPLINK" Value="http://www.example.com/" />
On release builds we version our installers, copying the msi file to a deployment directory. An example of this using a wixproj target called from AfterBuild target:
<Target Name="CopyToDeploy" Condition="'$(Configuration)' == 'Release'">
<!-- Note we append AssemblyFileVersion, changing MSI file name only works with Major Upgrades -->
<Copy SourceFiles="$(OutputPath)$(OutputName).msi"
DestinationFiles="..\Deploy\Setup\$(OutputName) $(AssemblyFileVersion)_$(Platform).msi" />
</Target>
Use heat to harvest files with wildcard (*) Guid. Useful if you want to reuse WXS files across multiple projects (see my answer on multiple versions of the same product). For example, this batch file automatically harvests RoboHelp output.
@echo off
robocopy ..\WebHelp "%TEMP%\WebHelpTemp\WebHelp" /E /NP /PURGE /XD .svn
"%WIX%bin\heat" dir "%TEMP%\WebHelp" -nologo -sfrag -suid -ag -srd -dir WebHelp -out WebHelp.wxs -cg WebHelpComponent -dr INSTALLLOCATION -var var.WebDeploySourceDir
There's a bit going on, robocopy
is stripping out Subversion working copy metadata before harvesting; the -dr
root directory reference is set to our installation location rather than default TARGETDIR; -var
is used to create a variable to specify the source directory (web deployment output).
Easy way to include the product version in the welcome dialog title by using Strings.wxl for localization. (Credit: saschabeaumont. Added as this great tip is hidden in a comment)
<WixLocalization Culture="en-US" xmlns="http://schemas.microsoft.com/wix/2006/localization">
<String Id="WelcomeDlgTitle">{\WixUI_Font_Bigger}Welcome to the [ProductName] [ProductVersion] Setup Wizard</String>
</WixLocalization>
Save yourself some pain and follow Wim Coehen's advice of one component per file. This also allows you to leave out (or wild-card *
) the component GUID.
Rob Mensching has a neat way to quickly track down problems in MSI log files by searching for value 3
. Note the comments regarding internationalization.
When adding conditional features, it's more intuitive to set the default feature level to 0 (disabled) and then set the condition level to your desired value. If you set the default feature level >= 1, the condition level has to be 0 to disable it, meaning the condition logic has to be the opposite to what you'd expect, which can be confusing :)
<Feature Id="NewInstallFeature" Level="0" Description="New installation feature" Absent="allow">
<Condition Level="1">NOT UPGRADEFOUND</Condition>
</Feature>
<Feature Id="UpgradeFeature" Level="0" Description="Upgrade feature" Absent="allow">
<Condition Level="1">UPGRADEFOUND</Condition>
</Feature>
Checking if IIS is installed:
<Property Id="IIS_MAJOR_VERSION">
<RegistrySearch Id="CheckIISVersion" Root="HKLM" Key="SOFTWARE\Microsoft\InetStp" Name="MajorVersion" Type="raw" />
</Property>
<Condition Message="IIS must be installed">
Installed OR IIS_MAJOR_VERSION
</Condition>
Checking if IIS 6 Metabase Compatibility is installed on Vista+:
<Property Id="IIS_METABASE_COMPAT">
<RegistrySearch Id="CheckIISMetabase" Root="HKLM" Key="SOFTWARE\Microsoft\InetStp\Components" Name="ADSICompatibility" Type="raw" />
</Property>
<Condition Message="IIS 6 Metabase Compatibility feature must be installed">
Installed OR ((VersionNT < 600) OR IIS_METABASE_COMPAT)
</Condition>
Keep all IDs in separate namespaces
F.
Examples: F.Documentation, F.Binaries, F.SampleCode. C.
Ex: C.ChmFile, C.ReleaseNotes, C.LicenseFile, C.IniFile, C.RegistryCA.
Ex: CA.LaunchHelp, CA.UpdateReadyDlg, CA.SetPropertyXFi.
Di.
I find this helps immensely in keeping track of all the various id's in all the various categories.
Fantastic question. I'd love to see some best practices shown.
I've got a lot of files that I distribute, so I've set up my project into several wxs source files.
I have a top level source file which I call Product.wxs which basically contains the structure for the installation, but not the actual components. This file has several sections:
<Product ...>
<Package ...>
<Media>...
<Condition>s ...
<Upgrade ..>
<Directory>
...
</Directory>
<Feature>
<ComponentGroupRef ... > A bunch of these that
</Feature>
<UI ...>
<Property...>
<Custom Actions...>
<Install Sequences....
</Package>
</Product>
The rest of the .wix files are composed of Fragments that contain ComponentGroups which are referenced in the Feature tag in the Product.wxs. My project contains a nice logical grouping of the files that I distribute
<Fragment>
<ComponentGroup>
<ComponentRef>
....
</ComponentGroup>
<DirectoryRef>
<Component... for each file
....
</DirectoryRef>
</Fragment>
This isn't perfect, my OO spider sense tingles a bit because the fragments have to reference names in the Product.wxs file (e.g. the DirectoryRef) but I find it easier to maintain that a single large source file.
I'd love to hear comments on this, or if anyone has any good tips too!
Add a checkbox to the exit dialog to launch the app, or the helpfile.
...
<!-- CA to launch the exe after install -->
<CustomAction Id ="CA.StartAppOnExit"
FileKey ="YourAppExeId"
ExeCommand =""
Execute ="immediate"
Impersonate ="yes"
Return ="asyncNoWait" />
<!-- CA to launch the help file -->
<CustomAction Id ="CA.LaunchHelp"
Directory ="INSTALLDIR"
ExeCommand ='[WindowsFolder]hh.exe IirfGuide.chm'
Execute ="immediate"
Return ="asyncNoWait" />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT"
Value="Launch MyApp when setup exits." />
<UI>
<Publish Dialog ="ExitDialog"
Control ="Finish"
Order ="1"
Event ="DoAction"
Value ="CA.StartAppOnExit">WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT</Publish>
</UI>
If you do it this way, the "standard" appearance isn't quite right. The checkbox is always a gray background, while the dialog is white:
alt text http://www.dizzymonkeydesign.com/blog/misc/adding-and-customizing-dlgs-in-wix-3/images/exit_dlg_1.gif
One way around this is to specify your own custom ExitDialog, with a differently-located checkbox. This works, but seems like a lot of work just to change the color of one control. Another way to solve the same thing is to post-process the generated MSI to change the X,Y fields in the Control table for that particular CheckBox control. The javascript code looks like this:
var msiOpenDatabaseModeTransact = 1;
var filespec = WScript.Arguments(0);
var installer = new ActiveXObject("WindowsInstaller.Installer");
var database = installer.OpenDatabase(filespec, msiOpenDatabaseModeTransact);
var sql = "UPDATE `Control` SET `Control`.`Height` = '18', `Control`.`Width` = '170'," +
" `Control`.`Y`='243', `Control`.`X`='10' " +
"WHERE `Control`.`Dialog_`='ExitDialog' AND " +
" `Control`.`Control`='OptionalCheckBox'";
var view = database.OpenView(sql);
view.Execute();
view.Close();
database.Commit();
Running this code as a command-line script (using cscript.exe) after the MSI is generated (from light.exe) will produce an ExitDialog that looks more professional:
alt text http://www.dizzymonkeydesign.com/blog/misc/adding-and-customizing-dlgs-in-wix-3/images/exit_dlg_2.gif
In a nutshell: Create unique UpgradeCode for each installer and automagically define the first character of each Guid for each installer, leaving the remaining 31 unique.
Example Config.wxi
<?xml version="1.0" encoding="utf-8"?>
<Include>
<!-- Upgrade code should not change unless you want to install
a new product and have the old product remain installed,
that is, both products existing as separate instances. -->
<?define UpgradeCode = "YOUR-GUID-HERE" ?>
<!-- Platform specific variables -->
<?if $(var.Platform) = x64 ?>
<!-- Product name as you want it to appear in Add/Remove Programs-->
<?define ProductName = "Foo 64 Bit [Live]" ?>
<?else ?>
<?define ProductName = "Foo [Live]" ?>
<?endif ?>
<!-- Directory name used as default installation location -->
<?define InstallName = "Foo [Live]" ?>
<!-- Registry key name used to store installation location -->
<?define InstallNameKey = "FooLive" ?>
<?define VDirName = "FooLive" ?>
<?define AppPoolName = "FooLiveAppPool" ?>
<?define DbName = "BlahBlahLive" ?>
</Include>
Example Config.Common.wxi
<?xml version="1.0" encoding="utf-8"?>
<Include>
<!-- Auto-generate ProductCode for each build, release and upgrade -->
<?define ProductCode = "*" ?>
<!-- Note that 4th version (Revision) is ignored by Windows Installer -->
<?define ProductVersion = "1.0.0.0" ?>
<!-- Minimum version supported if product already installed and this is an upgrade -->
<!-- Note that 4th version (Revision) is ignored by Windows Installer -->
<?define MinimumUpgradeVersion = "0.0.0.0" ?>
<!-- Platform specific variables -->
<?if $(var.Platform) = x64 ?>
<?define Win64 = "yes" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else ?>
<?define Win64 = "no" ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif ?>
<?define ProductManufacturer = "Foo Technologies"?>
<!-- Decimal Language ID (LCID) for the Product. Used for localization. -->
<?define ProductLanguage = "1033" ?>
<?define WebSiteName = "DefaultWebSite" ?>
<?define WebSitePort = "80" ?>
<?define DbServer = "(local)" ?>
</Include>
Example Components.wxs
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<!-- The pre-processor variable which allows the magic to happen :) -->
<?include $(sys.CURRENTDIR)\Config.wxi?>
<?include ..\Setup.Library\Config.Common.wxi?>
<Fragment Id="ComponentsFragment">
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="$(var.PlatformProgramFilesFolder)">
<Directory Id="INSTALLLOCATION" Name="$(var.InstallName)">
<Component Id="ProductComponent" Guid="0XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" KeyPath="yes">
...
Note: I would now suggest leaving the Guid attribute out of Component (equivalent of *
), using one file per component and setting the file as the keypath. This removes the need for calling ModifyComponentsGuids
and RevertComponentsGuids
targets shown below. This might not be possible for all your components though.
Example Setup.Live.wixproj
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets" />
<Target Name="BeforeBuild">
<CallTarget Targets="ModifyComponentsGuids" />
</Target>
<Target Name="AfterBuild">
<CallTarget Targets="RevertComponentsGuids" />
</Target>
<!-- Modify the first character of every Guid to create unique value for Live, Test and Training builds -->
<Target Name="ModifyComponentsGuids">
<FileUpdate Files="..\Setup.Library\Components.wxs" Regex="Guid="([a-f]|[A-F]|\d)" ReplacementText="Guid="A" />
</Target>
<!-- Revert the first character of every Guid back to initial value -->
<Target Name="RevertComponentsGuids">
<FileUpdate Files="..\Setup.Library\Components.wxs" Regex="Guid="([a-f]|[A-F]|\d)" ReplacementText="Guid="0" />
</Target>
Final thoughts
UPDATE 1: Auto-generating component Guids removes the need for calling FileUpdate task if you create component with Guid="*" for each file, setting the file as the keypath.
UPDATE 2: One of the issues we've come up against is if you don't auto-generate your component Guid's and the build fails, then the temp files need to be manually deleted.
UPDATE 3: Found a way to remove reliance on svn:externals and temporary file creation. This makes the build process more resilient (and is best option if you can't wildcard your Guids) and less brittle if there is a build failure in light or candle.
UPDATE 4: Support for Multiple Instances using instance transforms is in WiX 3.0+, definitely also worth a look.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With