How to keep a config file when major upgrade in wix v3.8?
Asked Answered
A

5

16

I want to keep a config file when the msi installer does a major upgrade. For the config file, I make a change when installing. The code is as follows:

<Component Id="MODIFYCONFIG" Guid="6A1D7762-B707-4084-A01F-6F936CC159CE" Win64="yes">
    <File Id="Application.config" Name="Application.config" Vital="yes" KeyPath="yes" Source="Resource\Application.config"></File>
    <util:XmlFile Id="SetValueIP" Action="setValue" Permanent="yes" File="[#Application.config]"
         ElementPath="/configuration/applicationSettings/Application.Properties.Settings/setting[\[]@name='IpAddress'[\]]/value"  Value="[IPADDRESS]" Sequence="1"/>
    <util:XmlFile Id="SetValuePort" Action="setValue" Permanent="yes" File="[#Application.config]"
         ElementPath="/configuration/applicationSettings/Application.Properties.Settings/setting[\[]@name='IpPort'[\]]/value"  Value="[PORT]" Sequence="2"/>
    <Condition>Not Installed</Condition>
  </Component>
  <Component Id="KEEPCONFIG" Guid="F7F173AA-C2FD-4017-BFBC-B81852A671E7" Win64="yes">
    <RemoveFile Id="ApplicationConfig" Name="Application.config" On="uninstall"/>
    <Condition>(REMOVE=ALL) AND (NOT UPGRADINGPRODUCTCODE)</Condition>
  </Component>

But when a major upgrade occurs the file is not preserved. How can I preserve the modified file?

Awl answered 24/4, 2013 at 7:39 Comment(0)
J
13

This solved it for me... config file is preserved with minor/major upgrade, and completely removed on uninstall.

Ref: Aaron Stebner: How to retain user-customized files during a Windows Installer major upgrade

EDIT: Summarized info from the linked page...

  1. Each config file shall have it's own component, where the config file is marked as the keypath of the component. Unversioned file replacement logic will be used by the Windows Installer.
  2. Add "RemoveExistingProducts" action after the "InstallFiles" action. New versions of all components are installed before removing the old MSI. When it's done in this sequence, the components will have their reference count incremented to 2, but the config files will not be replaced unless they are unmodified (because of unversioned file replacement logic). When the old MSI is removed, the reference count will be decremented back to 1, but the files will not be removed because the reference count are not 0.
Jeffries answered 27/1, 2014 at 14:14 Comment(7)
Yes, this works and is the best option. You need to obey all component rules and referencing or files may be missing after the major upgrade. You can also preserve a config file by setting its component permanent and then use a custom action conditioned to run only on an uninstall that is not part of a major upgrade to clean up when a real uninstall is performed. The condition UPGRADINGPRODUCTCODE can be used to detect if a major upgrade is being performed. Just leaving this in for reference for whoever may face the same problem.Arissa
The link is very good value, but I think it would be good to summarise the post just in case it ever goes away.Rotarian
Part 2 seem to be wrong. Get an error message, when adding "RemoveExistingProducts" action after "InstallFiles": Some action falls between InstallInitialize and RemoveExistingProducts.Brebner
This does not work with the MajorUpgrade as you cannot add RemoveExistingProducts yourself then.Heartbeat
@Heartbeat You cannot add RemoveExistingProducts but you can set the Schedule attribute on the MajorUpgrade element that will do the same job.Henryhenryetta
I got a DEBUG: Error 2613: RemoveExistingProducts action sequenced incorrectly.Ammonite
This looks like a really good option, but I found this issue: Duplicate symbol 'WixAction:InstallExecuteSequence/RemoveExistingProducts' found.Scuta
A
10

You have 3 options when upgrading:

  1. Make the config file component permanent. This will not un-install it, and you will be able to upgrade it, but removing it will be very difficult.
  2. Use the Remember property pattern to store the config settings for the IP and PORT in the registry.
  3. As part of the install, write the config file to a temporary filename and then use a CopyFile command to create the destination file. On upgrade check for the file using a FileSearch, and if it exists then don't copy. Only issue here is if the config file has changed you won't get the updated sections.

The best option is the remember me property as this has the least problems.

Aalesund answered 24/4, 2013 at 12:7 Comment(1)
You can decouple a component from reference counting by setting a blank GUID. Then the file is just dumped on disk and will be left there on uninstall. Then you can clean it up using a custom action on uninstall, or just leave the file installed on uninstall. A reinstall will then not reinstall the file in the default case - all kinds of pitfalls. You need to know what behavior you want.Arissa
R
2

It took me a while, but here is how I solved it myself. It's probably a variation of caveman_dick's third option.

1) Add new action into UISequence to back up your current config file. You can do it with the magic of custom action and ComponentSearch to actually locate the file.

2) Restore the file later in ExecuteSequence.

<Binary Id="CustomActions.CA.dll" SourceFile="..\CustomActions\bin\$(var.Configuration)\CustomActions.CA.dll" />
<CustomAction Id="BackupConfigFile"
         Return="check"
         BinaryKey="CustomActions.CA.dll"
         DllEntry="BackupFile" />

<CustomAction Id="RestoreConfigFile"
     Return="check"
     Execute="deferred"
     Impersonate="no"
     BinaryKey="CustomActions.CA.dll"
     DllEntry="RestoreFile" />

<CustomAction Id="PropertyDelegator" 
              Property="RestoreConfigFile" 
              Value="MYTARGET=[MYTARGET];FILENAME_TO_BACKUP=[FILENAME_TO_BACKUP]" />

<Property Id="FILENAME_TO_BACKUP" Value="test.exe.config" />

<Property Id="PREVIOUS_PATH">
  <ComponentSearch Id="evSearch" Guid="{010447A6-3330-41BB-8A7A-70D08ADB35E4}" />
</Property>

and here is quick CustomAction.cs I wrote:

[CustomAction]
public static ActionResult BackupFile(Session session)
{
    try
    {
        // check out if the previous installation has our file included
        // and if it does,
        // then make copy of it.
        var previousInstallationPath = session["PREVIOUS_PATH"];
        var fileToBackup = session["FILENAME_TO_BACKUP"];

        if (!string.IsNullOrEmpty(previousInstallationPath) && !string.IsNullOrEmpty(fileToBackup))
        {
            var absolutePath = Path.Combine(previousInstallationPath, fileToBackup);
            if (File.Exists(absolutePath))
            {
                var destinationPath = Path.Combine(Path.GetTempPath(),
                    string.Concat(fileToBackup, _MODIFIER));

                File.Copy(absolutePath, destinationPath);
            }
        }
    }
    catch (Exception e)
    {
        session.Log("Couldn't backup previous file: {0}", e);
    }
    return ActionResult.Success;
}

[CustomAction]
public static ActionResult RestoreFile(Session session)
{
    try
    {
        // check if our CustomAction made backup of file,
        // and if it indeed exists in temp path, then
        // we basically copy it back.
        var currentInstallationPath = session.CustomActionData["MYTARGET"];
        var fileToRestore = session.CustomActionData["FILENAME_TO_BACKUP"];
        var fileOriginalContentPath = Path.Combine(Path.GetTempPath(),
            string.Concat(fileToRestore, _MODIFIER));

        if (File.Exists(fileOriginalContentPath))
        {
            var destinationFile = Path.Combine(currentInstallationPath, fileToRestore);
            if (File.Exists(destinationFile))
                File.Delete(destinationFile);

            File.Move(fileOriginalContentPath, destinationFile);
        }
    }
    catch (Exception e)
    {
        session.Log("Couldn't restore previous file: {0}", e);
    }
    return ActionResult.Success;
}

to actually define sequences:

<InstallUISequence>
  <Custom Action="BackupConfigFile" After="AppSearch"></Custom>
</InstallUISequence>

<InstallExecuteSequence>
  <Custom Action="PropertyDelegator" Before="RestoreConfigFile" />
  <Custom Action="RestoreConfigFile" After="InstallFiles"></Custom>
</InstallExecuteSequence>

haven't tested it thoroughly, but seems to do the job for now. Caveat: Temp folder might change?!

Alternatively there is this one that I found from Internet, but haven't tested it.

            <!-- Support Upgrading the Product -->

            <Upgrade Id="{B0FB80ED-249E-4946-87A2-08A5BCA36E7E}">

                  <UpgradeVersion Minimum="$(var.Version)"
OnlyDetect="yes" Property="NEWERVERSIONDETECTED" />

                  <UpgradeVersion Minimum="0.0.0"
Maximum="$(var.Version)" IncludeMinimum="yes" 

                                          IncludeMaximum="no"
Property="OLDERVERSIONBEINGUPGRADED" />

            </Upgrade>

            <Property Id="OLDERVERSIONBEINGUPGRADED" Secure="yes" />



            <!-- Action to save and Restore the Config-File on reinstall
-->

            <!-- We're using CAQuietExec to prevent DOS-Boxes from
popping up -->

            <CustomAction Id="SetQtCmdLineCopy" Property="QtExecCmdLine"
Value="&quot;[SystemFolder]cmd.exe&quot; /c copy
&quot;[INSTALLDIR]MyApp.exe.config&quot;
&quot;[INSTALLDIR]config.bak&quot;" />

            <CustomAction Id="QtCmdCopy" BinaryKey="WixCA"
DllEntry="CAQuietExec" Execute="immediate" />

            <CustomAction Id="SetQtCmdLineRestore"
Property="QtCmdRestore" Value="&quot;[SystemFolder]cmd.exe&quot; /c move
/Y &quot;[INSTALLDIR]config.bak&quot;
&quot;[INSTALLDIR]MyApp.exe.config&quot;" />

            <CustomAction Id="QtCmdRestore" Execute="commit"
BinaryKey="WixCA" DllEntry="CAQuietExec" />



            <!-- These actions will run only for a major upgrade -->

            <InstallExecuteSequence>

                  <Custom Action="SetQtCmdLineCopy"
After="InstallInitialize"> NOT (OLDERVERSIONBEINGUPGRADED = "")</Custom>

                  <Custom Action="QtCmdCopy"
After="SetQtCmdLineCopy">NOT (OLDERVERSIONBEINGUPGRADED = "")</Custom>

                  <Custom Action="SetQtCmdLineRestore"
Before="InstallFinalize">NOT (OLDERVERSIONBEINGUPGRADED = "")</Custom>

                  <Custom Action="QtCmdRestore"
After="SetQtCmdLineRestore">NOT (OLDERVERSIONBEINGUPGRADED =
"")</Custom>

            </InstallExecuteSequence>
Rabb answered 22/10, 2013 at 16:13 Comment(0)
P
1

There is another option, but it may not be applicable to your scenario - it all depends on who is initially running your installer...

If your app is downloaded over the web for example, then we usually go with caveman_dick's remember property pattern.

However, we have a couple of suites of products that are always installed by our own installation staff who visit a clients site. In this instance, simply do not include the config file in the installer at all!

Put simply - if the installer doesn't know about a file, then it won't uninstall it!

In this case you have the option of your installation team creating and configuring the config file, or your app creating it when it doesn't exist, and asking the user for the values.

As stated this won't be an option in some scenarios, but it works fine for ours.

Precursor answered 20/8, 2014 at 9:40 Comment(0)
K
1

Add Schedule="afterInstallExecuteAgain" in the MajorUpgrade

<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." Schedule="afterInstallExecuteAgain" />

It work for me

Knobloch answered 10/4, 2019 at 12:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.