インストーラのカスタム動作のクセ

# 概要
Windowsのインストーラ作成にVisual Studioの拡張機能**Microsoft Visual Studio Installer Projects**(以降、Installer Projectsと呼ぶ)を使用している方も多いだろう。
インストール後にInstaller Projectsの標準機能では実行できない複雑な処理をする場合は**カスタム動作**を別途定義してイベント発生時に呼び出す場合がある。
カスタム動作について調べている中でいくつかクセを発見したのでメモしておく。

# テスト用セットアッププロジェクト
Installer Projectsのセットアッププロジェクト(`TestSetup`)からカスタム動作用DLL(`CustomAction1`、`CustomAction2`)を呼び出している。
カスタム動作の基本的な説明は[この神記事](https://dobon.net/vb/dotnet/deployment/customaction.html)を見ればたちどころにわかる。
カスタム動作の実行順序は[この神記事](http://www.devcity.net/PrintArticle.aspx?ArticleID=339)が非常に詳しい。

``` C#:CustomAction1.cs
using System.Windows.Forms;
using System.ComponentModel;
using System.Collections;

namespace CustomAction1
{
    [RunInstaller(true)]
    public class CustomAction1 : System.Configuration.Install.Installer
    {
        public override void Install(IDictionary stateSaver)
        {
            base.Install(stateSaver);
            MessageBox.Show(GetType().Name + ": Install");
        }

        public override void Commit(IDictionary savedState)
        {
            base.Commit(savedState);
            MessageBox.Show(GetType().Name + ": Commit");
        }

        public override void Rollback(IDictionary savedState)
        {
            base.Rollback(savedState);
            MessageBox.Show(GetType().Name + ": Rollback");
        }

        public override void Uninstall(IDictionary savedState)
        {
            base.Uninstall(savedState);
            MessageBox.Show(GetType().Name + ": Uninstall");
        }
    }
}
```

``` C#:CustomAction2.cs
using System.Windows.Forms;
using System.ComponentModel;
using System.Collections;

namespace CustomAction2
{
    [RunInstaller(true)]
    public class CustomAction2 : System.Configuration.Install.Installer
    {
        public override void Install(IDictionary stateSaver)
        {
            base.Install(stateSaver);
            MessageBox.Show(GetType().Name + ": Install");
        }

        public override void Commit(IDictionary savedState)
        {
            base.Commit(savedState);
            MessageBox.Show(GetType().Name + ": Commit");
        }

        public override void Rollback(IDictionary savedState)
        {
            base.Rollback(savedState);
            MessageBox.Show(GetType().Name + ": Rollback");
        }

        public override void Uninstall(IDictionary savedState)
        {
            base.Uninstall(savedState);
            MessageBox.Show(GetType().Name + ": Uninstall");
        }
    }
}
```

セットアッププロジェクトのカスタム動作
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57609/56445929-5692-3c26-896a-3537b8d59c4f.png)

https://github.com/TheParkSider/TestSetup

# RemovePreviousVersions=TrueのときはUninstallのカスタム動作が呼ばれない？
セットアッププロジェクトの`RemovePreviousVersions`を`True`にすると新しいバージョンのソフトをインストールする際(**バージョンアップ時**)に古いバージョンのソフトを自動的にアンインストールしてくれる。
これをしないと複数の異なるバージョンが1つのPC内に同居する状態になってしまう場合があり、不都合が生じる可能性がある。
ここで注意すべきなのは古いバージョンのアンインストール時にカスタム動作の`Uninstall`は**実行されない**ことである。

`RemovePreviousVersions=True`でバージョンアップするなら、古いバージョンを削除したときにして、
`Uninstall`のカスタム動作が呼ばれるかも？と思ったが、
実際は新しいバージョンの`Install`、`Commit`のカスタム動作が実行されるだけである。
まあ現状の仕様のほうが多くの人にメリットがあるように思う。

# インストール開始前に実行できるカスタム動作はない？
実はカスタム動作として使用できるのは`Install`、`Commit`、`Rollback`、`Uninstall`だけではなく、[この記事](http://www.devcity.net/PrintArticle.aspx?ArticleID=339)で紹介されている`OnBeforeInstall`や`OnAfterInstall`などもある。

その中でも`OnBeforeInstall`はインストール先の環境のチェックやインストール可否の制御に使用できそうな気がするが、
`OnBeforeInstall`の実行タイミングは**ファイルの配置がすべて完了した後**であって、インストールのプロセスがかなり進んだ後なのだ。
さらに困ったことにインストールを否認した場合の**エラーメッセージが非常に不親切**なのである。

たとえば`CustomAction1.cs`に以下のコードを追加して人為的に例外を返してやる。
これによってインストールが途中で中断されるはずだ。
``` C#
protected override void OnBeforeInstall(IDictionary savedState)
{
    base.OnBeforeInstall(savedState);
    MessageBox.Show(GetType().Name + ": OnBefore");
    throw new System.Configuration.Install.InstallException("Errorだあああああ");
}
```

インストールが開始されてファイルが配置される
![スクリーンショット 2022-05-02 163116.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57609/389f855b-bc7f-aee9-8e90-2765ade9f610.png)

ここで`OnBeforeInstall`が実行されて例外発生を示すメッセージが表示されるが、なんだか不親切な嫌な感じ。
![d.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57609/7f8859e3-4e9c-b1ed-94cc-dde7dc9aeff8.png)

OKを押しても立て続けに怪しいエラーが表示される。
![3241.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57609/73be5cdd-53aa-c15c-0980-d123717bb09e.png)

最後はこれ。今回カスタム動作用のDLLを2つ使っているため表示されるものと思われる。
![aaa.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57609/abb3e0fe-860b-5437-a849-f8b7828401fe.png)

もっとこう…あるだろう！！という感じだ。
インストーラの「起動条件」でもインストーラ実行可否の制御はできると思うが、凝ったことをやるのには不向きである。
いいやり方があれば誰か教えてください。