2

インストーラーが新しいファイルのコピーを開始する直前に、古いインストール ディレクトリ (存在する場合) を削除するインストーラーが必要です。このフォルダーには、プログラムの使用中に生成されたいくつかのファイルとサブフォルダーが含まれており、インストーラーには含まれていません。このため、これを行うカスタム アクションを作成しました。

それで、いくつかのコード。まず、カスタム アクション コード (特別なものはありません):

[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 インストーラーによってロックされます)、インストールの終了後に解放されます。カスタム アクションは、フォルダが解放されるまで待機してから削除します。残念ながら、手遅れです - フォルダーには既に新しいファイルが含まれています。

回避策を知っていますか?

4

1 に答える 1

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(); 
        }
    }
于 2012-09-19T13:09:26.203 に答える