Как сохранить файл конфигурации при серьезном обновлении в wix v3.8?

#wix #windows-installer #upgrade #wix3.8

#wix #Обновление #сохранить

Вопрос:

Я хочу сохранить файл конфигурации, когда установщик msi выполнит серьезное обновление. Для файла конфигурации я вношу изменения при установке. Код выглядит следующим образом:

 <Component Id="MODIFYCONFIG" Guid="6A1D7762-B707-4084-A01F-6F936CC159CE" Win64="yes">
    <File Id="Application.config" Name="Application.config" Vital="yes" KeyPath="yes" Source="ResourceApplication.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>
  

Но при серьезном обновлении файл не сохраняется. Как я могу сохранить измененный файл?

Ответ №1:

Это решило проблему для меня … файл конфигурации сохраняется при незначительном / значительном обновлении и полностью удаляется при удалении.

Ссылка: Аарон Стебнер: как сохранить пользовательские файлы во время крупного обновления установщика Windows

РЕДАКТИРОВАТЬ: обобщенная информация со связанной страницы…

  1. Каждый файл конфигурации должен иметь свой собственный компонент, где файл конфигурации помечен как ключевой путь компонента.Установщик Windows будет использовать неверсированную логику замены файлов.
  2. Добавьте действие «RemoveExistingProducts» после действия «InstallFiles».Новые версии всех компонентов устанавливаются перед удалением старого MSI. Когда это будет сделано в этой последовательности, количество ссылок на компоненты увеличится до 2, но файлы конфигурации не будут заменены, если они не будут изменены (из-за неверсионной логики замены файлов). При удалении старого MSI количество ссылок будет уменьшено до 1, но файлы не будут удалены, поскольку количество ссылок не равно 0.

Комментарии:

1. Да, это работает и является лучшим вариантом. Вам необходимо соблюдать все правила компонентов, и ссылки или файлы могут отсутствовать после серьезного обновления. Вы также можете сохранить файл конфигурации, установив его компонент постоянным, а затем использовать пользовательское действие, выполняемое только при удалении, которое не является частью серьезного обновления, для очистки при выполнении реального удаления. Условие UPGRADINGPRODUCTCODE можно использовать для определения того, выполняется ли серьезное обновление. Просто оставляю это для справки для тех, кто может столкнуться с той же проблемой.

2. Ссылка имеет очень хорошую ценность, но я думаю, было бы неплохо обобщить сообщение на случай, если оно когда-нибудь исчезнет.

3. Часть 2 кажется неправильной. При добавлении действия «RemoveExistingProducts» после «InstallFiles» появляется сообщение об ошибке: какое-то действие попадает между InstallInitialize и RemoveExistingProducts.

4. Это не работает с MajorUpgrade, поскольку тогда вы не сможете самостоятельно добавлять удаляемые продукты.

5. @markmnl Вы не можете добавить RemoveExistingProducts, но вы можете установить атрибут Schedule для элемента MajorUpgrade, который будет выполнять ту же работу.

Ответ №2:

При обновлении у вас есть 3 варианта:

  1. Сделайте компонент файла конфигурации постоянным. Это не приведет к его удалению, и вы сможете его обновить, но удалить его будет очень сложно.
  2. Используйте шаблон свойства запоминания, чтобы сохранить настройки конфигурации для IP и ПОРТА в реестре.
  3. В рамках установки запишите файл конфигурации во временное имя файла, а затем используйте команду CopyFile для создания файла назначения. При обновлении проверьте наличие файла с помощью поиска файлов, и если он существует, то не копируйте. Единственная проблема здесь в том, что если файл конфигурации изменился, вы не получите обновленные разделы.

Лучшим вариантом является свойство remember me, поскольку оно имеет наименьшие проблемы.

Комментарии:

1. Вы можете отделить компонент от подсчета ссылок, установив пустой идентификатор GUID. Затем файл просто сбрасывается на диск и остается там при удалении. Затем вы можете очистить его, используя пользовательское действие при удалении, или просто оставить файл установленным при удалении. Переустановка не приведет к переустановке файла в случае по умолчанию — всевозможные подводные камни. Вам нужно знать, какое поведение вы хотите.

Ответ №3:

Мне потребовалось некоторое время, но вот как я решил это сам. Вероятно, это вариант третьего варианта caveman_dick.

1) Добавьте новое действие в UISequence для резервного копирования вашего текущего файла конфигурации. Вы можете сделать это с помощью магии пользовательских действий и ComponentSearch, чтобы фактически найти файл.

2) Восстановите файл позже в ExecuteSequence.

 <Binary Id="CustomActions.CA.dll" SourceFile="..CustomActionsbin$(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>
  

и вот быстрый CustomAction.cs, который я написал:

 [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) amp;amp; !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;
}
  

для фактического определения последовательностей:

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

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

не тестировал его тщательно, но, похоже, пока справляется со своей задачей. Предостережение: временная папка может измениться ?!

В качестве альтернативы есть этот, который я нашел в Интернете, но не тестировал его.

             <!-- 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="amp;quot;[SystemFolder]cmd.exeamp;quot; /c copy
amp;quot;[INSTALLDIR]MyApp.exe.configamp;quot;
amp;quot;[INSTALLDIR]config.bakamp;quot;" />

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

            <CustomAction Id="SetQtCmdLineRestore"
Property="QtCmdRestore" Value="amp;quot;[SystemFolder]cmd.exeamp;quot; /c move
/Y amp;quot;[INSTALLDIR]config.bakamp;quot;
amp;quot;[INSTALLDIR]MyApp.exe.configamp;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>
  

Ответ №4:

Есть еще один вариант, но он может быть неприменим к вашему сценарию — все зависит от того, кто изначально запускает ваш установщик…

Например, если ваше приложение загружается через Интернет, мы обычно используем шаблон запоминания свойств caveman_dick.

Однако у нас есть несколько наборов продуктов, которые всегда устанавливаются нашим собственным персоналом по установке, который посещает сайт клиента. В этом случае просто не включайте файл конфигурации в установщик вообще!

Проще говоря, если установщик не знает о файле, он не будет его удалять!

В этом случае у вас есть возможность, чтобы ваша команда установки создала и настроила файл конфигурации, или ваше приложение создало его, когда он не существует, и запросило у пользователя значения.

Как указано, это не будет вариантом в некоторых сценариях, но для нас это работает нормально.

Ответ №5:

Добавьте Schedule="afterInstallExecuteAgain" в MajorUpgrade

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

Это работает для меня