installationwixwindows-installerregistrywix3

Preserve registry settings when upgrade


I am using WiX 3.0. I am trying to preseve all my registry settings when doing upgrade.

I found the following link http://www.mail-archive.com/wix-users@lists.sourceforge.net/msg28844.html

I prefer to use Property and RegistrySearch approach rather than custom action approach because I thought it looks simpler.

Unfortunately, this approach doesn't work very well for me. My projects got some DWORD and SZ type registry value. For all DWORD type registry values, it appends a # in the front. I checked the WiX refernce manual. This is how RegistrySearch works.

So, after the upgrade, the result is all my DWORD type registry is changed to SZ. The value looks the same except it has a # appended in the front. For example, I got value 2 in the registry value "LogLevel" with type DWORD. After upgrade, I get value "#2" in the registry value "LogLevel" with type SZ in the same location.

Here is the code fragment

<Property Id='LOG_LEVEL' Value='Information'>
  <RegistrySearch Id='LogLevelRegistry' Type='raw' Root='HKLM' Key='Software\Company\Product' Name='LogLevel' Win64='$(var.Win64)'/>
</Property>

<Component Id="RegistryKey">
    <RegistryKey Root='HKLM' Key='Software\Company\Product' Action='createAndRemoveOnUninstall'>
      <RegistryValue Type='int' Name='LogLevel' Value='[LOG_LEVEL]'/>
    </RegistryKey>
</Component>

I wonder if there is any string function that can help me trim off the "#" from LOG_LEVEL property before I put it back. Or is there any smarter way to preserve the registry keys when doing upgrade? Should I go for Custom Action approach?


Solution

  • I finally got the RegistrySearch approach working. I hope this may help some other people in the future.

    I checked the MSI documentation. Registry table actually doesn't contain a column called type. Instead, it stores the Value in a weird way so that MSI knows what type of registry value it should create. In my case, I want to create a DWORD type registry value. The value should be stored as #1 in the Registry table if I want to put numeric value 1 to the registry value. Here is a link to Registry table

    So, why does Wix RegistryValue has a mandatory attribute Type? I think it's because Wix is trying to be nice to you. If you mark the type as int, you can simply put in "1" in the value attribute. You don't need to remember you have to type in "#1" instead of "1". When you compile the Wix source code, the compiler will do the dirty work for you and translate "1" into "#1" for you.

    Similarly, RegistrySearch is a direct mapping to the MSI database table RegLocator. It returns #1 to me because my registry value type is DWORD. It's too bad that Wix didn't do the translation for me this time. The following code returns the raw data from the table to me. So, my property LOG_LEVEL is storing #1 instead of 1.

    <Property Id='LOG_LEVEL' Value='3'>
      <RegistrySearch Id='LogLevelRegistry' Type='raw' Root='HKLM' Key='Software\Company\Product' Name='LogLevel' Win64='$(var.Win64)'/>
    </Property>
    

    Here is the link for RegistrySearch

    My code stored the registry value into a property. Then, I was trying to put it back using the following code.

    <Component Id="RegistryKey">
      <RegistryKey Root='HKLM' Key='Software\Company\Product' Action='createAndRemoveOnUninstall'>
        <RegistryValue Type='int' Name='LogLevel' Value='[LOG_LEVEL]'/>
      </RegistryKey>
    </Component>
    

    As I said, Wix would append a # for me when seeing the type "int". So, the value in the Registry table now is ##1. If you check the MSDN document, ##1 would be interpreted as a string value "#1". Therefore, my registry value is recreated with a new type SZ and new value "#1"

    To fix this problem, I changed my code to this

    <Property Id='LOG_LEVEL' Value='#3'>
      <RegistrySearch Id='LogLevelRegistry' Type='raw' Root='HKLM' Key='Software\Company\Product' Name='LogLevel' Win64='$(var.Win64)'/>
    </Property>
    
    <Component Id="RegistryKey">
      <RegistryKey Root='HKLM' Key='Software\Company\Product' Action='createAndRemoveOnUninstall'>
        <RegistryValue Type='string' Name='LogLevel' Value='[LOG_LEVEL]'/>
      </RegistryKey>
    </Component>
    

    Note that althought I specify type "string" here, I still have a registry value with type DWORD created for me. It's because this is how the MSI interprets the value inside the Registry table. If registry value LogLevel didn't exist (brand new installation), I will set a default value #3 to it. If registry value LogLevel exists, it will preserve the existing LogLevel.

    Also note that this method works because Wix 3.0 doesn't do any processing on the property value. It puts the value stored inside the property directly into the Registry table. Just out of curiosity, I also tried the following.

    <RegistryValue Type='string' Name='LogLevel' Value='#1'/>
    

    This time, Wix correctly escape the # character and put ##1 into the Registry table. If later Wix chooses to escape the # character inside the property value too, my solution here won't work.