我想修改MSI安装程序(通过WiX创建)以在卸载时删除整个目录。

我了解WiX中的RemoveFileRemoveFolder选项,但是这些选项不足以递归删除整个目录安装后创建内容的文件夹。

我注意到类似的堆栈溢出问题,在卸载WiX时删除文件,但是我想知道是否可以更简单地使用批处理脚本调用来删除文件该文件夹。

这是我第一次使用WiX,但仍然遇到了自定义操作的困扰。将在卸载时运行批处理脚本的自定义操作的基本示例是什么?

#1 楼

编辑:也许在下面立即查看当前的答案。


长期以来,这个话题一直让人头疼。我终于想通了。
网上有一些解决方案,但没有一个真正有效。当然也没有文档。
因此,在下表中,建议使用一些属性,以及它们在各种安装方案中的值:



因此,就我而言,我想要一个仅在卸载时运行的CA,而不是升级,维修或修改的CA。根据上面的表格,我必须使用

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


,并且有效!

评论


该图表中的值是否正确?为什么需要添加REMOVE =“ ALL”? NOT UPGRADINGPRODUCTCODE仅适用于卸载(根据图表),因此(NOT UPGRADINGPRODUCTCODE)AND(REMOVE =“ ALL”)也仅适用于卸载。 REMOVE =“ ALL”似乎不必要。

– Todd Ropog
2010年5月14日在19:02

我同意@ToddRopog-示例和真值表似乎不同意。真的对吗?

–提姆·朗
11年4月29日在19:33

真值表有些错误。 NOT UPGRADINGPRODUCTCODE也适用于首次安装

–尼尔
2011年11月24日15:56

常见条件:alekdavis.blogspot.ru/2013/05/…

– KinDragon
2014年8月11日19:14在

请确认:“已安装”和“已安装”是不同的东西,只有“已安装”由Windows Installer设置。我认为“安装”无效。

– Micha Wiedenmann
16年9月2日在13:23

#2 楼

yaluna的答案存在多个问题,属性名称也区分大小写,Installed是正确的拼写(INSTALLED无法使用)。
上表应该是这样的:



还假设完全修复并卸载了属性的实际值可能是:



WiX Expression Syntax文档说:


在这些表达式中,您可以使用属性名称(请记住,它们区分大小写)。


属性已在Windows Installer指南(例如已安装)中进行了说明。 )

编辑:对第一个表进行小的校正;显然,仅REMOVETrue也会发生“卸载”。

评论


删除似乎也设置为更改

– szx
2014年5月21日在7:34

“升级”列是在旧版本的卸载过程中还是在新版本的安装过程中?

– Nick Whaley
2014年12月30日21:43

@NickWhaley:我已经有一段时间没有研究它了,但是我相信只有当安装的版本大于已经安装的版本时,“升级”选项才会出现。

–ahmd0
15年1月16日在18:34

@ ahmd0,当然可以。但是,在RemoveExistingProducts中发生的嵌套安装具有完全不同的属性集。这就是您的“升级”列中的内容。其余升级与“安装”列相同。

– Nick Whaley
2015年2月3日在21:07

@NickWhaley:在执行先前版本的卸载程序的过程中,“ REMOVE”选项适用于“主要升级”,即1.0.0到2.0.0,而不是1.0.0到1.1.0。要在新版本的重大升级期间运行自定义操作,您需要引用该版本升级的“升级MSI”表中定义的ActionProperty。 symantec.com/connect/articles/msi-upgrade-overview msdn.microsoft.com/en-us/library/aa372379%28v=vs.85%29.aspx

–Chaoix
2015年2月25日在17:46

#3 楼

您可以通过自定义操作来执行此操作。您可以在<InstallExecuteSequence>下向您的自定义操作添加引用:

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


那么您还将必须在<Product>下定义您的操作:

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


其中FileCleanerEXE是二进制文件(在我的情况下,一个小c ++程序执行自定义操作),该程序也在<Product>下定义:

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


真正的窍门是自定义操作中的Installed AND NOT UPGRADINGPRODUCTCODE条件,而您的操作将在每次升级时运行(因为升级实际上是卸载,然后重新安装)。如果要删除文件,那可能是升级期间不想要的。

在旁注:我建议您避免使用C ++程序而不是批处理脚本来执行操作的麻烦由于它提供了强大的功能和控制功能,因此您可以防止安装程序运行时“ cmd提示”窗口闪烁。

评论


25票赞成,但没有被接受的答案。欢迎来到安装者世界! :)

–克里斯托弗画家
13年2月12日在12:15

这实际上是行不通的。当您要执行安装在您自己的安装文件夹中的fileCleaner.exe时,这将是鸡与蛋的问题:CustomAction将执行“ After ='InstallFinalize'”。此时,所有文件将从“安装”文件夹中删除。也是fileCleaner.exe。因此,您将无法通过CustomAction执行它。这个答案是完全错误的。我想知道42次投票!

–西蒙
16-4-7在12:33



@Simon通过使用不是已安装文件的DLL而不是EXE可以轻松防止所有这些。

–将
8月5日23:26

此答案的真正问题是,假设上述@ ahmd0的答案中的表格正确,那么在“更改和修复”过程中,Installed AND NOT UPGRADINGPRODUCTCODE的评估结果也为true。解决方案:使用(删除=“全部”)并且不升级产品代码

–将
8月6日0:15

#4 楼

批处理脚本的最大问题是在用户单击“取消”时处理回滚(或在安装过程中出现问题)。解决此情况的正确方法是创建一个CustomAction,该操作将临时行添加到RemoveFiles表中。这样,Windows Installer即可为您处理回退案例。

无论如何,只有在卸载期间才执行操作,请添加条件元素:

REMOVE ~= "ALL"


〜=表示不区分大小写(即使我认为ALL始终是大写)。有关更多信息,请参见MSI SDK文档中有关条件语法的更多信息。

PS:从来没有出现过让我坐下来思考的情况:“哦,批处理文件将是安装软件包中的一个很好的解决方案。 ”实际上,找到其中包含批处理文件的安装软件包只会鼓励我退回产品以获得退款。

评论


我正要使用批处理脚本,然后查看PS部分。感谢您的救助:)删除〜=“ ALL”对我有用。

– ArNumb
19年5月10日在6:59

#5 楼

这是我制作的一组属性,使用起来比内置的东西更直观。条件基于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 / upgradeingproductcode



评论


这是一个很好的解决方案。记住还要考虑PATCH和MSIPATCHREMOVE条件。

–Garet Jax
19年11月5日在15:39

在您的真值表中,您是要使用PREVIOUSVERSIONSINSTALTAL而不是ahmd0使用的UPGRADINGPRODUCTCODE吗?我没有在MSI属性参考页(docs.microsoft.com/zh-cn/windows/win32/msi/property-reference)上看到对PREVIOUSVERSIONSINSTALLED的任何参考。

–帕特里克(Patrick)
19年11月6日在1:07



属性的某些谓词没有考虑ahmd0表中的所有行(已安装,REINSTALL,UPGRADINGPRODUCTCODE和REMOVE)。你能解释为什么吗?

–帕特里克(Patrick)
19年11月6日,1:18

#6 楼

我使用在C ++ DLL中单独编码的“自定义操作”,并使用DLL在使用以下语法卸载时调用适当的函数:

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


使用上面的代码块,我能够在卸载时运行C ++ DLL中定义的任何函数。
仅供参考,我的卸载函数具有有关清除当前用户数据和注册表项的代码。