Как добавить пользовательское действие WiX, которое происходит только при удалении (через MSI)?

161

Я хотел бы изменить установщик MSI (созданный через WiX ), чтобы удалить весь каталог при удалении.

Я понимаю параметры RemoveFileи RemoveFolderв WiX, но они недостаточно надежны, чтобы рекурсивно удалить всю папку, содержимое которой было создано после установки.

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

Это мой первый раз, когда я использую WiX, и я все еще изучаю пользовательские действия . Что будет основным примером настраиваемого действия, которое будет запускать пакетный скрипт при удалении?

Сообщество
источник

Ответы:

188

РЕДАКТИРОВАТЬ : Возможно, посмотрите на ответ в настоящее время непосредственно ниже .


Эта тема долгое время была головной болью. Я наконец-то понял. В Интернете есть несколько решений, но ни одно из них не работает. И, конечно, нет документации. Таким образом, в таблице ниже есть несколько свойств, которые предлагается использовать, и значения, которые они имеют для различных сценариев установки:

альтернативный текст

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

<Custom Action='CA_ID' Before='other_CA_ID'>
        (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>

И это сработало!

nelsonjchen
источник
25
Верны ли значения в этой таблице? Зачем вам нужно добавить REMOVE = "ALL"? NOT UPGRADINGPRODUCTCODE имеет значение true только для удаления (согласно диаграмме), поэтому (NOT UPGRADINGPRODUCTCODE) AND (REMOVE = "ALL") также будет иметь значение true только для удаления. REMOVE = "ALL" кажется ненужным.
Тодд Ропог
2
Я согласен с @ToddRopog - пример и таблица истинности, похоже, не согласны. Это действительно правильно?
Тим Лонг
19
Таблица правды немного ошибочна. NOT UPGRADINGPRODUCTCODE верно и для первой установки
Нил
2
Общие условия: alekdavis.blogspot.ru/2013/05/…
KindDragon
1
Пожалуйста, подтвердите: Установлено и Установлено - это разные вещи, только Установлено устанавливается Установщиком Windows. Я не думаю, что УСТАНОВЛЕННЫЙ работает.
Миха Виденманн
140

Есть много проблем с ответом yaluna , также имена свойств чувствительны к регистру, Installedэто правильное написание ( INSTALLEDне будет работать). Таблица выше должна была быть такой:

введите описание изображения здесь

Также при условии полного восстановления и удаления фактические значения свойств могут быть:

введите описание изображения здесь

Документация по синтаксису выражений WiX гласит:

В этих выражениях вы можете использовать имена свойств (помните, что они чувствительны к регистру).

Свойства задокументированы в Руководстве по установке Windows (например, Установлено )

РЕДАКТИРОВАТЬ: небольшая коррекция к первой таблице; очевидно, «удаление» может произойти и с REMOVEсамим собой True.

ahmd0
источник
3
УДАЛИТЬ также, кажется, установлен для Изменения
szx
2
Колонка «Обновление», это во время последовательности удаления старой версии или последовательности установки новой версии?
Ник Уэйли
1
@NickWhaley: я давно не изучал этот вопрос, но я считаю, что опция «Обновить» доступна только при установке версии, которая больше уже установленной.
ahmd0
1
@ ahmd0, ну конечно. Но в RemoveExistingProducts есть вложенная установка, которая имеет совершенно другой набор свойств. Это то, что находится в вашей колонке «Обновление». Остальная часть обновления идентична столбцу «Установка».
Ник Уэйли
1
@NickWhaley: параметр REMOVE будет действительным для основных обновлений, т. Е. С 1.0.0 до 2.0.0, а не с 1.0.0 до 1.1.0, во время выполнения программы удаления предыдущей версии. Чтобы запустить настраиваемое действие во время крупного обновления в установке новых версий, вам нужно сослаться на ActionProperty, определенный в таблице MSI обновления для обновления этой версии. symantec.com/connect/articles/msi-upgrade-overview msdn.microsoft.com/en-us/library/aa372379%28v=vs.85%29.aspx
Chaoix
48

Вы можете сделать это с помощью специального действия. Вы можете добавить ссылку на свое пользовательское действие в разделе <InstallExecuteSequence>:

<InstallExecuteSequence>
...
  <Custom Action="FileCleaner" After='InstallFinalize'>
          Installed AND NOT UPGRADINGPRODUCTCODE</Custom>

Тогда вам также нужно будет определить свое действие в разделе <Product>:

<Product> 
...
  <CustomAction Id='FileCleaner' BinaryKey='FileCleanerEXE' 
                ExeCommand='' Return='asyncNoWait'  />

Где FileCleanerEXE - это двоичный файл (в моем случае это небольшая программа на С ++, выполняющая пользовательское действие), которая также определяется в разделе <Product>:

<Product> 
...
  <Binary Id="FileCleanerEXE" SourceFile="path\to\fileCleaner.exe" />

Реальная хитрость в этом заключается в Installed AND NOT UPGRADINGPRODUCTCODEусловии настраиваемого действия, при котором ваше действие будет запускаться при каждом обновлении (поскольку обновление действительно является деинсталляцией, а затем переустановите). Что, если вы удаляете файлы, вероятно, не хотите, чтобы вы хотели во время обновления.

С другой стороны: я рекомендую решить проблему с использованием чего-то вроде программы на C ++ для выполнения действия вместо пакетного скрипта из-за его мощности и контроля - и вы можете предотвратить мигание окна «cmd prompt» во время Ваш установщик работает.

csexton
источник
3
25 голосов, но не принятый ответ. Добро пожаловать в мир установщиков! :)
Кристофер Пэйнтер
4
Это на самом деле не работает. Когда вы захотите выполнить файл fileCleaner.exe, который установлен в вашей собственной папке установки, это будет проблемой "курица и яйцо": CustomActionбудет выполнено "After = 'InstallFinalize'". На этом этапе все файлы удаляются из папки установки. Также файл Cleaner.exe. Таким образом, вы не можете выполнить его через CustomAction. Этот ответ просто неправильный. Мне интересно про 42 голоса!
Симон
40

Самая большая проблема с пакетным скриптом - обработка отката, когда пользователь нажимает кнопку отмены (или что-то идет не так во время вашей установки). Правильный способ обработки этого сценария - создать CustomAction, который добавляет временные строки в таблицу RemoveFiles. Таким образом, установщик Windows обрабатывает случаи отката для вас. Это безумно проще, когда вы видите решение.

В любом случае, чтобы действие выполнялось только во время удаления, добавьте элемент Condition с помощью:

REMOVE ~= "ALL"

~ = говорит, что сравнивать без учета регистра (хотя я думаю, что ALL всегда uppercaesd). См. Документацию MSI SDK о синтаксисе условий для получения дополнительной информации.

PS: Никогда не было случая, чтобы я сел и подумал: «О, пакетный файл был бы хорошим решением в установочном пакете». На самом деле, поиск установочного пакета, в котором есть пакетный файл, только побудил бы меня вернуть продукт за возмещение.

Роб Меншинг
источник
Я собирался использовать пакетный скрипт, а затем увидел раздел PS. Спасибо за то, что спасли меня :) У меня работает Remove ~ = "ALL".
ArNumb
12

Вот набор свойств, которые я сделал, которые более интуитивно понятны, чем встроенные. Условия основаны на таблице истинности, представленной выше ahmd0.

<!-- truth table for installer varables (install vs uninstall vs repair vs upgrade) https://stackoverflow.com/a/17608049/1721136 -->
 <SetProperty Id="_INSTALL"   After="FindRelatedProducts" Value="1"><![CDATA[Installed="" AND PREVIOUSVERSIONSINSTALLED=""]]></SetProperty>
 <SetProperty Id="_UNINSTALL" After="FindRelatedProducts" Value="1"><![CDATA[PREVIOUSVERSIONSINSTALLED="" AND REMOVE="ALL"]]></SetProperty>
 <SetProperty Id="_CHANGE"    After="FindRelatedProducts" Value="1"><![CDATA[Installed<>"" AND REINSTALL="" AND PREVIOUSVERSIONSINSTALLED<>"" AND REMOVE=""]]></SetProperty>
 <SetProperty Id="_REPAIR"    After="FindRelatedProducts" Value="1"><![CDATA[REINSTALL<>""]]></SetProperty>
 <SetProperty Id="_UPGRADE"   After="FindRelatedProducts" Value="1"><![CDATA[PREVIOUSVERSIONSINSTALLED<>"" ]]></SetProperty>

Вот пример использования:

  <Custom Action="CaptureExistingLocalSettingsValues" After="InstallInitialize">NOT _UNINSTALL</Custom>
  <Custom Action="GetConfigXmlToPersistFromCmdLineArgs" After="InstallInitialize">_INSTALL OR _UPGRADE</Custom>
  <Custom Action="ForgetProperties" Before="InstallFinalize">_UNINSTALL OR _UPGRADE</Custom>
  <Custom Action="SetInstallCustomConfigSettingsArgs" Before="InstallCustomConfigSettings">NOT _UNINSTALL</Custom>
  <Custom Action="InstallCustomConfigSettings" Before="InstallFinalize">NOT _UNINSTALL</Custom>

Вопросы:

  • UPGRADINGPRODUCTCODE устанавливается во время действия RemoveExistingProducts, поэтому любые пользовательские действия, которые вы выполняете ранее, не будут знать, что это обновление https://docs.microsoft.com/en-us/windows/desktop/Msi/upgradingproductcode
Билл Тарбелл
источник
Это отличное решение. Не забудьте также рассмотреть условия PATCH и MSIPATCHREMOVE.
Гарет Джакс
В своей таблице истинности вы имели в виду использовать PREVIOUSVERSIONSINSTALLED вместо UPGRADINGPRODUCTCODE, который используется ahmd0? Я не вижу ссылок на PREVIOUSVERSIONSINSTALLED на странице справки по свойствам MSI ( docs.microsoft.com/en-us/windows/win32/msi/property-reference ).
Патрик
Некоторые из предикатов для ваших свойств не учитывают все строки таблицы ahmd0 (Установлено, REINSTALL, UPGRADINGPRODUCTCODE и REMOVE). Не могли бы вы объяснить, почему?
Патрик
0

Я использовал Custom Action, отдельно закодированный в C ++ DLL, и использовал DLL для вызова соответствующей функции при деинсталляции, используя этот синтаксис:

<CustomAction Id="Uninstall" BinaryKey="Dll_Name" 
              DllEntry="Function_Name" Execute="deferred" />

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

Sid
источник