So you have one of those annoyingly XMLish setups from Windows Installer and you want to preserve the values you input so they are prefilled at future upgrades. There are a lot of articles on the Internet on how to do this, but all of them seem to be missing something. I am sure this one will too, but it worked for me.

Let's start with a basic setup.
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<UI Id="DatabaseAuthenticationDialogUI">
<Property Id="DATABASEDOMAIN" Secure="yes"/>
<Dialog
Id="DatabaseAuthenticationDialog"
Width="370"
Height="270"
Title="[ProductName] database authentication"
NoMinimize="yes">
<Control Id="DatabaseDomainLabel" Type="Text" X="45" Y="110" Width="100" Height="15" TabSkip="no" Text="!(loc.Domain):" />
<Control Id="DatabaseDomainEdit" Type="Edit" X="45" Y="122" Width="220" Height="18" Property="DATABASEDOMAIN" Text="{80}"/>

So this is a database authentication dialog, with only the relevant lines in it. We have a property defined as DATABASEDOMAIN and then an edit control that edits this property. Ideally, we would want to make sure this property is being saved somewhere at the end of the install and it is retrieved before the install to be populated. To do this we will first define a DATABASEDOMAIN_SAVED property and load/save it in the registry, then link it with DATABASEDOMAIN.

First, there is the issue of where to put this code. Personally, I put them all under Product, as a separate mechanism for preserving and loading values. I am sure there are other places in your XML files where you can do it. Here is how my Product.wxs code looks like (just relevant lines):
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Product ... >
<Property Id="SAVED_DATABASEDOMAIN" Secure="yes">
<RegistrySearch Id="FindSavedDATABASEDOMAIN"
Root="HKLM"
Key="SOFTWARE\MyCompany\MyProduct"
Name="DatabaseDomain"
Type="raw" />
</Property>
<SetProperty Id="DATABASEDOMAIN" After="AppSearch" Value="[SAVED_DATABASEDOMAIN]" Sequence="ui">
<![CDATA[SAVED_DATABASEDOMAIN]]>
</SetProperty>
</Product>
 
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFiles64Folder">
<Directory Id="MyFolder" Name="MyFolder">
<Directory Id="INSTALLFOLDER" Name="MyProduct">
<Component Id="InstallFolderComponent" Guid="c5ccddcc-8442-49e8-aa17-59f84feb4deb">
<RegistryKey
Root="HKLM"
Key="SOFTWARE\MyCompany\MyProduct"
>
<RegistryValue Id="DatabaseDomain"
Action="write"
Type="string"
Name="DatabaseDomain"
Value="[DATABASEDOMAIN]" />
</RegistryKey>
<CreateFolder/>
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
</Fragment>
</Wix>

This is what happens:
  1. We search in the registry and set the value for DATABASEDOMAIN_SAVED.
  2. We set the DATABASEDOMAIN value from the DATABASEDOMAIN_SAVED value, if that is set. Note that Sequence is set to "ui". This is very important, as the default value is "both". In my case I spent hours to figure out why the values were written in the registry, but then would never change again. It was because there are two sequences: "ui" and "execute". The code would read the value from the registry, the user would then change the values, then, right before installing anything, the value would be read from the registry AGAIN and would overwrite the user input.
  3. Finally, when we install the product we save in the registry the value of DATABASEDOMAIN, whatever it is.

This should be it, but there are a few gotchas. One of them is checkboxes. For Windows Installer the value of a checkbox either is or isn't. It's not set to 0 or 1, true or false or anything like that. So if you save the value attached to an unchecked checkbox control, when read, even if empty, it will be set. Your checkbox will always be set from then on. The solution I used was adding a prefix, then setting the value for the checkbox only if that value is what I expect it to be. Here it is, in a gist:

    <!-- Product.wxs -->
<!-- this doesn't change -->
<Property Id="SAVED_DONOTRUNSCRIPTS" Secure="yes">
<RegistrySearch Id="FindSavedDONOTRUNSCRIPTS"
Root="HKLM"
Key="SOFTWARE\MyCompany\MyProduct"
Name="DoNotRunScripts"
Type="raw" />
</Property>
<!-- here, however, I check for Val1 to set the value of the property to 1 -->
<SetProperty Id="DONOTRUNSCRIPTS" After="AppSearch" Value="1" Sequence="ui">
<![CDATA[SAVED_DONOTRUNSCRIPTS = "Val1"]]>
</SetProperty>
<!-- Note the Val prefix when saving the value -->
<RegistryValue Id="DoNotRunScripts"
Action="write"
Type="string"
Name="DoNotRunScripts"
Value="Val[DONOTRUNSCRIPTS]" />
 
<!-- DatabaseSetup.wxs -->
<!-- Note the checkbox value -->
<Control Id="DoNotRunScriptsCheckbox" Type="CheckBox" X="45" Y="197" Height="17" Width="17" Property="DONOTRUNSCRIPTS" CheckBoxValue="1"/>

I Hope that helps people.

Comments

Be the first to post a comment

Post a comment