Before I begin, let me say that I read almost all the related questions here on SO and did not find a solution. Thus, the question poses the same problem as before:
How to delete temporary folders and misc files that are created when the application is running using WiX?
So far I have come up with the following ways to do this:
Using CustomAction (written, for example, in C #), which simply deletes all files and folders (this works fine, but this workaround seems invalid to me, since there is no support for MSI rollback ).
Using the util: RemoveFolderEx tag from WixUtilExtension . I could not do the job.
Using CustomAction (again written in C #), which will populate MSI DB tables ( Directory and RemoveFile ) immediately before UnInstall is executed. This will force MSI to correctly and in its own way uninstall all of the listed files and folders (theoretically, this should do this) using the RemoveFiles action by default.
I focused on the third method and asked you to help a little here.
A few notes about the layout of the MSI application I built works great with ProgramFiles, Shortcuts, Registry and all that. But during the installation, I put some configuration files in the C:\ProgramData\MyApp\ folder (they are also deleted without any problems).
But while the application is running, it creates additional files and dirs in C:\ProgramData\MyApp\ , which are used to update the application after the release of the new version. Suppose the user closes the application in the middle of the update process and wants to remove the application. Here is what we currently have in the C:\ProgramData\MyApp folder:
C: \ ProgramData \ MyApp \
C: \ ProgramData \ MyApp \ Temp \
C: \ ProgramData \ MyApp \ Temp \ tool.exe
C: \ ProgramData \ MyApp \ Temp \ somelib.dll
C: \ ProgramData \ MyApp \ Temp \ <UniqueFolderNameBasedOnGUID> \ someliba.dll
C: \ ProgramData \ MyApp \ Temp \ <UniqueFolderNameBasedOnGUID <\ somelibb.dll
At the end of the uninstall procedure, I do not see the folder C:\ProgramData\MyApp\ . And I can see this if temp dirs / files is not created.
Please note: I do not know the names of folders and files placed in the C:\ProgramData\MyApp\Temp\ folder, since the final folder name is automatically generated using the GUID.
Let me focus on the most important parts of the project and show you what I have done so far to complete the task (please remember, I chose the third way: through CustomAction ).
Main MyApp.wxs file
<Product Id=...> ... <Binary Id="RemoveUpdatesAction.CA.dll" src="RemoveUpdatesAction.CA.dll" /> <CustomAction Id="RemoveUpdatesAction" Return="check" Execute="immediate" BinaryKey="RemoveUpdatesAction.CA.dll" DllEntry="RemoveUpdatesAction" /> ... <InstallExecuteSequence> <Custom Action='RemoveUpdatesAction' Before='RemoveFiles'> (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") </Custom> </InstallExecuteSequence> ... </Product>
And later in the file I define the Folder Layout directory for ProgramData strong>:
<Fragment> <Directory Id="TARGETDIR" Name="SourceDir"> .... <Directory Id="ProgramFilesFolder"> <Directory Id="APPINSTALLFOLDER" Name="MyApp" /> </Directory> .... <Directory Id="CommonAppDataFolder"> <Directory Id="SETTINGSINSTALLFOLDER" Name="MyApp" /> </Directory> .... </Directory> </Fragment> ... a lot of different stuff here ... <Fragment> <Component Id="AddConfigurationFilesFolder" ...> ... ... </Fragment>
So far so good.
Now RemoveUpdatesAction.cs file , which contains a custom action:
public class CustomActions { [CustomAction] public static ActionResult RemoveUpdatesAction(Session session) { try { // Provide unique IDs int indexFile = 1; int indexDir = 1; // Begin work session.Log("Begin RemoveUpdatesAction"); // Bind to the component that for sure will be uninstalled during UnInstall action // You can see this component mentioned above const string componentId = "AddConfigurationFilesFolder"; // Get '..\{ProgramData}\MyApp' folder // This property (SETTINGSINSTALLFOLDER) is mentioned too string appDataFolder = session["SETTINGSINSTALLFOLDER"]; // Populate RemoveFile table in MSI database with all files // created in '..\{ProgramData}\MyApp\*.*' folder - pls see notes at the beginning if (!Directory.Exists(appDataFolder)) { session.Log("End RemoveUpdatesAction"); return ActionResult.Success; } foreach (var directory in Directory.GetDirectories(appDataFolder, "*", SearchOption.AllDirectories)) { session.Log("Processing Subdirectory {0}", directory); foreach (var file in Directory.EnumerateFiles(directory)) { session.Log("Processing file {0}", file); string keyFile = string.Format("CLEANFILE_{0}", indexFile); // Set values for columns in RemoveFile table: // {1}: FileKey => just unique ID for the row // {2}: Component_ => reference to a component existed in Component table // In our case it is already mentioned 'AddConfigurationFilesFolder' // {3}: FileName => localizable name of the file to be removed (with ext.) // {4}: DirProperty => reference to a full dir path // {5}: InstallMode => 3 means remove on Install/Remove stage var fieldsForFiles = new object[] { keyFile, componentId, Path.GetFileName(file), directory, 3 }; // The following files will be processed: // 1. '..\ProgramData\MyApp\Temp\tool.exe' // 2. '..\ProgramData\MyApp\Temp\somelib.dll' // 3. '..\ProgramData\MyApp\Temp\<UniqueFolderNameBasedOnGUID>\someliba.dll' // 4. '..\ProgramData\MyApp\Temp\<UniqueFolderNameBasedOnGUID>\somelibb.dll' InsertRecord(session, "RemoveFile", fieldsForFiles); indexFile++; } string keyDir = string.Format("CLEANDIR_{0}", indexDir); // Empty quotes mean we we want to delete the folder itself var fieldsForDir = new object[] { keyDir, componentId, "", directory, 3 }; // The following paths will be processed: // 1. '..\ProgramData\MyApp\Temp\' // 2. '..\ProgramData\MyApp\Temp\<UniqueFolderNameBasedOnGUID>\' InsertRecord(session, "RemoveFile", fieldsForDir); indexDir++; } session.Log("End RemoveUpdatesAction"); return ActionResult.Success; } catch (Exception exception) { session.Log("RemoveUpdatesAction EXCEPTION:" + exception.Message); return ActionResult.Failure; } } // Took completely from another SO question, but is accoring to MSDN docs private static void InsertRecord(Session session, string tableName, Object[] objects) { Database db = session.Database; string sqlInsertSring = db.Tables[tableName].SqlInsertString + " TEMPORARY"; View view = db.OpenView(sqlInsertSring); view.Execute(new Record(objects)); view.Close(); } }
Note. The condition for deletion ( (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") ) I took from here .
This is almost all that I did, and after the Install / Uninstall cycle, I see in the msi log file and in the RemoveFile table (by listing the entries) that all these entries are actually inserted. But the files and folders in ..\ProgramData\MyApp\Temp\... remain regardless of what I do .
Can someone clarify what I'm doing wrong?
I believe the problem may be how I defined DirProperty in the Custom Action class. I put the directory path there, but I know (using Orca) that there is no record in the Directory table with a temporary folder that I found through Directory.GetDirectories .
The RemoveFile table is filled with entries that have an invalid link to the Directory table (is this correct?). I tried to manually add these detected folders to the Directory table, but failed - every time I try to get a link to the Directory table, I get an exception. What is the correct way to populate a Directory table in MSI? Does it make sense to populate the Directory table at all?
For example, if I need to put the following paths there:
..\ProgramData\MyApp\Temp\..\ProgramData\MyApp\Temp\<UniqueFolderNameBasedOnGUID>\
How can i do this?
In any case, please suggest something - any advice, advice or comments will be highly appreciated!
Thanks a lot!
MSI Uninstall Log :
MSI (s) (B4:28) [05:28:39:427]: Doing action: RemoveUpdatesAction .... MSI (s) (B4:4C) [05:28:39:450]: Invoking remote custom action. DLL: C:\WINDOWS\Installer\MSI7643.tmp, Entrypoint: RemoveUpdatesAction .... Action start 5:28:39: RemoveUpdatesAction. SFXCA: Extracting custom action to temporary directory: C:\WINDOWS\Installer\MSI7643.tmp-\ SFXCA: Binding to CLR version v4.0.30319 Calling custom action RemoveUpdatesAction!RemoveUpdatesAction.CustomActions.RemoveUpdatesAction Begin RemoveUpdatesAction Processing Subdirectory C:\ProgramData\MyApp\Temp Processing file C:\ProgramData\MyApp\Temp\somelib.dll Processing file C:\ProgramData\MyApp\Temp\tool.exe Processing Subdirectory C:\ProgramData\MyApp\Temp\48574917-4351-4d4c-a36c-381f3ceb2e56 Processing file C:\ProgramData\MyApp\Temp\48574917-4351-4d4c-a36c-381f3ceb2e56\someliba.dll Processing file C:\ProgramData\MyApp\Temp\48574917-4351-4d4c-a36c-381f3ceb2e56\somelibb.dll End RemoveUpdatesAction MSI (s) (B4:28) [05:28:39:602]: Doing action: RemoveFiles MSI (s) (B4:28) [05:28:39:602]: Note: 1: 2205 2: 3: ActionText Action ended 5:28:39: RemoveUpdatesAction. Return value 1. Action start 5:28:39: RemoveFiles. MSI (s) (B4:28) [05:28:39:607]: Note: 1: 2727 2: C:\ProgramData\MyApp\Temp MSI (s) (B4:28) [05:28:39:607]: Note: 1: 2727 2: C:\ProgramData\MyApp\Temp\48574917-4351-4d4c-a36c-381f3ceb2e56 MSI (s) (B4:28) [05:28:39:607]: Counted 2 foreign folders to be removed. MSI (s) (B4:28) [05:28:39:607]: Removing foreign folder: C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Compass Mobile\ MSI (s) (B4:28) [05:28:39:607]: Removing foreign folder: C:\ProgramData\MyApp\ MSI (s) (B4:28) [05:28:39:607]: Doing action: RemoveFolders MSI (s) (B4:28) [05:28:39:607]: Note: 1: 2205 2: 3: ActionText Action ended 5:28:39: RemoveFiles. Return value 1. Action start 5:28:39: RemoveFolders.
As you can see, I received error code 2727 . This means that there are no entries for the following folders in the Directory table:
C: \ ProgramData \ MyApp \ Temp
C: \ ProgramData \ MyApp \ Temp \ 48574917-4351-4d4c-a36c-381f3ceb2e56
So, maybe my suggestion is correct?
Links for specified tags, MSI tables, etc .:
Creating custom WiX actions in C # and transfer options
MSI DB RemoveFile table description
MSI DB Database Table Description
RemoveFiles Action
CustomAction Element for WiX