wix - удалить старую папку с программой перед установкой

Мне нужно, чтобы установщик удалил старый установочный каталог (если он существует), непосредственно перед тем, как установщик начнет копировать новые файлы. Эта папка содержит некоторые файлы и подпапки, созданные во время использования программы, и они не включены в программу установки. Из-за этого я создал специальное действие для этого.

Итак, немного кода. Во-первых, код пользовательского действия (ничего особенного):

[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 (он определяет вызов пользовательских действий):

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

В чем проблема? Как видите, actionRemoveOldDatabase должен запускаться до того, как установщик начнет копировать новые файлы (параметры уже установлены с помощью SetRemoveOldDatabaseParameters). Итак - удалять нужно только старые файлы - но этого не происходит. Если я сделаю что-то таким образом, действие actionRemoveOldDatabase, каталог установки будет удален после того, как установщик скопирует в него новые файлы. Таким образом, все новые файлы, скопированные установщиком, будут удалены.

Я не понимаю, почему? Как удалить только старую, уже существующую папку и почему мое пользовательское действие удаляет все скопированные файлы?

[править] Кажется, я уже знаю причину. В этом случае Install Dir используется (вероятно, установщик Windows блокирует его) и освобождается после завершения установки. Пользовательское действие будет ждать, пока папка будет освобождена, а затем удалит ее. К сожалению, уже поздно - папка уже содержит новые файлы.

Вы знаете какой-нибудь обходной путь?


person user1209216    schedule 19.09.2012    source источник
comment
У меня была аналогичная проблема. Я обошел это, используя разные install.dir для каждой версии, добавляя имя каталога к номеру версии, например. «Установить_V2.03».   -  person Martin James    schedule 19.09.2012


Ответы (1)


Элемент RemoveFile предназначен именно для этого. Вы используете это, чтобы научить MSI удалять данные приложений, которые он не устанавливал. Преимущество в том, что при откате файлы будут возвращены на место.

Вы также можете использовать элемент RemoveFolder для удаления всего каталога. Обычно концепция заключается в * удалении файла и указании папки. Это не рекурсивно, поэтому вам нужно сделать это для любых подкаталогов, которые также могли быть созданы.

Написание пользовательских действий просто изобретает велосипед и увеличивает хрупкость установщика. Его следует использовать только тогда, когда подкаталоги не могут быть известны заранее. В этой ситуации идеально было бы использовать временные строки в MSI для динамической отправки строк в MSI во время установки и позволить MSI обрабатывать фактическое удаление. Это позволяет функции отката по-прежнему работать.

Вот действительно простая версия того, как это будет выглядеть. Это можно было бы улучшить, сделав управление данными из пользовательской таблицы вместо постоянных строк для 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(); 
        }
    }
person Christopher Painter    schedule 19.09.2012
comment
Хорошо, но что, если у меня есть несколько папок и подпапок. И я не знаю, какие файлы и какие подкаталоги создаются во время использования программы - и мне нужно удалить весь установочный каталог, не заботясь о его содержимом (потому что его содержимое неизвестно) - могу ли я использовать RemoveFile или RemoveFolder для этого ? - person user1209216; 20.09.2012
comment
Вы пишете пользовательское действие C#/DTF, которое получает список подкаталогов и для каждого из них создает временную строку в MSI. Затем, когда срабатывает стандартное действие, оно все очищает. Что-то вроде этого, но с другой таблицей SQL: blog.deploymentengineering.com/2008/05/ - person Christopher Painter; 20.09.2012
comment
Вы имеете в виду пользовательское действие, вызываемое во время удаления - оно перечисляет все оставшиеся файлы / каталоги для удаления и передачи куда-то извлеченных данных - после этого стандартное действие удаления удалит эти файлы? Кажется, это не так просто, есть пример? Какую таблицу sql мне использовать? - person user1209216; 20.09.2012
comment
Да, сложно сделать все правильно. Обновлен некоторым образцом кода. - person Christopher Painter; 20.09.2012
comment
Спасибо, я попробую это. Это странно - такая простая вещь и такая сложная, я совсем запутался с msi :/ - person user1209216; 21.09.2012
comment
MSI — это другая парадигма программирования, к которой вы, вероятно, привыкли. Это декларативный язык программирования, а не императивный язык программирования. Он предназначен для управления данными (в стиле 1990-х годов - таблицы базы данных) и предназначен для транзакций (никогда не оставляйте машину в промежуточном состоянии). - person Christopher Painter; 21.09.2012
comment
WiX — это просто абстракция. Процесс компиляции преобразует элементы в табличные данные. Суть в том, что вы фокусируетесь на том, что делать, а не на том, как это делать. То есть до тех пор, пока модель не поддерживает то, что вам нужно, вы расширяете ее с помощью пользовательских действий, которые следуют той же философии. (Идеально) - person Christopher Painter; 21.09.2012