Wix Toolset - upgrade and do not overwrite config file - wix

I have an WPF app that connects to some web service on some URL. I have made an installation and it works like a charm, it even asks about URL and changes .config file as instructed. Now, I want to upgrade the app, but leave the .config file intact.
I have tried some solutions on web, it doesn't want to upgrade, it seems that only does a clean install every time.
<Package InstallerVersion="400" Compressed="yes" InstallScope="perMachine" Platform="x64" />
<Icon Id="icon.ico" SourceFile="$(var.ProjectDir)Icon.ico" />
<Property Id="ARPPRODUCTICON" Value="icon.ico" />
<WixVariable Id="WixUIBannerBmp" Value="Images\installer_top-banner.bmp" />
<WixVariable Id="WixUIDialogBmp" Value="Images\installer_background.bmp" />
<WixVariable Id="WixUILicenseRtf" Value="$(var.ProjectDir)\license.rtf" />
<Property Id="ARPURLINFOABOUT" Value="http://www.Company.com" />
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
<Property Id="FULLURL" Value="http://demo.Company.com/WcfFullPortal.svc" Secure="yes" />
<Property Id="AUTH" Value="0" Secure="yes" />
<Property Id="AUTHVALUE" Value="$(var.httpValue)" Secure="yes" />
<Property Id="PREVIOUSVERSIONSINSTALLED" Secure="yes" />
<Upgrade Id="1d96517a-8fc5-4150-b1cb-c3adf479a57d">
<UpgradeVersion Minimum="1.0.0.0" Maximum="99.0.0.0" Property="PREVIOUSVERSIONSINSTALLED" IncludeMinimum="yes" IncludeMaximum="no" />
</Upgrade>
<UIRef Id="SetupDialogUI" />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Launch MyApp" />
<Property Id="WixShellExecTarget" Value="[#MyApp.UI.WPF.exe]" />
<CustomAction Id="LaunchApplication" BinaryKey="WixCA" DllEntry="WixShellExec" Impersonate="yes" />
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." AllowSameVersionUpgrades="yes" Schedule="afterInstallExecute" />
<MediaTemplate EmbedCab="yes" />
<!-- 32-bit / 64-bit variables -->
<?if $(var.Platform) = x64 ?>
<?define bitness = "(64 bit)" ?>
<?define Win64 = "yes" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?else?>
<?define bitness = "(32 bit)" ?>
<?define Win64 = "no" ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?endif?>
<Condition Message="Minimum supported OS is Windows 7."><![CDATA[Installed OR (VersionNT >= 600)]]></Condition>
<!--
<?if $(var.Platform) = x64 ?>
<Condition Message="You need to install the 32-bit version of this product on 32-bit Windows.">
<![CDATA[VersionNT64]]>
</Condition>
<?endif?>
<?if $(var.Platform) = x86 ?>
<Condition Message="You need to install the 64-bit version of this product on 64-bit Windows.">
<![CDATA[NOT VersionNT64]]>
</Condition>
<?endif?> -->
</Product>
...

Two approaches:
Finish the work in the installer. MSI doesn't persist (remember) properties on subsequent insallations. You have to harvest the value from the config file back into the property so it can be shown in the UI again and reapplied during the installation.
http://robmensching.com/blog/posts/2010/5/2/the-wix-toolsets-remember-property-pattern/
Move the complexity out of the installer and into the application. Design the application so it asks for the setting on first run. Use the app.config appSettings element file attribute to specify a second file to contain the override setting. Have the installer create the app.config but not the second file. Have the application save the first run obtained setting to the second file. The installer doesn't know about this so it'll never touch the file during subsequent installs.
https://learn.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/appsettings/appsettings-element-for-configuration
I'm a WiX expert and can handle approach 1 easily. For people new to WiX it can be a challenge. I generally go with approach 1 when it's just a handful of settings and the program is non interactive like a windows service or say a web application where the inputs are pool identity. Otherwise I find it's easier for my customers to support if I move the complexity to code they better understand.

Related

Create DSN with Wix

Having trouble making a System DSN via Wix
here is my test code
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" Name="TestProduct" Language="1033" Version="0.0.0.1" Manufacturer="WixEdit" UpgradeCode="*">
<Package Description="Test file in a Product" Comments="Simple test" InstallerVersion="200" Compressed="yes" />
<Media Id="1" Cabinet="simple.cab" EmbedCab="yes" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Component Id="C_PROD_DSN" Guid="*">
<ODBCDataSource Id="Prod_Dsn" Name="SomeName" Registration="machine" DriverName="ODBC Driver 11 for SQL Server">
<Property Id="Description" Value="Some Description" />
<Property Id="Database" Value="SomeDB" />
<Property Id="Language" Value="us_english" />
<Property Id="LastUser" Value="SomeUser" />
<Property Id="Driver" Value="C:\windows\system32\msodbcsql11.dll" />
</ODBCDataSource>
</Component>
</Directory>
<Feature Id="DefaultFeature" Title="Main Feature" Level="1">
<ComponentRef Id="C_PROD_DSN" />
</Feature>
<UI />
</Product>
</Wix>
Wix Edit compiles it just fine but when i run it I get
Error configuring ODBC data source: SomeName. ODBC error 6: Component not found in the registry. Verify that the file SomeName exists and that you can access it.
This is a DSN to a SQL DB not to a file.
I know I can make this work by just building up the Registry trees I need, I just like to try it using what should be the built in functionality for the task.
Can this be done using ODBCDataSource? (without Custom Actions, I just don't have time to write another one right now) or should I just cut my losses and do it with RegistryKey and RegistryValue?

Wix installer does not overwrite previous version of an executable

I have a very simple installer - copy a single dll to a Program Files subfolder and register it with regsvr32.exe. Works great, but if an older version of the dll is installed, "Repair" does not overwrite the existing dll. The dll is signed and its version (build) number is always incremented (e.g. 2.0.0.123 - > 2.0.0.124).
Looking at the previous similar posts, I added RemoveExistingProducts and specified ProductId as "*". Uninstalling and then installing the newer version works fine, but I really need Repair to update the existing dll.
Is there anything else I need to do?
Thank you!
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<!--
When creating a new install for the next version, these fields must be modified
-->
<?define ProductVersion = "2.0.00" ?>
<?define ProductId64 = "*" ?>
<?define ProductId32 = "*" ?>
<?define PackageId = "45F34788-66AC-441C-B666-707FFA7F1EE9" ?>
<!-- Product name as you want it to appear in Add/Remove Programs-->
<?if $(var.Platform) = x64 ?>
<?define ProductName = "XYZ (64 bit)" ?>
<?define Win64 = "yes" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?define ProductId = "$(var.ProductId64)" ?>
<?define MainDllName = "XYZ64.dll" ?>
<?define MainDllSource = "..\..\bin\Win64\Release\XYZ64.dll" ?>
<?else ?>
<?define ProductName = "XYZ (32 bit)" ?>
<?define Win64 = "no" ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?define ProductId = "$(var.ProductId32)" ?>
<?define MainDllName = "XYZ.dll" ?>
<?define MainDllSource = "..\..\bin\Win32\Release\XYZ.dll" ?>
<?endif ?>
<?define UpgradeCode = "{C3763742-7C1C-4AB7-A404-F030B7550E97}" ?>
<Product Id="$(var.ProductId)" Name="$(var.ProductName)" Language="1033" Version="$(var.ProductVersion)" Manufacturer="Advanced Messaging Systems LLC" UpgradeCode="$(var.UpgradeCode)">
<Package Id="$(var.PackageId)" InstallerVersion="200" Compressed="yes" Description="XYZ Installer package" InstallPrivileges="elevated"/>
<!-- No restore point -->
<Property Id="MSIFASTINSTALL" Value="3" />
<Media Id="1" Cabinet="media1.cab" EmbedCab="yes" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="$(var.PlatformProgramFilesFolder)">
<Directory Id="INSTALLLOCATION" Name="XYZ">
<Component Id="XYZDll" Guid="E2CBEE41-6C0E-4A84-95C1-7282747B4A3D">
<File Id='MainDll' Name="$(var.MainDllName)" DiskId='1' Source="$(var.MainDllSource)" SelfRegCost="0" />
<!-- TODO: Insert files, registry keys, and other resources here. -->
</Component>
</Directory>
</Directory>
</Directory>
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLLOCATION" />
<!-- Note: Custom actions to install/uninstall the dll using regsvr32.exe -->
<CustomAction Id="RegisterDll"
Directory="INSTALLLOCATION"
ExeCommand='regsvr32.exe /s "[INSTALLLOCATION]$(var.MainDllName)"'
Return="check">
</CustomAction>
<CustomAction Id="UnregisterDll"
Directory="INSTALLLOCATION"
ExeCommand='regsvr32.exe /s /u "[INSTALLLOCATION]$(var.MainDllName)"'>
</CustomAction>
<Feature Id="ProductFeature" Title="XYZ" Level="1">
<ComponentRef Id="XYZDll" />
<!-- Note: The following ComponentGroupRef is required to pull in generated authoring from project references. -->
<ComponentGroupRef Id="Product.Generated" />
</Feature>
<InstallUISequence>
<Custom Action="WixCloseApplications" Before="AppSearch"/>
</InstallUISequence>
<InstallExecuteSequence>
<!-- Uninstall previous version before installing this one. -->
<RemoveExistingProducts Before="InstallInitialize"/>
<SelfRegModules/>
</InstallExecuteSequence>
<Icon Id="XYZ.ico" SourceFile="..\Graphics\XYZ.ico"/>
<Property Id="ARPPRODUCTICON" Value="XYZ.ico" />
<!-- UI -->
<UIRef Id="WixUI_InstallDir"/>
<UIRef Id="WixUI_ErrorProgressText" />
<WixVariable Id="WixUILicenseRtf" Value="..\EULA\license.rtf" />
<WixVariable Id="WixUIBannerBmp" Value="..\Graphics\banner.jpg" />
<WixVariable Id="WixUIDialogBmp" Value="..\Graphics\logo.jpg" />
<!-- End UI -->
</Product>
</Wix>
UPDATE. The following worked for me after modifying the upgrade entries:
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<!--
When creating a new install for the next version, these fields must be modified
-->
<?define ProductVersion = "2.0.4" ?>
<?define ProductId64 = "*" ?>
<?define ProductId32 = "*" ?>
<?define PackageId = "*" ?>
<!-- Product name as you want it to appear in Add/Remove Programs-->
<?if $(var.Platform) = x64 ?>
<?define ProductName = "XYZ (64 bit)" ?>
<?define Win64 = "yes" ?>
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
<?define ProductId = "$(var.ProductId64)" ?>
<?define MainDllName = "XYZ64.dll" ?>
<?define MainDllSource = "..\..\bin\Win64\Release\XYZ64.dll" ?>
<?else ?>
<?define ProductName = "XYZ (32 bit)" ?>
<?define Win64 = "no" ?>
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
<?define ProductId = "$(var.ProductId32)" ?>
<?define MainDllName = "XYZ.dll" ?>
<?define MainDllSource = "..\..\bin\Win32\Release\XYZ.dll" ?>
<?endif ?>
<?define UpgradeCode = "{C3763742-7C1C-4AB7-A404-F030B7550E97}" ?>
<Product
Id="$(var.ProductId)"
Name="$(var.ProductName)"
Language="1033"
Version="$(var.ProductVersion)"
Manufacturer="Advanced Messaging Systems LLC"
UpgradeCode="$(var.UpgradeCode)"
>
<Package Id="$(var.PackageId)"
InstallerVersion="200"
Compressed="yes"
Description="XYZ Installer package"
InstallPrivileges="elevated"
/>
<!-- No restore point -->
<Property Id="MSIFASTINSTALL" Value="3" />
<Upgrade Id="$(var.UpgradeCode)">
<UpgradeVersion Minimum="1.0.0"
IncludeMinimum="yes"
OnlyDetect="no"
Maximum="$(var.ProductVersion)"
IncludeMaximum="no"
Property="PREVIOUSFOUND" />
</Upgrade>
<Media Id="1" Cabinet="media1.cab" EmbedCab="yes" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="$(var.PlatformProgramFilesFolder)">
<Directory Id="INSTALLLOCATION" Name="XYZ">
<Component Id="XYZDll" Guid="E2CBEE41-6C0E-4A84-95C1-7282747B4A3D">
<File Id='MainDll' Name="$(var.MainDllName)" DiskId='1' Source="$(var.MainDllSource)" SelfRegCost="0" />
<!-- TODO: Insert files, registry keys, and other resources here. -->
</Component>
</Directory>
</Directory>
</Directory>
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLLOCATION" />
<!-- Note: Custom actions to install/uninstall the dll using regsvr32.exe -->
<CustomAction Id="RegisterDll"
Directory="INSTALLLOCATION"
ExeCommand='regsvr32.exe /s "[INSTALLLOCATION]$(var.MainDllName)"'
Return="check">
</CustomAction>
<CustomAction Id="UnregisterDll"
Directory="INSTALLLOCATION"
ExeCommand='regsvr32.exe /s /u "[INSTALLLOCATION]$(var.MainDllName)"'>
</CustomAction>
<Feature Id="ProductFeature" Title="XYZ" Level="1">
<ComponentRef Id="XYZDll" />
<!-- Note: The following ComponentGroupRef is required to pull in generated authoring from project references. -->
<ComponentGroupRef Id="Product.Generated" />
</Feature>
<InstallUISequence>
<Custom Action="WixCloseApplications" Before="AppSearch"/>
</InstallUISequence>
<InstallExecuteSequence>
<RemoveExistingProducts After="InstallInitialize"/>
<SelfRegModules/>
</InstallExecuteSequence>
<Icon Id="XYZ.ico" SourceFile="..\Graphics\XYZ.ico"/>
<Property Id="ARPPRODUCTICON" Value="XYZ.ico" />
<!-- UI -->
<UIRef Id="WixUI_InstallDir"/>
<UIRef Id="WixUI_ErrorProgressText" />
<WixVariable Id="WixUILicenseRtf" Value="..\EULA\license.rtf" />
<WixVariable Id="WixUIBannerBmp" Value="..\Graphics\banner.jpg" />
<WixVariable Id="WixUIDialogBmp" Value="..\Graphics\logo.jpg" />
<!-- End UI -->
</Product>
</Wix>
If you want a major uprade, start with the WiX MajorUpgrade element. The general rules for an upgrade are:
Different ProductCode and PackageCode from older product.
Increment ProductVersion somewhere in the first three fields.
Same UpgradeCode as older product.
Do something (like Wix MajorUprade or Upgrade elements) to ensure that you have an upgrade in place.
A per user install will not upgrade a per system install, or vice versa.
In your original case, failure to follow rules 1 and 2 meant that Windows thought the same product was already installed and went into Repair mode. That should have been your first warning because a major upgrade looks like a fresh install, not a repair. If you have two entries in Programs&Features it means that one or more of those 4 requirements has not been met. If you get "Another version of this product is already installed" it means you didn't follow rule 1, and variations in behavior are about ProductCode and PackageCode values compared with the installed product.
I am surprised that this line actually is allowed by the Wix compiler and linker:
<?define PackageId = "45F34788-66AC-441C-B666-707FFA7F1EE9" ?>
If this actually works and goes through, which I haven't tested, it means your package has a hard coded package id. I thought Wix featured protection against this problem? Perhaps it does? We should check with the Wix community if it ever makes any sense to allow hard coded package guid. I suppose it could be necessary for debugging and testing purposes - but there should at least be a compiler warning.
The idea of a package GUID is that it should be unique for each compiled MSI file. It is simply there to uniquely identify a file. Two different MSI files with the same package guid will be treated by Windows Installer as the same file by definition. All kinds of x-files problems result. Accordingly a package GUID should always be auto-generated since it is simply supposed to be unique. Please try to resolve this problem first to check if this solves your overall problem. Set it equal to *.
My advice is to auto-generate package id and product id but to set a hard coded upgrade code. An upgrade code identifies a "family of products" and is useful for identifying any instance of your product regardless of language and version. It might be useful to use a separate upgrade code for your 64 vs 32 bit setups since both versions can be installed at the same time on some systems.
You might also want to eliminate the use of regsvr32.exe for self-registration and extract COM data from your dll for proper MSI support: MSI register dll - Self-Registration considered harmful. And perhaps also check this: Register ActiveX exe server using WiX (check out RegSpy2 if Heat doesn't work).
Also note that you can leave out a lot of source attributes from your Wix xml file and rely on Wix defaults instead of hard coding values.
Some further details on GUIDs and file replacement:
Change my component GUID in wix?
MSI Reference Counting: Two products install the same MSIs
Forcing an upgrade of a file that is modified during its initial installation
msi version numbers
File Versioning Rules
Replacing existing files (diagram, both files have version)
Plain English file versioning description by Aaron Stebner
Short answer (the other one became too messy): try removing this line and let the package ID be auto generated by setting it to "*":
<?define PackageId = "45F34788-66AC-441C-B666-707FFA7F1EE9" ?>
Note that you must stop using all previous MSI builds after uninstalling them all. This is due to the faulty hard coded package guid which can cause unpredictable and unforeseen problems.

WiX: Setting the install directory from a RegistySearch causes the error "Could not acess network location"

I am making an installer based on WixUI_Advanced. I am using the solution from here: WiX: How to override "C:\Program Files (x86)" on x64 machine in WixUI_Advanced sequence?
However, I want the installer to remember the previous install directory. Attempting to set the APPLICATIONFOLDER property results in the error "Could not access network location" followed by the directory name. This only happens when a value is actually found in the registry (when upgrading the program).
Here is the relevant code:
<Product Id="*" Name="$(var.ProductName)" Language="1033" Version="$(var.Version)" Manufacturer="OronDF343" UpgradeCode="22187c5e-5fd6-4734-802e-236abd321433">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" Description="$(var.ProductName)" />
<PropertyRef Id="NETFRAMEWORK45" />
<Condition Message="This application requires .NET Framework 4.5.1 or later. Please install .NET Framework version 4.5.1 or higher and then run this installer again.">
<![CDATA[Installed OR (NETFRAMEWORK45 >= "#378758")]]>
</Condition>
<MajorUpgrade AllowSameVersionUpgrades="yes" DowngradeErrorMessage="A newer version of $(var.GlobalProductName) is already installed." />
<!-- This is where the problem is. This is how it was implemented in the solution linked above, but I have tried it without the SetDirectory. -->
<Property Id="APPLICATIONFOLDER" Secure="yes">
<RegistrySearch Id="RegistrySearch" Type="raw" Root="HKLM" Win64="$(var.Win64)" Key="Software\$(var.Company)\$(var.ProgID)" Name="InstallLocation" />
</Property>
<SetDirectory Id="APPLICATIONFOLDER" Value="[$(var.PlatformProgramFilesFolder)]$(var.GlobalProductName)">APPLICATIONFOLDER=""</SetDirectory>
<!---->
<MediaTemplate EmbedCab="yes" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="DesktopFolder" Name="Desktop" />
<Directory Id="$(var.PlatformProgramFilesFolder)">
<Directory Id="APPLICATIONFOLDER" Name="$(var.GlobalProductName)">
<Directory Id="skins" Name="skins"/>
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="ApplicationProgramsFolder" Name="$(var.GlobalProductName)"/>
</Directory>
</Directory>
<Property Id="ApplicationFolderName" Value="$(var.GlobalProductName)" />
<Property Id="WixAppFolder" Value="WixPerMachineFolder" />
<WixVariable Id="WixUISupportPerUser" Value="0" />
<WixVariable Id="WixUILicenseRtf" Value="gpl-3.0.rtf" />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Launch $(var.GlobalProductName)" />
<Property Id="WixShellExecTarget" Value="[#$(var.Project.TargetFileName)]" />
<CustomAction Id="LaunchApplication" BinaryKey="WixCA" DllEntry="WixShellExec" Impersonate="yes" />
<Feature Id="ProductFeature" Title="$(var.ProductName) Core Files" Level="1" Absent="disallow">
<ComponentRef Id="CreateDirectories" />
<ComponentGroupRef Id="ProductComponents" />
<ComponentGroupRef Id="RegistryStuff" />
</Feature>
<UI>
<UIRef Id="WixUI_Advanced" />
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchApplication">WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish>
</UI>
<!--Hacks for WixUI_Advanced install folder-->
<CustomAction Id="OverwriteWixSetDefaultPerMachineFolder" Property="WixPerMachineFolder" Value="[APPLICATIONFOLDER]" Execute="immediate" />
<CustomAction Id="SetARPINSTALLLOCATION" Property="ARPINSTALLLOCATION" Value="[APPLICATIONFOLDER]" />
<InstallUISequence>
<Custom Action="OverwriteWixSetDefaultPerMachineFolder" After="WixSetDefaultPerMachineFolder" />
</InstallUISequence>
<InstallExecuteSequence>
<Custom Action="OverwriteWixSetDefaultPerMachineFolder" After="WixSetDefaultPerMachineFolder" />
<Custom Action="SetARPINSTALLLOCATION" After="InstallValidate"/>
</InstallExecuteSequence>
</Product>
EDIT: I didn't include the code which saves the registry value but it was being saved as '"[APPLICATIONFOLDER]"' and removing the double quotes fixed it for a fresh install.
Also forgot brackets in the SetDirectory

WiX Extended Bootstrapper Application: How to determine which radio button is selected?

Here's my setup:
Compiling at .NET 4.0 (this cannot change)
Using WiX 3.7
Using the WiX Extended Bootstrapper Application extension
So I'm creating a bootstrapper installation project and though I've got it working, I feel like there is a much more clean and easy way to do it.
Basically, I need to have the user select which environment they wish to use when they install the product. To do this, I use the WiX Extended Bootstrapper Application extension to allow me to put some radio buttons on the first install screen.
Getting that all setup was fine, but then I realized that I don't know how to easily determine which radio button has been selected. So as I work around I just listed the MSI three times in the bundle and put install conditions on each one.
Is there a way to determine which radio button has been selected with just one property? (For example, when I pass the InstallFolder property to my MSI as seen in the code below.) And if there isn't a way to do this currently, is there a way for me to do this without adding my MSI three times to the bundle?
Here's my code (Notice how I list essentially the same MsiPackage three times):
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:bal="http://schemas.microsoft.com/wix/BalExtension"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<?include Properties.wxi ?>
<Bundle Name="!(loc.Product.Name)" Version="$(var.Version)" Manufacturer="!(loc.Product.Manufacturer)" UpgradeCode="$(var.UpgradeCode)" Condition="VersionNT >= v5.1">
<BootstrapperApplicationRef Id="WixExtendedBootstrapperApplication.Hyperlink2License">
<bal:WixExtendedBootstrapperApplication SuppressRepair="yes" LicenseUrl="" ThemeFile="$(var.Bundle.ExtTheme.RadioBtns.Path)" LocalizationFile="$(var.Bundle.ExtTheme.RadioBtns.l10n.Path)" />
</BootstrapperApplicationRef>
<WixVariable Id="WixMbaPrereqPackageId" Value="Netfx4Full" />
<Variable Name="InstallFolder" Type="string" Value="$(var.InstallFolder.Value)" />
<Variable Name="RadioButton1" Type="numeric" Value="0" />
<Variable Name="RadioButton2" Type="numeric" Value="0" />
<Variable Name="RadioButton3" Type="numeric" Value="1" />
<Chain>
<!-- Other Package References Here -->
<MsiPackage Id="DBConnections_Dev"
SourceFile="$(var.MSI.Path)"
Visible="no"
Vital="yes"
InstallCondition="RadioButton1 = 1">
<MsiProperty Name="INSTALLFOLDER" Value="[InstallFolder]" />
<MsiProperty Name="SELECTED_ENV" Value="1" />
</MsiPackage>
<MsiPackage Id="DBConnections_Stage"
SourceFile="$(var.MSI.Path)"
Visible="no"
Vital="yes"
InstallCondition="RadioButton2 = 1">
<MsiProperty Name="INSTALLFOLDER" Value="[InstallFolder]" />
<MsiProperty Name="SELECTED_ENV" Value="2" />
</MsiPackage>
<MsiPackage Id="DBConnections_Prod"
SourceFile="$(var.MSI.Path)"
Visible="no"
Vital="yes"
InstallCondition="RadioButton3 = 1">
<MsiProperty Name="INSTALLFOLDER" Value="[InstallFolder]" />
<MsiProperty Name="SELECTED_ENV" Value="3" />
</MsiPackage>
</Chain>
</Bundle>
</Wix>
I also asked this question on the project's CodePlex site and the developer responded as follows (here is a link to the full discussion: http://wixextba.codeplex.com/discussions/432341):
This can now be done using the custom actions feature.
I've tested his response and found it to be correct! This functionality is available as of version 3.7.4791.32058. There is also an example included in the source code demonstrating how this is done. I've posted the pertinent code below:
Needed in Custom Action c++ code:
STDMETHODIMP OnPlanCustomAction()
{
...
if (SUCCEEDED(BalGetNumericVariable(L"RadioButton1", &llValue)) && llValue)
{
m_pEngine->SetVariableNumeric(L"RadioButton", 1);
BalExitOnFailure(hr, "Failed to set variable.");
}
else if (SUCCEEDED(BalGetNumericVariable(L"RadioButton2", &llValue)) && llValue)
{
m_pEngine->SetVariableNumeric(L"RadioButton", 2);
BalExitOnFailure(hr, "Failed to set variable.");
}
else if (SUCCEEDED(BalGetNumericVariable(L"RadioButton3", &llValue)) && llValue)
{
m_pEngine->SetVariableNumeric(L"RadioButton", 3);
BalExitOnFailure(hr, "Failed to set variable.");
}
else if (SUCCEEDED(BalGetNumericVariable(L"RadioButton4", &llValue)) && llValue)
{
m_pEngine->SetVariableNumeric(L"RadioButton", 4);
BalExitOnFailure(hr, "Failed to set variable.");
}
else
{
m_pEngine->SetVariableNumeric(L"RadioButton", 0);
BalExitOnFailure(hr, "Failed to set variable.");
}
...
Needed in WiX XML (from examples that come with downloaded DLL):
<Variable Name="RadioButton1" Type="numeric" Value="0" />
<Variable Name="RadioButton2" Type="numeric" Value="1" />
<Variable Name="RadioButton3" Type="numeric" Value="0" />
<Variable Name="RadioButton4" Type="numeric" Value="0" />
<Chain DisableSystemRestore="yes">
<PackageGroupRef Id="NetFx40Redist" />
<MsiPackage
Id="Setup"
Compressed="yes"
SourceFile="Setup.msi"
Vital="yes">
<MsiProperty Name="APPLICATIONFOLDER" Value="[InstallFolder]" />
<MsiProperty Name="RadioButton" Value="[RadioButton]" />
</MsiPackage>
</Chain>
Check out the links in the text for the original examples from which these code samples were taken. Hope this helps others in the future.

Make WiX installation set upgrade to the same folder

How can I make a major upgrade to an installation set (MSI) built with WiX install into the same folder as the original installation?
The installation is correctly detected as an upgrade, but the directory selection screen is still shown and with the default value (not necessarily the current installation folder).
Do I have to do manual work like saving the installation folder in a registry key upon first installing and then read this key upon upgrade? If so, is there any example?
Or is there some easier way to achieve this in MSI or WiX?
As reference, I my current WiX file is below:
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi">
<Product Id="a2298d1d-ba60-4c4d-92e3-a77413f54a53"
Name="MyCompany Integration Framework 1.0.0"
Language="1033"
Version="1.0.0"
Manufacturer="MyCompany"
UpgradeCode="9071eacc-9b5a-48e3-bb90-8064d2b2c45d">
<!-- Package information -->
<Package Keywords="Installer"
Id="e85e6190-1cd4-49f5-8924-9da5fcb8aee8"
Description="Installs MyCompany Integration Framework 1.0.0"
Comments="Installs MyCompany Integration Framework 1.0.0"
InstallerVersion="100"
Compressed="yes" />
<Upgrade Id='9071eacc-9b5a-48e3-bb90-8064d2b2c45d'>
<UpgradeVersion Property="PATCHFOUND"
OnlyDetect="no"
Minimum="0.0.1"
IncludeMinimum="yes"
Maximum="1.0.0"
IncludeMaximum="yes"/>
</Upgrade>
<!-- Useless but necessary... -->
<Media Id="1" Cabinet="MyCompany.cab" EmbedCab="yes" />
<!-- Precondition: .NET 2 must be installed -->
<Condition Message='This setup requires the .NET Framework 2 or higher.'>
<![CDATA[MsiNetAssemblySupport >= "2.0.50727"]]>
</Condition>
<Directory Id="TARGETDIR"
Name="SourceDir">
<Directory Id="MyCompany"
Name="MyCompany">
<Directory Id="INSTALLDIR"
Name="Integrat"
LongName="MyCompany Integration Framework">
<Component Id="MyCompanyDllComponent"
Guid="4f362043-03a0-472d-a84f-896522ce7d2b"
DiskId="1">
<File Id="MyCompanyIntegrationDll"
Name="IbIntegr.dll"
src="..\Build\MyCompany.Integration.dll"
Vital="yes"
LongName="MyCompany.Integration.dll" />
<File Id="MyCompanyServiceModelDll"
Name="IbSerMod.dll"
src="..\Build\MyCompany.ServiceModel.dll"
Vital="yes"
LongName="MyCompany.ServiceModel.dll" />
</Component>
<!-- More components -->
</Directory>
</Directory>
</Directory>
<Feature Id="MyCompanyProductFeature"
Title='MyCompany Integration Framework'
Description='The complete package'
Display='expand'
Level="1"
InstallDefault='local'
ConfigurableDirectory="INSTALLDIR">
<ComponentRef Id="MyCompanyDllComponent" />
</Feature>
<!-- Task scheduler application. It has to be used as a property -->
<Property Id="finaltaskexe"
Value="MyCompany.Integration.Host.exe" />
<Property Id="WIXUI_INSTALLDIR"
Value="INSTALLDIR" />
<InstallExecuteSequence>
<!-- command must be executed: MyCompany.Integration.Host.exe /INITIALCONFIG parameters.xml -->
<Custom Action='PropertyAssign'
After='InstallFinalize'>NOT Installed AND NOT PATCHFOUND</Custom>
<Custom Action='LaunchFile'
After='InstallFinalize'>NOT Installed AND NOT PATCHFOUND</Custom>
<RemoveExistingProducts Before='CostInitialize' />
</InstallExecuteSequence>
<!-- execute comand -->
<CustomAction Id='PropertyAssign'
Property='PathProperty'
Value='[INSTALLDIR][finaltaskexe]' />
<CustomAction Id='LaunchFile'
Property='PathProperty'
ExeCommand='/INITIALCONFIG "[INSTALLDIR]parameters.xml"'
Return='asyncNoWait' />
<!-- User interface information -->
<UIRef Id="WixUI_InstallDir" />
<UIRef Id="WixUI_ErrorProgressText" />
</Product>
</Wix>
There's an example in the WiX tutorial: https://www.firegiant.com/wix/tutorial/getting-started/where-to-install/
<Property Id="INSTALLDIR">
<RegistrySearch Id='AcmeFoobarRegistry' Type='raw'
Root='HKLM' Key='Software\Acme\Foobar 1.0' Name='InstallDir' />
</Property>
Of course, you've got to set the registry key as part of the install too. Stick this inside a component that's part of the original install:
<RegistryKey
Key="Software\Software\Acme\Foobar 1.0"
Root="HKLM">
<RegistryValue Id="FoobarRegInstallDir"
Type="string"
Name="InstallDir"
Value="[INSTALLDIR]" />
</RegistryKey>
'Registry' is deprecated. Now that part of code should look like this:
<RegistryKey Id="FoobarRegRoot"
Action="createAndRemoveOnUninstall"
Key="Software\Software\Acme\Foobar 1.0"
Root="HKLM">
<RegistryValue Id="FoobarRegInstallDir"
Type="string"
Name="InstallDir"
Value="[INSTALLDIR]" />
</RegistryKey>
You don't really need to separate RegistryKey from RegistryValue in a simple case like this. Also, using HKMU instead of HKLM takes care of it whether you're doing a machine or user install.
<RegistryValue
Root="HKMU"
Key="Software\[Manufacturer]\[ProductName]"
Name="InstallDir"
Type="string"
Value="[INSTALLDIR]"
KeyPath="yes" />