注意:セットアッププロジェクトはVisual Studio 2012からサポートされなくなりました。
セットアッププロジェクト(デプロイメントプロジェクト)のカスタム動作エディタを使って、作成するWindows Installerパッケージで使用するカスタムアクションを指定することができます。カスタムアクションとは、Windows Installerだけの機能では実現が難しい動作をEXE、DLL、スクリプト等を使って行うものです。
早速ですが、まずはカスタムアクションがどのようなものか、簡単な例を見てみましょう。ここでは、VBScriptを使用することにします。
はじめにカスタムアクションで使用するスクリプトを作成します。ここでは次のようなごく簡単なスクリプトを作成し、"action.vbs"という名前で保存します。メッセージボックスを表示し、「こんにちは」と表示するだけのスクリプトです。
MsgBox "こんにちは"
まずは、この"action.vbs"をセットアッププロジェクトに追加します。ソリューションエクスプローラでプロジェクトを右クリックし、メニューの[追加]-[ファイル]で追加できます。
次に「カスタム動作エディタ」を開き、スクリプトを追加します。カスタム動作を開くには、ソリューションエクスプローラでプロジェクトを右クリックし、メニューの[表示]-[カスタム動作]を選択します。
カスタム動作エディタには4つのノードがありますが、これらの説明は後に回し、ここでは「インストール」ノードにカスタムアクションを追加します。「インストール」ノードを右クリックし、メニューの[カスタム動作の追加]で表示される「プロジェクトから項目を選択」ダイアログで"action.vbs"を選択し、OKをクリックします。
これでカスタムアクションが追加されました。早速ビルドして試してみましょう。
作成したMSIファイルを実行すると、「(アプリケーション名)をインストールしています」というステップの途中で「こんにちは」というメッセージボックスが表示されるはずです。
上記の例では、スクリプトファイルがアプリケーションフォルダに配置されます。スクリプトファイルを配置しないようにするには、ソリューションエクスプローラで"action.vbs"を選択し、ExcludeプロパティをTrueにします。
補足:ExcludeプロパティがFalseの場合、MSIファイルのCustomActionテーブルに登録されるカスタムアクションのTypeは22となります。これに対してExcludeプロパティをTrueにすると、VBSファイルはBinaryテーブルに埋め込まれ、Custom Action Typeは6となります。
後述しますが、Installerクラスによるカスタムアクションでは、この方法はできません。
カスタム動作エディタには4つのノード「インストール」、「確定」、「ロールバック」、「アンインストール」があります。それぞれが何を意味しているかについては、ヘルプの「カスタム動作エディタ」で説明されています。
この説明を以下に引用させていただきます。
補足:さらに詳しく調べるには、MSIファイルの中身をOrcaで覗いてみる必要があるでしょう。という訳で、実際に調べてみました。
「インストール」、「確定」、「ロールバック」に追加されたカスタムアクションは、すべてInstallExecuteSequenceテーブルにConditionが「$(スクリプトファイルのコンポーネントのID)>2」(注)、Sequenceが5999(複数登録すると、5998、5997...となる)として登録されます。「$(スクリプトファイルのコンポーネントのID)>2」の"2"は"msiInstallStateAbsent"を表しており、2より大きいということは、このコンポーネントがインストールされたということを示しています。また、Sequenceが5999というのは、StartServicesの後、RegisterUserの前ということになります。詳しくは、「Suggested InstallExecuteSequence」をご覧ください。
注:スクリプトファイルを配置しない設定にしたときは、「NOT REMOVE~="ALL"」となります。つまり、完全に削除する以外のときということになります。詳しくは、「Conditional Statement Syntax」や「REMOVE Property」をご覧ください。
「アンインストール」に追加されたカスタムアクションは、InstallExecuteSequenceテーブルにConditionが「$(スクリプトファイルのコンポーネントのID)=2」(注)、Sequenceが1699(複数登録すると、1698、1697...となる)として登録されます。Sequenceが1699というのは、MsiUnpublishAssembliesの後、UnpublishComponentsの前ということになります。
注:スクリプトファイルを配置しない設定にしたときは、「REMOVE~="ALL" AND ProductState <> 1」となります。
「インストール」、「確定」、「ロールバック」の違いは、CustomActionテーブルのTypeに表れます。「インストール」、「確定」、「ロールバック」のTypeはそれぞれ、1046、1558、1302となります。これらの数字が意味しているものは、それぞれの数字から22を引いて、「Custom Action In-Script Execution Options」を調べれば分かります。
つまり、「インストール」、「確定」、「ロールバック」のカスタムアクションにはそれぞれmsidbCustomActionTypeInScript、msidbCustomActionTypeInScript + msidbCustomActionTypeCommit、msidbCustomActionTypeInScript + msidbCustomActionTypeRollbackのオプションフラッグが付いており、それぞれの意味するところは、「Deferred Execution Custom Actions」、「Commit Custom Actions」、「Rollback Custom Actions」ということになります。詳しくは、下記のリンク先をご覧ください。
上記で紹介したようなスクリプト以外に、EXEやDLLファイルをカスタムアクションに使用することもできます。EXEはマネージコードでも構いませんが(注)、DLLはアンマネージコードである必要があります。しかし、Installクラスを使用したカスタムアクションの場合は、DLLでも大丈夫です。ここからは、Installクラスを使った方法を紹介します。
注:EXEファイルをそのまま実行する場合は、InstallerClassプロパティをFalseにする必要があります。
.NET Frameworkには、「Installer クラス」というクラスが用意されており、これをカスタムアクションに使用することができます。
はじめに、簡単な例から紹介します。
まず、新しいクラスライブラリのプロジェクトを作成します。ここではセットアッププロジェクトと同じソリューション内に作成し、名前を「CustomAction」としました。このクラスの参照設定に、「System.Configuration.Install.dll」を追加します(ここではMessageBoxを使用しているため、「System.Windows.Forms.dll」も追加します)。
次に、Installerクラスの派生クラスを作成し、RunInstallerAttribute属性を追加し、Trueにします。さらに、必要に応じてInstall、Commit、Rollback、Uninstallメソッドをオーバーライドします。これらのメソッドは、それぞれ「インストール」、「確定」、「ロールバック」、「アンインストール」の時に呼び出されます。これらのメソッドでは、まずはじめに基本クラスのメソッドを呼び出します。(「インストール コンポーネントの既定のメソッドのオーバーライド」でも説明されています。)
ここでは以下のようなコードでビルドし、DLLファイル「CustomAction.dll」を作成しました。
<System.ComponentModel.RunInstaller(True)> _ Public Class Installer1 Inherits System.Configuration.Install.Installer Public Overrides Sub Install( _ ByVal stateSaver As System.Collections.IDictionary) MyBase.Install(stateSaver) System.Windows.Forms.MessageBox.Show("Install") End Sub Public Overrides Sub Commit( _ ByVal savedState As System.Collections.IDictionary) MyBase.Commit(savedState) System.Windows.Forms.MessageBox.Show("Commit") End Sub Public Overrides Sub Rollback( _ ByVal savedState As System.Collections.IDictionary) MyBase.Rollback(savedState) System.Windows.Forms.MessageBox.Show("Rollback") End Sub Public Overrides Sub Uninstall( _ ByVal savedState As System.Collections.IDictionary) MyBase.Uninstall(savedState) System.Windows.Forms.MessageBox.Show("Uninstall") End Sub End Class
[System.ComponentModel.RunInstaller(true)] public class Installer1 : System.Configuration.Install.Installer { public override void Install(System.Collections.IDictionary stateSaver) { base.Install(stateSaver); System.Windows.Forms.MessageBox.Show("Install"); } public override void Commit(System.Collections.IDictionary savedState) { base.Commit(savedState); System.Windows.Forms.MessageBox.Show("Commit"); } public override void Rollback(System.Collections.IDictionary savedState) { base.Rollback(savedState); System.Windows.Forms.MessageBox.Show("Rollback"); } public override void Uninstall(System.Collections.IDictionary savedState) { base.Uninstall(savedState); System.Windows.Forms.MessageBox.Show("Uninstall"); } }
このカスタムアクションをセットアッププロジェクトで使用するためには、先ほどと同じように、作成したDLLをセットアッププロジェクトに追加してから(ここでは、「プロジェクト出力」から、「CustomActionプロジェクトのプライマリ出力」を追加しました)、カスタム動作エディタに追加します。ここでは、4つのノードすべてに追加するために、「カスタム動作」のノードを右クリックし、メニューの[カスタム動作の追加]を選び、「CustomActionのプライマリ出力」を選択します。
Installerクラスをカスタムアクションとして使用するには、カスタム動作のInstallerClassプロパティをTrueとする必要がありますが、デフォルトでTrueのため、このままでOKです。
ビルドしてMSIファイルを作成し、実行してみましょう。VBScriptの例と同じように、インストールの最後の方で「Install」と表示され、その後「Commit」と表示されます。また、アンインストール時には、「Uninstall」と表示されます。
補足:ここではInstall、Commit、Rollback、Uninstallメソッドをすべてオーバーライドしましたが、必要なものだけで大丈夫です。
補足:ここでは、Installerクラスを独自に作成しましたが、Visual Studioでは、「コンポーネントの追加」で追加することができます。具体的には、Visual StudioのソリューションエディタでInstallerクラスを追加したいプロジェクトを右クリックし、メニューの[追加]-[コンポーネントの追加]から「新しい項目の追加」ダイアログを表示し、「インストーラクラス」を選択します。
補足:ここではカスタムアクションのためだけのDLLを作成しましたが、配布するアプリケーションにInstallerクラスの派生クラスを追加する方法もあり、MSDN等では、主にこの方法が紹介されています。
補足:この方法では、先ほどのVBScriptの例と違い、ExcludeプロパティをTrueにしてDLLファイルをMSIファイルに埋め込むことはできません。
例によってOrcaにより、作成されたMSIファイルの詳細を見てみます。
Installerクラスを使った場合、カスタム動作を一つ追加すると、CustomActionテーブルに2つの列が追加されます。「インストール」ノードに追加されたカスタム動作の場合、例えば、次のような2つの列が追加されます。
まず、Type51のカスタムアクションにより、プロパティが設定されます。次に、Type1025のカスタムアクションにより、InstallUtilのManagedInstall関数が呼び出され、この時に先に設定されたプロパティが引数として渡されます。Type1025は、Type1+msidbCustomActionTypeInScriptですので、埋め込まれたDLLを呼び出すカスタムアクションであることが分かりますが、実際にInstallUtilはBinaryテーブルに埋め込まれています。InstallUtilという名前からInstallUtil.exeが埋め込まれていると思われるかもしれませんが、ここに埋め込まれているのはInstallUtilLib.dllです。
なお、「/LogFile= "[#_A367697580D6A572E31AB7AA55DFF20A]"」の「_A367697580D6A572E31AB7AA55DFF20A」は、Installerクラスのあるファイル(ここでは、CustomAction.dll)のIDであり、先頭に「#」が付いていることから、ファイルのパスを意味していることになります。
これらのカスタムアクションは、InstallExecuteSequenceテーブルで使用されます。もちろん、Type51のカスタムアクションの後で、Type1のカスタムアクションが実行されます。
「インストーラ」ノード以外に追加されたカスタム動作でも同様の列がCustomActionテーブルに追加されます。ただし、「install」の文字列がすべて「commit」「rollback」「uninstall」に置き換わります。また、TypeはVBScriptの例で紹介したのと同じオプションフラッグが付きます。
カスタム動作にデータ(文字列)を渡すには、CustomActionDataプロパティを使います。まずは、Installerクラスを使ったときの方法から説明します。
Installerクラスを使ったカスタムアクションにデータを渡すときは、CustomActionDataプロパティに「/name=value」という形式で指定します。ここで「name」は名前で、「value」はその値を示します。複数のデータを渡す場合は、スペース文字で区切ります。つまり、「/name1=value1 /name2=value2」のようにします。valueにスペースが含まれる場合は、"value 1"のように"で囲みます。"で囲まれた文字列の最後が\となるときは、さらに\を付けて、"\\"とします。また、Windows Installerのプロパティを使うときは、[]で囲みます。
Installerクラス内でデータを取得するには、InstallContext.Parametersプロパティを使います。
次に例を示します。まずカスタム動作のCustomActionDataプロパティに
/name=value /dir="[TARGETDIR]\"
と入力します。[TARGETDIR]はインストール先のフォルダのパスを示すWindows Installerのプロパティです。[TARGETDIR]が"で囲まれているのは、[TARGETDIR]にスペースが含まれる可能性があるからで、また最後に\が付いているのは、[TARGETDIR]の最後に\が付くからです。
このデータを取得するために、InstallerクラスのInstallメソッドを次のようにオーバーライドします。
Public Overrides Sub Install( _ ByVal stateSaver As System.Collections.IDictionary) MyBase.Install(stateSaver) Dim val As String = Me.Context.Parameters("name") Dim targetdir As String = Me.Context.Parameters("dir") System.Windows.Forms.MessageBox.Show(("name = " + val + _ vbCrLf + "TARGETDIR = " + targetdir)) End Sub
public override void Install(System.Collections.IDictionary stateSaver) { base.Install(stateSaver); string val = this.Context.Parameters["name"]; string targetdir = this.Context.Parameters["dir"]; System.Windows.Forms.MessageBox.Show("name = " + val + "\nTARGETDIR = " + targetdir); }
変数valは"value"となり、targetにはインストール先のフォルダのパスが入ります。
補足:このようにCustomActionDataプロパティを指定した時、作成されるMSIファイルのCustomActionテーブルは次のようになります。つまり、CustomActionDataプロパティで指定された文字列が引数として渡されていることが分かります。
VBScriptの場合は、CustomActionDataプロパティで指定された文字列をそのままSession.Propertyで取得できます。例えば、CustomActionDataプロパティを「[TARGETDIR]」とした場合、次のようなVBScriptにより、インストール先のフォルダのパスを表示できます。
Dim val
val = Session.Property("CustomActionData")
MsgBox val
複数のデータを渡すには、独自に区切り文字を決めるしかないようです。
Installerクラスを使ったカスタムアクションでエラーを発生させて、インストールが失敗するようにするには、InstallExceptionオブジェクトをスローします。具体例として、次のようなカスタムアクションを試してみましょう。
<System.ComponentModel.RunInstaller(True)> _ Public Class Installer1 Inherits System.Configuration.Install.Installer Public Overrides Sub Install( _ ByVal stateSaver As System.Collections.IDictionary) MyBase.Install(stateSaver) Throw New System.Configuration.Install.InstallException("テストエラー") End Sub Public Overrides Sub Rollback( _ ByVal savedState As System.Collections.IDictionary) MyBase.Rollback(savedState) System.Windows.Forms.MessageBox.Show("Rollback") End Sub End Class
[System.ComponentModel.RunInstaller(true)] public class Installer1 : System.Configuration.Install.Installer { public override void Install(System.Collections.IDictionary stateSaver) { base.Install(stateSaver); throw new System.Configuration.Install.InstallException("テストエラー"); } public override void Rollback(System.Collections.IDictionary savedState) { base.Rollback(savedState); System.Windows.Forms.MessageBox.Show("Rollback"); } }
作成されるMSIファイルを実行すると、「テストエラー」というダイアログが表示され、その後、「Rollback」というメッセージボックスが表示されます。つまり、ロールバックが行われます。
VBScriptではこのようにWindows Installerにエラーを報告し、ロールバックすることはできませんが、次のようにしてエラーダイアログを表示することはできます。
msiMessageTypeError = &H01000000
Set record = Session.Installer.CreateRecord(0)
record.StringData(0) = "テストエラー"
Session.Message msiMessageTypeError, record
カスタム動作に関して、ヘルプにも幾つかの例が載っていますので、参考にしてください。
(この記事は、「.NETプログラミング研究」の第57号で紹介したものを基にしています。)