问题
I need installer to remove old install dir (if exists), just before installer starts copy new files. This folder contains some files and subfolders generated during program usage and they are not included in installer. Because of this, I've created custom action to do this.
So, some code. First, custom action code (nothing special there):
[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 wix code (it defines custom actions call):
<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's the problem? As you can see, actionRemoveOldDatabase should be trigerred before installer starts to copy new files (with paremeters already set by SetRemoveOldDatabaseParameters). So - only old files should be deleted - but this does not happen. If I do things this way, action actionRemoveOldDatabase, installation dir will be deleted after installer copy new files to it. So - all new files copied by installer will be deleted.
I don't undestand why? How to delete only old, already existing folder and why my custom action deletes all files copied?
[edit] It seems I already know the reason. In this case, Install Dir is in use (probably windows installer locks it) and it's released after installation ends. Custom action will wait until folder is released and then, removes it. Unfortunatelly, it's too late - folder already contains new files.
Do you know any workaround?
回答1:
The RemoveFile element is designed to do exactly this. You use this to teach MSI to remove application data it didn't install. The advantage is during a rollback the files will be put back in place.
You can also use the RemoveFolder element to delete the entire directory. Generally the concept is to * the file removal and specify the folder also. This isn't recursive so you need to do this for any subdirectories that might have got created also.
Writing custom actions is just reinventing the wheel and increases installer fragility. It should only be used when the subdirectories cannot be known in advance. In that situation the ideal story is to use temp rows in MSI to dynamically emit the rows into the MSI at install time and let MSI handle thea actual delete. This allows the rollback functionality to still work.
Here is a really simple version of what that would look like. It could be improved by making it data driven from a custom table instead of constant strings 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();
}
}
来源:https://stackoverflow.com/questions/12492769/wix-remove-old-program-folder-before-install