简体   繁体   English

wix-安装前删除旧程序文件夹

[英]wix - remove old program folder before install

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): 和wix代码(它定义了自定义操作调用):

<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). 正如你所看到的,actionRemoveOldDatabase应该安装程序开始复制新文件(已经SetRemoveOldDatabaseParameters设置paremeters)之前trigerred。 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. 如果以这种方式进行操作,则安装程序将新文件复制到其中后,动作actionRemoveOldDatabase ,安装目录将被删除。 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. 在这种情况下,正在使用Install Dir(可能是Windows Installer锁定了它),并且在安装结束后将其释放。 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? 您知道任何解决方法吗?

The RemoveFile element is designed to do exactly this. RemoveFile元素旨在完全做到这一点。 You use this to teach MSI to remove application data it didn't install. 您可以使用它来教导MSI删除尚未安装的应用程序数据。 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. 您还可以使用RemoveFolder元素删除整个目录。 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. 在那种情况下,理想的情况是在安装时使用MSI中的临时行将这些行动态发送到MSI中,然后让MSI处理实际的删除操作。 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. 可以通过使它从自定义表而不是由ComponentID和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(); 
        }
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM