卸载 WiX 时删除文件

Posted

技术标签:

【中文标题】卸载 WiX 时删除文件【英文标题】:Removing files when uninstalling WiX 【发布时间】:2010-09-16 18:59:55 【问题描述】:

卸载我的应用程序时,我想配置Wix 设置以删除在原始安装之后添加的所有文件。似乎卸载程序仅删除了最初从 MSI 文件安装的目录和文件,并将后来添加的所有其他内容保留在应用程序文件夹中。换句话说,我想在卸载时清除目录。我该怎么做?

【问题讨论】:

【参考方案1】:

将 RemoveFile element 与 On="uninstall" 一起使用。这是一个例子:

<Directory Id="CommonAppDataFolder" Name="CommonAppDataFolder">
  <Directory Id="MyAppFolder" Name="My">
    <Component Id="MyAppFolder" Guid="*">
      <CreateFolder />
      <RemoveFile Id="PurgeAppFolder" Name="*.*" On="uninstall" />
    </Component>
  </Directory>
</Directory>

更新

它没有 100% 有效。它删除了文件,但是没有其他目录 - 安装后创建的那些 - 已被删除。对此有什么想法吗? – 普里贝罗

很遗憾,Windows Installer 不支持删除带有子目录的目录。在这种情况下,您必须诉诸自定义操作。或者,如果您知道子文件夹是什么,创建一堆 RemoveFolder 和 RemoveFile 元素。

【讨论】:

谢谢帕维尔。然而,它并没有 100% 有效。它删除了文件,但是没有删除任何附加目录(安装后创建的目录)。对此有什么想法吗? 哦,那些目录下的文件都没有被删除。 当您在重大升级时将文件(例如配置文件)保存在“MyAppFolder”中时,您会遇到这种方法的问题。升级将删除所有文件。 您的代码是否可以在 MyAppFolder 中创建目录?当我在&lt;/Component&gt; 之后添加目录时,我编译失败Found orphaned Component 'MyAppFolder'. @PhilipRego 有关 CommonAppDataFolder 文档,请参阅 msdn.microsoft.com/en-us/library/windows/desktop/aa367992.aspx。【参考方案2】:

在 WiX 中使用 Util 扩展中的 RemoveFolderEx 元素。 使用这种方法,所有子目录也会被删除(与using RemoveFile element directly 不同)。此元素将临时行添加到 MSI 数据库中的 RemoveFileRemoveFolder 表中。

【讨论】:

警告:在使用 RemoveFolderEx on="uninstall" 时,它还会在升级时删除文件夹(Wix 3.9)。 RemoveFileRemoveFolder 的行为相同。如果您想在升级时保留文件,则不能使用所有这些方法。 @Simon 如果您想在升级时保留文件,您会建议什么方法? @Bijington:当您想在升级时将特定文件保留在安装文件夹中时,请使用执行自定义代码的自定义操作(例如 c# 编写的 HandleSetup.exe)。自定义代码在安装时交付您的文件,在升级时保留您的文件并在卸载时删除文件。 @Simon 谢谢,我可能不得不研究这种方法,尽管这个方法目前正在工作:***.com/a/21383113/32348【参考方案3】:

为此,我只是创建了一个在卸载时调用的自定义操作。

WiX 代码如下所示:

<Binary Id="InstallUtil" src="InstallUtilLib.dll" />

<CustomAction Id="DIRCA_TARGETDIR" Return="check" Execute="firstSequence" Property="TARGETDIR" Value="[ProgramFilesFolder][Manufacturer]\[ProductName]" />
<CustomAction Id="Uninstall" BinaryKey="InstallUtil" DllEntry="ManagedInstall" Execute="deferred" />
<CustomAction Id="UninstallSetProp" Property="Uninstall" Value="/installtype=notransaction /action=uninstall /LogFile= /targetDir=&quot;[TARGETDIR]\Bin&quot; &quot;[#InstallerCustomActionsDLL]&quot; &quot;[#InstallerCustomActionsDLLCONFIG]&quot;" />

<Directory Id="BinFolder" Name="Bin" >
    <Component Id="InstallerCustomActions" Guid="*">
        <File Id="InstallerCustomActionsDLL" Name="SetupCA.dll" LongName="InstallerCustomActions.dll" src="InstallerCustomActions.dll" Vital="yes" KeyPath="yes" DiskId="1" Compressed="no" />
        <File Id="InstallerCustomActionsDLLCONFIG" Name="SetupCA.con" LongName="InstallerCustomActions.dll.Config" src="InstallerCustomActions.dll.Config" Vital="yes" DiskId="1" />
    </Component>
</Directory>

<Feature Id="Complete" Level="1" ConfigurableDirectory="TARGETDIR">
    <ComponentRef Id="InstallerCustomActions" />
</Feature>

<InstallExecuteSequence>
    <Custom Action="UninstallSetProp" After="MsiUnpublishAssemblies">$InstallerCustomActions=2</Custom>
    <Custom Action="Uninstall" After="UninstallSetProp">$InstallerCustomActions=2</Custom>
</InstallExecuteSequence>

InstallerCustomActions.DLL 中 OnBeforeUninstall 方法的代码将如下所示(在 VB 中)。

Protected Overrides Sub OnBeforeUninstall(ByVal savedState As System.Collections.IDictionary)
    MyBase.OnBeforeUninstall(savedState)

    Try
        Dim CommonAppData As String = Me.Context.Parameters("CommonAppData")
        If CommonAppData.StartsWith("\") And Not CommonAppData.StartsWith("\\") Then
            CommonAppData = "\" + CommonAppData
        End If
        Dim targetDir As String = Me.Context.Parameters("targetDir")
        If targetDir.StartsWith("\") And Not targetDir.StartsWith("\\") Then
            targetDir = "\" + targetDir
        End If

        DeleteFile("<filename.extension>", targetDir) 'delete from bin directory
        DeleteDirectory("*.*", "<DirectoryName>") 'delete any extra directories created by program
    Catch
    End Try
End Sub

Private Sub DeleteFile(ByVal searchPattern As String, ByVal deleteDir As String)
    Try
        For Each fileName As String In Directory.GetFiles(deleteDir, searchPattern)
            File.Delete(fileName)
        Next
    Catch
    End Try
End Sub

Private Sub DeleteDirectory(ByVal searchPattern As String, ByVal deleteDir As String)
    Try
        For Each dirName As String In Directory.GetDirectories(deleteDir, searchPattern)
            Directory.Delete(dirName)
        Next
    Catch
    End Try
End Sub

【讨论】:

【参考方案4】:

这是@tronda 建议的一个变体。我正在删除在卸载期间由另一个自定义操作创建的文件“install.log”:

<Product>
    <CustomAction Id="Cleanup_logfile" Directory="INSTALLFOLDER"
    ExeCommand="cmd /C &quot;del install.log&quot;"
    Execute="deferred" Return="ignore" HideTarget="no" Impersonate="no" />

    <InstallExecuteSequence>
      <Custom Action="Cleanup_logfile" Before="RemoveFiles" >
        REMOVE="ALL"
      </Custom>
    </InstallExecuteSequence>
</Product>

据我了解,我不能使用“RemoveFile”,因为该文件是在安装后创建的,并且不是组件组的一部分。

【讨论】:

我确实使用了这个解决方案,并进行了一些更改以删除整个目录:ExeCommand="cmd /C RD "[INSTALLFOLDER]" /s /q" @Dennis 如何删除 INSTALLFOLDER,在 win 10 上会被删除,但在 Windows server 2012 上不会。 很好的解决方案。谢谢! 我尝试了很多方法——人们不会认为在卸载过程中删除单个文件会那么困难。不过这对我有用 - 谢谢!【参考方案5】:

对于@Pavel 的建议,这将是一个更完整的答案,对我来说它 100% 有效:

<Fragment Id="FolderUninstall">
    <?define RegDir="SYSTEM\ControlSet001\services\[Manufacturer]:[ProductName]"?>
    <?define RegValueName="InstallDir"?>
    <Property Id="INSTALLFOLDER">
        <RegistrySearch Root="HKLM" Key="$(var.RegDir)" Type="raw" 
                  Id="APPLICATIONFOLDER_REGSEARCH" Name="$(var.RegValueName)" />
    </Property>

    <DirectoryRef Id='INSTALLFOLDER'>
        <Component Id="UninstallFolder" Guid="*">
            <CreateFolder Directory="INSTALLFOLDER"/>
            <util:RemoveFolderEx Property="INSTALLFOLDER" On="uninstall"/>
            <RemoveFolder Id="INSTALLFOLDER" On="uninstall"/>
            <RegistryValue Root="HKLM" Key="$(var.RegDir)" Name="$(var.RegValueName)" 
                    Type="string" Value="[INSTALLFOLDER]" KeyPath="yes"/>
        </Component>
    </DirectoryRef>
</Fragment>

并且,在产品元素下:

<Feature Id="Uninstall">
    <ComponentRef Id="UninstallFolder" Primary="yes"/>
</Feature>

此方法使用要在卸载时删除的文件夹的所需路径设置注册表值。 最后,从系统中删除 INSTALLFOLDER 和注册表文件夹。请注意,注册表的路径可以位于其他配置单元和其他位置。

【讨论】:

【参考方案6】:

不是 WIX 专家,但可能的(更简单?)解决方案是运行作为 WIX 内置扩展的一部分的 Quiet Execution Custom Action?

可以使用 /S 和 /Q 选项运行 rmdir MS DOS 命令。

<Binary Id="CommandPrompt" SourceFile="C:\Windows\System32\cmd.exe" />

完成这项工作的自定义操作很简单:

<CustomAction Id="DeleteFolder" BinaryKey="CommandPrompt" 
              ExeCommand='/c rmdir /S /Q "[CommonAppDataFolder]MyAppFolder\PurgeAppFolder"' 
              Execute="immediate" Return="check" />

然后您将不得不修改 InstallExecuteSequence,如许多地方所记录的那样。

更新: 这种方法有问题。最终改为制作自定义任务,但仍然认为这是一个可行的解决方案,但没有让细节发挥作用。

【讨论】:

我喜欢这个选项,因为您在安装程序中包含了 cmd.exe。当然每台机器都会有它,你只需要使用 DirectorySearch 来找到它! :) 不要这样做。 1) 您正在将cmd.exe 嵌入到您的安装程序中。 2)您在脚本生成期间对系统进行了更改 3)没有回滚选项 4)没有正确处理锁定的文件 我怀疑从 Windows 安装分发文件是否合法。还不清楚它是否可以在可能运行不同版本 Windows 的目标系统上工作。 没有文件被分发。它使用安装在操作系统上的文件。

以上是关于卸载 WiX 时删除文件的主要内容,如果未能解决你的问题,请参考以下文章

Wix - 卸载删除文件但不删除文件夹

WIX 安装程序,在卸载时,从临时目录中删除文件

卸载时未删除Windows服务(WIX 3)

Wix 自定义卸载操作 - 如何在 msi 删除文件之前运行

如何在卸载时立即生成WIX执行命令

卸载后程序文件中存在空目录