Removing files when uninstalling WiX

WixInstallationWindows InstallerCustom Action

Wix Problem Overview


When uninstalling my application, I'd like to configure the Wix setup to remove all the files that were added after the original installation. It seems like the uninstaller removes only the directories and files that were originally installed from the MSI file and it leaves everything else that was added later in the application folder. In another words, I'd like to purge the directory when uninstalling. How do I do that?

Wix Solutions


Solution 1 - Wix

Use RemoveFile element with On="uninstall". Here's an example:

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

Update > It didn't work 100%. It removed the files, however none of the additional directories - > the ones created after the installation - were removed. Any thoughts on that? – pribeiro

Unfortunately Windows Installer doesn't support deleting directories with subdirectories. In this case you have to resort to custom action. Or, if you know what subfolders are, create a bunch of RemoveFolder and RemoveFile elements.

Solution 2 - Wix

Use RemoveFolderEx element from Util extension in WiX.
With this approach, all the subdirectories are also removed (as opposed to using RemoveFile element directly). This element adds temporary rows to RemoveFile and RemoveFolder table in the MSI database.

Solution 3 - Wix

To do this, I simply created a custom action to be called on uninstall.

The WiX code will look like this:

<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>

The code for the OnBeforeUninstall method in InstallerCustomActions.DLL will look like this (in 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

Solution 4 - Wix

Here's a variation on @tronda's suggestion. I'm deleting a file "install.log" that gets created by another Custom Action, during Uninstall:

<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>

As far as I understand, I can't use "RemoveFile" because this file is created after the installation, and is not part of a Component Group.

Solution 5 - Wix

This would be a more complete answer for @Pavel suggestion, for me it's working 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>

And, under Product element:

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

This approach set a registry value with the desired path of the folder to be deleted on uninstall. At the end, both INSTALLFOLDER and registry folder are removed from the system. Note that the path to the registry can be at other hive and other locations.

Solution 6 - Wix

Not an WIX expert, but could a possible (simpler?) solution to this be to run the Quiet Execution Custom Action which is part of the built in extensions of WIX?

Could run the rmdir MS DOS command with the /S and /Q options.

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

And the custom action doing the job is simple:

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

Then you'll have to modify the InstallExecuteSequence as documented many places.

Update: Had issues with this approach. Ended up making a custom task instead, but still considers this a viable solution, but without getting the details to work.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionpribeiroView Question on Stackoverflow
Solution 1 - WixPavel ChuchuvaView Answer on Stackoverflow
Solution 2 - WixAlexey IvanovView Answer on Stackoverflow
Solution 3 - WixFriend Of GeorgeView Answer on Stackoverflow
Solution 4 - WixPierreView Answer on Stackoverflow
Solution 5 - WixEliView Answer on Stackoverflow
Solution 6 - WixtrondaView Answer on Stackoverflow