Wix - delete the old program folder before installation

I need to install the installer to remove the old installation directory (if one exists) before installing the installer for new files. This folder contains some files and subfolders created during the use of the program, and they are not included in the installation program. Because of this, I created my own action for this.

So, some code. Firstly, a custom action code (nothing special):

[CustomAction] public static ActionResult RemoveOldDatabase(Session session) { bool removeDatabase = session.CustomActionData["RemoveDatabase"] == "true"; string installDir = session.CustomActionData["InstallDir"]; if (removeDatabase) { try { Directory.Delete(installDir, true); } catch (Exception ex) { session.Log(ex.StackTrace); } } return ActionResult.Success; } 

And the wix code (it defines the invocation of user actions):

 <CustomAction Id="actionCheckServerName" BinaryKey="actionBinary" DllEntry="CheckServerName" Execute="immediate" Return="check" /> <CustomAction Id="actionInstall" BinaryKey="actionBinary" DllEntry="Install" Execute="deferred" HideTarget="no" Impersonate ="no" Return="check"/> <CustomAction Id="actionUninstall" BinaryKey="actionBinary" DllEntry="Uninstall" Execute="deferred" HideTarget="no" Impersonate ="no" Return="check"/> <CustomAction Id="actionRemoveOldDatabase" BinaryKey="actionBinary" DllEntry="RemoveOldDatabase" Execute="deferred" HideTarget="no" Impersonate ="no" Return="ignore"/> <CustomAction Id="actionGetNetworkComputers" BinaryKey="actionBinary" DllEntry="GetNetworkComputers" Execute="immediate" Return="check"/> <CustomAction Id="SetInstallParameters" Return="check" Property="actionInstall" Value="InstallDir=[INSTALLDIR];ServerName=[SERVER_LIST];InstallMode=[SETUP_MODE];Single=[single];RemoveDatabase=[REMOVE_DATABASE]" /> <CustomAction Id="SetUninstallParameters" Return="check" Property="actionUninstsall" Value="UnInstallDir=[INSTALLDIR];ServerName=[SERVER_LIST];UnInstallMode=[INSTALL_MODE]" /> <CustomAction Id="SetRemoveOldDatabaseParameters" Return="check" Property="actionRemoveOldDatabase" Value="InstallDir=[INSTALLDIR];RemoveDatabase=[REMOVE_DATABASE]" /> <InstallExecuteSequence> <Custom Action='AlreadyUpdated' After='FindRelatedProducts'>SELFFOUND</Custom> <Custom Action='NoDowngrade' After='FindRelatedProducts'>NEWERFOUND</Custom> <Custom Action="SetRemoveOldDatabaseParameters" Before="ProcessComponents"/> <Custom Action="actionRemoveOldDatabase" After="SetRemoveOldDatabaseParameters">NOT Installed AND NOT UPGRADINGPRODUCTCODE</Custom> <Custom Action="SetInstallParameters" Before="actionInstall"/> <Custom Action="SetUninstallParameters" Before="RemoveFiles">Installed AND NOT REINSTALL AND NOT UPGRADINGPRODUCTCODE</Custom> <Custom Action="actionInstall" Before="InstallFinalize">NOT Installed AND NOT UPGRADINGPRODUCTCODE</Custom> <Custom Action="actionUninstall" After="SetUninstallParameters">Installed AND NOT REINSTALL AND NOT UPGRADINGPRODUCTCODE</Custom> </InstallExecuteSequence> 

What is the problem? As you can see, actionRemoveOldDatabase must be a trigger before the installer starts copying new files (with parameters already set by SetRemoveOldDatabaseParameters). So - only old files should be deleted - but this does not happen. If I do this, the actionRemoveOldDatabase action, the installation directory will be deleted after the installer copies the new files to it. So - all new files copied by the installer will be deleted .

I do not understand why? How to delete only an old, already existing folder and why does my user action delete all files copied?

[edit] I think I already know the reason. In this case, Install Dir is used (perhaps the Windows installer blocks it), and it is freed after the installation is completed. A custom action will wait for the folder to be released, and then delete it. Unfortunately, this is too late - the folder already contains new files.

Do you know a workaround?

+1
source share
1 answer

The RemoveFile element is for this. You use this to teach MSI to delete application data that it did not install. The advantage is rollback, files will be returned to the place.

You can also use the RemoveFolder element to remove the entire directory. Typically, the concept is to * delete the file and also indicates the folder. This is not recursive, so you need to do this for any subdirectories that could be created as well.

Writing custom actions is just reinventing the wheel and increasing installer fragility. It should be used only if the subdirectories cannot be known in advance. In this situation, the ideal story is to use temporary lines in MSI to dynamically emit lines in MSI during installation and let MSI handle the actual deletion. This allows you to work with rollback.

Here is a really simple version of how it will look. This can be improved by passing data from the user table instead of constant rows for ComponentID and DirectoryID.

  public class RecursiveDeleteCustomAction { [CustomAction] public static ActionResult RecursiveDeleteCosting(Session session) { // SOMECOMPONENTID is the Id attribute of a component in your install that you want to use to trigger this happening const string ComponentID = "SOMECOMPONENTID"; // SOMEDIRECTORYID would likely be INSTALLDIR or INSTALLLOCATION depending on your MSI const string DirectoryID = "SOMEDIRECTORYID"; var result = ActionResult.Success; int index = 1; try { string installLocation = session[DirectoryID]; session.Log("Directory to clean is {0}", installLocation); // Author rows for root directory // * means all files // null means the directory itself var fields = new object[] { "CLEANROOTFILES", ComponentID, "*", DirectoryID, 3 }; InsertRecord(session, "RemoveFile", fields); fields = new object[] { "CLEANROOTDIRECTORY", ComponentID, "", DirectoryID, 3 }; InsertRecord(session, "RemoveFile", fields); if( Directory.Exists(installLocation)) { foreach (string directory in Directory.GetDirectories(installLocation, "*", SearchOption.AllDirectories)) { session.Log("Processing Subdirectory {0}", directory); string key = string.Format("CLEANSUBFILES{0}", index); string key2 = string.Format("CLEANSUBDIRECTORY{0}", index); session[key] = directory; fields = new object[] { key, ComponentID, "*", key, 3 }; InsertRecord(session, "RemoveFile", fields); fields = new object[] { key2, ComponentID, "", key, 3 }; InsertRecord(session, "RemoveFile", fields); index++; } } } catch (Exception ex) { session.Log(ex.Message); result = ActionResult.Failure; } return result; } private static void InsertRecord(Session session, string tableName, Object[] objects) { Database db = session.Database; string sqlInsertSring = db.Tables[tableName].SqlInsertString + " TEMPORARY"; session.Log("SqlInsertString is {0}", sqlInsertSring); View view = db.OpenView(sqlInsertSring); view.Execute(new Record(objects)); view.Close(); } } 
0
source

Source: https://habr.com/ru/post/1200872/


All Articles