for WPF developers
Home Profile Tips 全記事一覧

モダンなインストーラへの道 2 ~ msi インストーラパッケージを生成する ~

(2017/03/24 9:21:06 created.)

(2017/05/24 16:54:49 modified.)

前回はインストーラ開発のためのツールセット WiX をインストールしました。
今回はこの WiX を使って実際に msi インストーラパッケージを生成します。
その前に、インストーラでインストールさせるものが何もないとやりにくいので、
サンプルとして WPF アプリケーションを先に作成しましょう。

早速 Visual Studio で WPF アプリケーションのプロジェクトを作成します。
ここでは SampleWpfApplication という名前のプロジェクトにしています。


このアプリケーションがメインではないので特に触る必要はありませんが、せっかくなので外部 DLL を参照させ、exe ファイル単体では動作しないようにしておきます。
ここでは私が作成したコントロールライブラリ YKToolkit.Controls を使用します。


このライブラリを使用するために、
MainWindow.xaml および MainWindow.xaml.cs を次のように書き換えます。

MainWindow.xaml.cs
  1. namespace SampleWpfApplication
  2. {
  3.     using YKToolkit.Controls;
  4.  
  5.     /// <summary>
  6.     /// MainWindow.xaml の相互作用ロジック
  7.     /// </summary>
  8.     public partial class MainWindow : Window
  9.     {
  10.         public MainWindow()
  11.         {
  12.             InitializeComponent();
  13.         }
  14.     }
  15. }
MainWindow.xaml
  1. <YK:Window x:Class="SampleWpfApplication.MainWindow"
  2.            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.            xmlns:YK="clr-namespace:YKToolkit.Controls;assembly=YKToolkit.Controls"
  5.            Title="MainWindow" Height="100" Width="200">
  6.     <Grid>
  7.         <TextBlock Text="Sample!" TextAlignment="Center" VerticalAlignment="Center" />
  8.     </Grid>
  9. </YK:Window>

実行すると次のようなウィンドウに仕上がります。


それではこの WPF アプリケーションをインストールするためのインストーラを作成しましょう。WiX の Setup Project を新しいプロジェクトとして同じソリューションに追加します。ここでは Installer という名前のプロジェクトにしています。


追加すると Product.wxs というファイルだけを持つプロジェクトが追加されます。


このファイルを開いてみると、次のように XML による雛形のコードが書かれています。WiX はその名の通り、XML コードをコンパイル、ビルドすることでインストーラパッケージを生成します。

Product.wxs
  1. xml version="1.0" encoding="UTF-8"?>
  2. <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  3.   <Product Id="*" Name="Installer" Language="1033" Version="1.0.0.0" Manufacturer="Microsoft" UpgradeCode="293e235a-da12-4ce3-a998-85deeb52698b">
  4.     <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
  5.  
  6.     <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
  7.     <MediaTemplate />
  8.  
  9.     <Feature Id="ProductFeature" Title="Installer" Level="1">
  10.       <ComponentGroupRef Id="ProductComponents" />
  11.     </Feature>
  12.   </Product>
  13.  
  14.   <Fragment>
  15.     <Directory Id="TARGETDIR" Name="SourceDir">
  16.       <Directory Id="ProgramFilesFolder">
  17.         <Directory Id="INSTALLFOLDER" Name="Installer" />
  18.       </Directory>
  19.     </Directory>
  20.   </Fragment>
  21.  
  22.   <Fragment>
  23.     <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
  24.       <!-- TODO: Remove the comments around this Component element and the ComponentRef below in order to add resources to this installer. -->
  25.       <!-- <Component Id="ProductComponent"> -->
  26.         <!-- TODO: Insert files, registry keys, and other resources here. -->
  27.       <!-- </Component> -->
  28.     </ComponentGroup>
  29.   </Fragment>
  30. </Wix>

何やら色々とありますが、とりあえずコメントの TODO にあるように、ComponentGroup 要素の中の Component 要素のコメントを外し、その中にインストールさせたいファイルを追加しましょう。
Component 要素に対してファイルを指定するときは File 要素を使用します。

Product.wxs
  1. xml version="1.0" encoding="UTF-8"?>
  2. <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  3.   <Product Id="*" Name="Installer" Language="1033" Version="1.0.0.0" Manufacturer="Microsoft" UpgradeCode="293e235a-da12-4ce3-a998-85deeb52698b">
  4.     <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
  5.  
  6.     <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
  7.     <MediaTemplate />
  8.  
  9.     <Feature Id="ProductFeature" Title="Installer" Level="1">
  10.       <ComponentGroupRef Id="ProductComponents" />
  11.     </Feature>
  12.   </Product>
  13.  
  14.   <Fragment>
  15.     <Directory Id="TARGETDIR" Name="SourceDir">
  16.       <Directory Id="ProgramFilesFolder">
  17.         <Directory Id="INSTALLFOLDER" Name="Installer" />
  18.       </Directory>
  19.     </Directory>
  20.   </Fragment>
  21.  
  22.   <Fragment>
  23.     <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
  24.        <Component Id="SampleWpfApplication.exe" Guid="E30C960A-5D63-47E6-BBD7-25DA2F6B7901">
  25.          <File Source="../SampleWpfApplication/bin/Release/SampleWpfApplication.exe" />
  26.        </Component>
  27.       <Component Id="YKToolkit.Controls.dll" Guid="62E0220E-572C-4589-8B4E-A354FF1F2B70">
  28.         <File Source="../SampleWpfApplication/bin/Release/YKToolkit.Controls.dll" />
  29.       </Component>
  30.     </ComponentGroup>
  31.   </Fragment>
  32. </Wix>

コメント文にはありませんでしたが、Component 要素にはそれぞれ GUID を必ず与える必要があります。
Component 要素は Windows Installer にとって操作単位を表していて、どの操作によってファイルまたはレジストリを追加したかを認識するために GUID を利用しています。
また、バージョンアップ時にはどの操作が必要なのかについてもこの GUID によって識別しているため、同じ GUID を持つ Component 要素は同じファイルを持たなければいけません。
逆に、ファイルが以前のものと異なる場合は Component 要素の GUID を変更する必要があります。
このことから、Component 要素は File 要素をひとつだけ持つようにしたほうが良いでしょう。

File 要素では Source 属性にファイルの実体を絶対パスまたは相対パスで指定します。
ここでは同一ソリューション内にある SampleWpfApplication プロジェクトのビルド出力を参照しています。
ところで、インストーラを作成するということはビルド構成は Release となるはずです。したがって、ここで参照しているファイルは Release フォルダを参照するようにしています。

以上の設定ができたら早速ビルドしてみましょう。このとき、「プロジェクト」→「プロジェクト依存関係」メニューから、Installer プロジェクトが SampleWpfApplication プロジェクトに依存している設定をしておくと、ビルド順序が自動的に設定されるので便利です。
まだ SampleWpfApplication プロジェクトが最新状態でビルドされていないのに、先に Installer プロジェクトがビルドされてしまうことを防止できます。
SampleWpfApplication プロジェクトのビルド出力がクリーンされた状態なら Installer プロジェクトをビルドした時点でエラーが発生するので問題に気付きますが、古い出力ファイルが残っている場合、その古いファイルを参照してしまうことになります。
このようなことを起こさないようにするためにも、プロジェクトの依存関係の設定はしておいたほうが良いでしょう。


それではビルドして出力されたファイルを見てみましょう。


cab ファイルはインストールされるファイル群が圧縮されたものです。msi ファイルが Windows Installer のためのインストーラになります。wixpdb ファイルは通常の pdb ファイルと同じくデバッガが使用するデバッグ情報です。
インストーラを配布するときは cab ファイルと msi ファイルを公開し、wixpdb ファイルはもしものときのために開発元がセットで管理しておけば良いでしょう。

早速 msi ファイルを実行してみると、唐突にインストールが開始されます。ユーザアカウント制御が有効の場合は変更の許可を求められるので、「はい」を選択してください。


もし cab ファイルが同じフォルダにない状態で msi ファイルを実行すると、次のように怒られてしまいますので、インストーラを配布するときは注意しましょう。


インストールが完了したら、Program Files フォルダを確認してみましょう。
SampleWpfApplication.exe がインストールされています。
ただし、フォルダ名が "SampleWpfApplication" ではなく "Installer" になってしまっています!


これはよろしくないですね。実はこの辺の設定を Product.wxs で変更する必要があります。コードを編集する前にとりあえずインストールしてしまったものをアンインストールしましょう。
コントロールパネルから「プログラムと機能」を開いてアンインストールしましょう。


あぁ、こっちも "Installer" になってる…。

アンインストールしたら Product.wxs に戻りましょう。実は 3 行目の Product 要素にある Name 属性を変更する必要があります。

Product.wxs
  1.   <Product Id="*" Name="SampleWpfApplication" Language="1033" Version="1.0.0.0" Manufacturer="YKSoftware" UpgradeCode="293e235a-da12-4ce3-a998-85deeb52698b">

Name 属性が "Installer" となっていたので、これを "SampleWpfApplication" に修正します。また、Manufacturer 属性が "Microsoft" となっていたので、ついでにこれも変更しています。

この修正をしてからビルドし、もう一度 msi ファイルを実行してインストールしてみましょう。


そういえば表示されていた Windows Installer のダイアログのキャプションなども "Installer" だったのが、"SampleWpfApplication" に変わっていますね。

それでは Program Files フォルダを覗いてみましょう。


あれ?やっぱり "Installer" というフォルダ名になっている…。
どうやら設定が足りなかったようです。
もう一度アンインストールしましょう。


お、こちらは変更した通りになっていますね。
Product 要素の属性はあくまでも製品名に関する情報のようです。

Product.wxs のソースをもう一度確認してみましょう。良く見てみると、中ほどに Direcotry 要素があるのがわかります。

Product.wxs
  1.   <Fragment>
  2.     <Directory Id="TARGETDIR" Name="SourceDir">
  3.       <Directory Id="ProgramFilesFolder">
  4.         <Directory Id="INSTALLFOLDER" Name="Installer" />
  5.       </Directory>
  6.     </Directory>
  7.   </Fragment>

実はこの Directory 要素であらかじめフォルダ構成を記述しており、入れ子にすることでそのツリー構造を表現しています。
Id 属性はそれぞれ固有の識別子となっていますが、一部予約語があるようです。
例えば "ProgramFilesFolder" という Id は "C:\Program Files" というようないわゆるデフォルトのプログラムフォルダを指すようになります。
この Id を持つ Directory 要素の子要素として Name="Installer" という値を持つ Directory 要素が設定されています。つまり、"C:\Program Files\Installer" フォルダを表しているわけですね。
そしてこの Directory 要素の Id は "INSTALLFOLDER" となっていることに注目します。この Id がどこで使われているかというと、最初にファイルを指定した Component 要素を含む ComponentGroup 要素で使用されています。

Product.wxs
  1.     <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
  2.        <Component Id="SampleWpfApplication.exe" Guid="E30C960A-5D63-47E6-BBD7-25DA2F6B7901">
  3.          <File Source="../SampleWpfApplication/bin/Release/SampleWpfApplication.exe" />
  4.        </Component>
  5.       <Component Id="YKToolkit.Controls.dll" Guid="62E0220E-572C-4589-8B4E-A354FF1F2B70">
  6.         <File Source="../SampleWpfApplication/bin/Release/YKToolkit.Controls.dll" />
  7.       </Component>
  8.     </ComponentGroup>

つまり、この ComponentGroup 要素に含まれるファイル群は "INSTALLFOLDER" で指定されたフォルダ "C:\Program Files\Installer" にコピーしてくださいと書いてあることになります。
そういうわけで、17 行目の Directory 要素を変更します。

Product.wxs
  1.   <Fragment>
  2.     <Directory Id="TARGETDIR" Name="SourceDir">
  3.       <Directory Id="ProgramFilesFolder">
  4.         <Directory Id="ROOTFOLDER" Name="YKSoftware">
  5.           <Directory Id="INSTALLFOLDER" Name="SampleWpfApplication" />
  6.         </Directory>
  7.       </Directory>
  8.     </Directory>
  9.   </Fragment>

17 行目の Directory 要素の前に Id="ROOTFOLDER" を持つ Directory 要素を追加し、Name 属性を "YKSoftware" というように開発元の名前を指定しています。そしてその Direcotry 要素の子要素として Id="INSTALLFOLDER" を持つ Directory 要素を指定し、その Name 属性を "SampleWpfApplication" としています。

これでようやくほぼ完成です。ビルドして msi ファイルを実行してインストールしてみましょう。


晴れて意図したフォルダにアプリケーションをインストールすることができました!

その他まだ編集していない要素などがありますが、基本的にはデフォルトのままで良いでしょう。
ただし、Product 要素の Language 属性は Windows Installer の言語対応を指定していますが、デフォルトで英語が指定されています。日本語を指定する場合は "1041" という値に修正しましょう。

Product.wxs
  1.   <Product Id="*" Name="SampleWpfApplication" Language="1041" Version="1.0.0.0" Manufacturer="YKSoftware" UpgradeCode="293e235a-da12-4ce3-a998-85deeb52698b">

ビルドし直して msi ファイルを実行してみると、確かに Windows Installer のダイアログが日本語表記になっていることが確認できます。


だいぶ長くなりましたが、今回はここで終了します。
Product.wxs による詳細な設定については下記のサイトが参考になると思います。


次回はカスタム UI によるインストーラを開発するための Bootstrapper Project を紹介します。