wix-安装前删除旧程序文件夹 [英] wix - remove old program folder before install

查看:104
本文介绍了wix-安装前删除旧程序文件夹的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在安装程序开始复制新文件之前,我需要安装程序删除旧的安装目录(如果存在)。该文件夹包含一些在程序使用过程中生成的文件和子文件夹,它们不包含在安装程序中。因此,我创建了自定义操作来执行此操作。

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;
        }

然后是wix代码(它定义了自定义操作调用):

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>

出什么问题了?如您所见,在安装程序开始复制新文件(使用SetRemoveOldDatabaseParameters已设置的参数)之前,应先触发 actionRemoveOldDatabase 。因此-仅删除旧文件-但这不会发生。如果我以这种方式执行操作 actionRemoveOldDatabase ,则安装程序将新文件复制到其中后,安装目录将被删除。因此-安装程序复制的所有新文件都将被删除

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]
看来我已经知道原因了。在这种情况下,正在使用Install Dir(可能是Windows Installer锁定了它),并且在安装结束后将其释放。自定义操作将一直等到文件夹释放后再将其删除。不幸的是,为时已晚-文件夹已经包含新文件。

[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?

推荐答案

RemoveFile元素旨在做到这一点。您可以用它教MSI删除尚未安装的应用程序数据。优点是在回滚期间文件将被放回原处。

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.

您还可以使用RemoveFolder元素删除整个目录。通常,此概念是*删除文件并指定文件夹。这不是递归的,因此您需要对可能也会创建的任何子目录执行此操作。

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.

编写自定义操作只是在重新发明轮子并增加安装程序的脆弱性。仅在无法预先知道子目录时才应使用它。在那种情况下,理想的情况是在安装时使用MSI中的临时行将这些行动态地发送到MSI中,然后让MSI处理实际的删除操作。

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.

这里是一个看起来非常简单的版本。可以通过使它从自定义表驱动数据,而不是从ComponentID和DirectoryID的常量字符串驱动数据来进行改进。

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(); 
        }
    }

这篇关于wix-安装前删除旧程序文件夹的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆