86

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?

Stein Åsmul
  • 34,628
  • 23
  • 78
  • 140
pribeiro
  • 1,161
  • 2
  • 11
  • 13

6 Answers6

85

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.

Pavel Chuchuva
  • 21,289
  • 9
  • 93
  • 110
  • 1
    Thank you Pavel. However 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 Oct 22 '08 at 18:21
  • Oh, neither the files under those directories were deleted. – pribeiro Oct 22 '08 at 18:22
  • 1
    When you will keep files (config files for example) in 'MyAppFolder' on Major Upgrades, you will get problems with this approach. All files will be removed by an Upgrade. – Simon Apr 05 '16 at 10:47
  • This will cause problem with shared components: they will be removed regardless of other usages still active. – Andriy K Jun 30 '16 at 13:14
  • 1
    Is there possible with your code to create a Directory in MyAppFolder ? When I add a directory after `` I have an compilation failed `Found orphaned Component 'MyAppFolder'.` – A.Pissicat Aug 30 '16 at 07:53
  • @A.Pissicat Please ask a separate question for your problem and include the full error text. – Pavel Chuchuva Sep 01 '16 at 01:10
  • 2
    @PhilipRego See https://msdn.microsoft.com/en-us/library/windows/desktop/aa367992.aspx for CommonAppDataFolder documenation. – Pavel Chuchuva Mar 08 '17 at 23:50
  • Thanks to Pavel, I added more complete answer (100% success) [here](https://stackoverflow.com/a/33736356/2069294) – Eli Jan 24 '19 at 07:48
  • I had a problem removing top folder when unistalling simply because I was in that directory in a cmd prompt. How do you handle this case? – Rod Apr 02 '20 at 00:52
  • this solution no longer works: `Found orphaned Component 'MyAppFolder'. If this is a Product, every Component must have at least one parent Feature. To include a Component in a Module, you must include it directly as a Component element of the Module element or indirectly via ComponentRef, ComponentGroup, or ComponentGroupRef elements.` – Alex G May 29 '20 at 17:53
31

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.

Community
  • 1
  • 1
Alexey Ivanov
  • 10,757
  • 4
  • 36
  • 58
  • 2
    Warning: When using RemoveFolderEx on="uninstall", it also removes the folder on an upgrade (Wix 3.9). Same behaviour on `RemoveFile` and `RemoveFolder`. If you want to keep files on an upgrade, you cannot use all these approaches. – Simon Apr 07 '16 at 08:01
  • @Simon what approach would you suggest if you wanted to keep the files on an upgrade? – Bijington Sep 07 '16 at 20:57
  • @Bijington: When you want to keep specific files in your installation folder on an upgrade, then use an custom action which executes custom code (e.g. c# written HandleSetup.exe). The custom code delivers your files on install, keeping your files on upgrade and removes the files on uninstall. – Simon Sep 08 '16 at 07:08
  • @Simon thanks, I may have to look into that approach although this one is currently working: http://stackoverflow.com/a/21383113/32348 – Bijington Sep 09 '16 at 06:21
13

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
Stein Åsmul
  • 34,628
  • 23
  • 78
  • 140
Friend Of George
  • 478
  • 1
  • 6
  • 16
11

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.

Pierre
  • 3,470
  • 1
  • 32
  • 33
8

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.

Community
  • 1
  • 1
Eli
  • 3,616
  • 1
  • 21
  • 35
6

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.

tronda
  • 3,774
  • 4
  • 30
  • 55
  • I like this option bar the fact that you are including the cmd.exe in the installer. Surely every machine will have it, you just need to use a DirectorySearch to find it! :) – caveman_dick Jul 08 '11 at 09:49
  • 5
    Don't do this. 1) you are embedding `cmd.exe` into your installer. 2) You are making changes to the system during script generation 3) There is no rollback option 4) Doesn't deal with locked files correctly – Nick Whaley Nov 17 '15 at 19:24
  • I have doubts that it's legal to distribute a file from the Windows installation. It's also unclear if it will work on the target system which may run a different version of Windows. – Paul B. Jul 30 '20 at 07:56
  • No files has been distributed. It uses the files installed on the OS. – tronda Oct 06 '20 at 12:42