セットアッププロジェクトのUIとカスタム動作の連動

# 概要

[Visual Studio 2013 でセットアッププロジェクトが復活](http://www.infoq.com/jp/news/2014/05/vs2013_installer_project)したとのこと
標準では InstallShield LE が使えるようになっているが、こいつは **フル機能を使いたいか。使わせてやる。金を払え** という感じなので、カスタマイズが容易なセットアッププロジェクトを使ってみることに
その中で、今回はインストーラーでの設定項目の追加と、カスタム動作との連動についてまとめる

# 準備

1. [配布サイト](http://visualstudiogallery.msdn.microsoft.com/9abe329c-9bba-44a1-be59-0fbf6151054d) よりインストーラーをダウンロードし、セットアッププロジェクトをインストールする
2. ソリューションにデフォルト設定でのセットアッププロジェクトを追加する
 - `プライマリ出力 from [メインのプロジェクト]` を含める

# インストーラーのカスタマイズ

今回は例として、インストール中に用意された権限からひとつを選択し、その値をどこか（DBやレジストリ等）に保存してアプリケーションを制御したいと仮定する
以下でその方法を実現するための方法を順を追って説明する

## インストール中に設定する項目を追加する

まずは権限を設定する画面を作らなくてはいけない
しかしセットアッププロジェクトで作成されるインストーラーはデフォルトだとインストールフォルダを選択する画面しかない
ここではインストール中の設定項目を追加する方法を紹介する

1. `セットアッププロジェクト右クリック` > `View` > `ユーザーインターフェイス` を選択
2. Start, Progress, End の中から追加したい場所を選び、右クリック > `ダイアログの追加` を選択
3. 設定に合ったダイアログを選択
 - 今回はオプションボタン（3ボタン）を選択
4. 追加されたダイアログの表示順番や、表示内容（プロパティ）を変更する
 - 今回はインストールフォルダ選択のあとに表示することにし、プロパティは以下のように変更

![setup4.png](https://qiita-image-store.s3.amazonaws.com/0/48120/0cbb1fe8-701f-4b08-6d35-dff707c50e17.png)

なお、これだけだとあくまで **表示される画面が増えただけ** である  

## インストール中に処理を割りこませる

画面を追加しただけであとはインストーラーがよろしくやってくれるわけではない
その処理を作成し、かつ **インストール中に実行される** ようにしなくてはならない
このようなインストール中に割り込ませる処理を **カスタム動作** （またはカスタムアクション）という
ここではカスタム動作の追加方法を紹介する

### メインプロジェクトの処理

1. **インストールしたいプロジェクト内** に以下のようなクラスを追加し、リビルドする

```csharp
using System.Collections;

namespace TestApplication
{
    [System.ComponentModel.RunInstaller(true)]
    public class CustomAction : System.Configuration.Install.Installer
    {
        public override void Install(IDictionary stateSaver)
        {
            base.Install(stateSaver);

            // インストール時に実行したい処理内容
            System.Windows.Forms.MessageBox.Show("テスト");
        }
    }
}
```

### セットアッププロジェクトの処理

1. `セットアッププロジェクト右クリック` > `View` > `カスタム動作` を選択
2. 表示された項目から `Install` を右クリックし、 `カスタム動作の追加` を選択
3. `Application Folder` を選択し、OKを押す
4. `プライマリ出力 from ○○(Active)` を選択し、OKを押す
5. セットアッププロジェクトをリビルドする

インストーラーを実行すると、以下のようにインストール中にカスタム動作が動いていることがわかる

![setup5.png](https://qiita-image-store.s3.amazonaws.com/0/48120/c7a39e52-e4ec-0534-eea9-47b7e3446d49.png)

## UIとカスタム動作を連動させる

選択された権限を保存するためには、追加されたUIで選択された値をカスタム動作に渡してやらなければならない
また、カスタム動作側も渡された値を適切に取得しなければならない
ここではその方法を紹介する

### カスタム動作に値を渡す方法

1. `セットアッププロジェクト右クリック` > `View` > `カスタム動作` を選択
2. `プライマリ出力 from ○○(Active)` を右クリックし、 `プロパティウィンドウ` を選択
3. `CustomActionData` の部分に `/auth=[AUTH]` と入力し、保存
 - `auth` は名前であり、カスタム動作側で値を取得するときに参照する
 - `AUTH` はUIを追加した時に設定したプロパティの `ButtonProperty` の値

これにより、 `auth=選択された権限の値` として、カスタム動作に送られることになる
なお、常に一定の値をカスタム動作に送りたい場合は、 `/name=value` のように記入する
複数の値を送りたい場合はスペースで区切り、 `/auth=[AUTH] /name=value` のように記入する

### カスタム動作で値を受け取る方法

次にカスタム動作側ですが、先ほど作ったカスダム動作を以下のように変更する

```csharp
using System.Collections;

namespace TestApplication
{
    [System.ComponentModel.RunInstaller(true)]
    public class CustomAction : System.Configuration.Install.Installer
    {
        public override void Install(IDictionary stateSaver)
        {
            base.Install(stateSaver);

            int auth = int.Parse(this.Context.Parameters["auth"]);
            // データの保存処理
        }
    }
}
```

`this.Context.Parameters["auth"]` の部分で渡された値を取得している
あとはその値をDBなりレジストリなりに保存してやればいい
これでインストール中に権限を保存できるのだが……

## 初回インストール時のみカスタム動作を動かす

ところでこの画面を見てくれ。こいつをどう思う？

![setup6.png](https://qiita-image-store.s3.amazonaws.com/0/48120/8e833c7b-2b5e-2e89-4acb-3cda6129dbf0.png)

これはすでにアプリケーションがインストールされている状態で、再度インストーラーを実行した時に表示される画面
注目してもらいたいのは **修復** という選択肢
この修復を実行すると再度インスールが実行されるのだが、このとき権限の選択画面は表示されないで、いきなりインストールの進行状況を表す画面になる
でもこれはインストールのため、 **カスタム動作はちゃんと実行される**
ではカスタム動作へ渡される値はどうなっているかというと、 **DefaultValue の値** が渡される
つまり今のままだと、修復を実行すると選択の余地なくデフォルトの権限に変わってしまうということ
これを回避するためには

1. カスタム動作側ですでに保存されている場合は更新しないなどの処理を埋め込む
2. 修復時にはカスタム動作が実行しないように設定する

1 の場合は特に設定を変える必要はない。C#ガンバレ
ここでは 2 の設定方法を紹介する

1. `セットアッププロジェクト右クリック` > `View` > `カスタム動作` を選択
2. `プライマリ出力 from ○○(Active)` を右クリックし、 `プロパティウィンドウ` を選択
3. `Condition` の部分に `NOT Installed` と入力し、保存

こう設定することで、Not Installed の時のみ、つまり初回インストールの時のみカスタム動作が起動し、修復の時はスキップされるようになる

# 余談

ここまで読んで気づいた方もいると思うが、実はカスタム動作中に自作した権限を選択するフォームを出力するような処理を書けばそれでも問題なく動いたりする