WiX チュートリアル (日本語訳)

Lesson 1 始めよう

既に前書きで述べたように、WiX ツールセットは XML ソース・ファイルを使用して、アプリケーションのインストール・プロセスを構成するコンポーネントを記述します。また、XML ソース・ファイルには、ショートカット、レジストリおよび .ini ファイルの変更、サービスなど関する付加的なデータも記述します。さらに、インストーラ・パッケージは、ユーザーのディスクにコピーしたいファイルに加えて、インストールのプロセスに参加はするけれども実際にはインストールされないヘルパー・ファイルを持つことが有り得ます。そういうヘルパー・ファイルとしては、インストールの UI に使用されるダイアログやアイコンおよびビットマップ、あるいは、ライセンス・ファイルや readme ファイル、さらには、Windows Installer がサポートしていないプログラム的な処理を実行するカスタム DLL が含まれます。(例えば、何らかのユーザー登録やキー・チェック・ロジックを実装したいと思うかも知れませんが、Windows Installer はそういう処理をサポートしていません。)

これら全てのものをソース・ファイルに記述して、WiX のコンパイラに供給します。ツールセットはいくつかの部分から構成されていますが、そのうちの二つを使って、インストーラ・パッケージをコンパイルします。ここで Sample.wxs というソース・ファイルを用意したと仮定しましょう。最初に、

candle.exe Sample.wxs

というコマンドで、コンパイルの第一段階を実行して、Sample.wixobj という半分消化されたファイルを作成します。このファイルはまだ XML ですが、その内部構造は人間が読むことを想定したものではありません。通常のコンパイラ用語でオブジェクト・ファイルと呼ばれるものだと考えて下さい。次に、第二のコマンドとして、

light.exe Sample.wixobj

を実行し、この中間表現を最終的なパッケージである Sample.msi というファイルに変換します。コンパイラとリンカにそっくりですね。実際は、それだけに留まりません。リンカは、明示的にそうするなと指示しない限りは、検証ステップを実行して、完成されたインストーラ・データベースを数百項目にわたってチェックし、エラーや問題が無いかを調べます。

今日のコンパイラでは普通のことですが、もはやコマンド・ラインでの使用に限定されてはいません。Microsoft Visual StudioSharpDevelop のような統合開発環境も、IDE そのものの内蔵機能やアドインとして、WiX プロジェクトをサポートすることが出来ます。Visual Studio の場合は、WiX v3 パッケージをインストールすると Visual Studio の WiX サポートが自動的にインストールされます。この手法を使って、独立したセットアップ・ソリューションを作成することも出来ますし、セットアップを部分プロジェクトとして全体のソリューションに含めることも出来ます。この場合、最終的なセットアップ・パッケージをビルドするためにプログラミング環境を離れる必要はなくなります。

コンパイラとリンカの比喩は非常に明解で、WiX が実際のインストール・パッケージをビルドする方法を理解するのに役立つかも知れません。しかし、もうすぐ書き始めようとしている WiX のソースをスクリプトやプログラミング言語のようなものだと考えるべきではありません。私たちがソースに記述するのは、アプリケーションをインストールするのに必要なステップ操作を集めたものではありません。アプリケーションの配布に使用する .msi ファイルは、セットアップ・アプリケーションではなく、インストール・データベースです。プログラムのロジック、すなわち、アプリケーションをインストールする方法や、レジストリ・キーを修正する方法、ショートカットやユーザーやネットワーク共有を作成する方法、ウェブ・ディレクトリやサービスを操作する方法は、すべて、Windows Installer の方に内在しています。私たちが作成するセットアップ・ファイルは、Windows Installer によってが実行されるべきかを記述し、そして、配置すべきファイル(およびインストールのプロセスで使用するインタフェイス要素)を提供するだけのものです。

データベースの手法を取っているということは、WiX のソース・ファイルは通常のプログラムと同じようにビルドされる訳ではない、ということを意味しています。WiX には順次実行という概念はありません。最初のソース行が次のソース行に先行して実行されるという想定はありません。参照に先立って宣言が必要であるということもありません。さまざまな要素が別の場所で記述されることが有り得ます。そして、要素間にリンクが必要な場合は、私たちが提供する一意の識別子を使用して、一方から他方を参照しなければなりません。プログラミング言語の用語で考える必要がある、というのであれば、WiX は命令的・指示的な言語ではなく、関数的・宣言的な言語であると考えて下さい。

また、WiX はそれ自体としてはインストール環境ではない、ということにも留意して下さい。簡単に言うと、WiX は、インストールの要求事項を記述するための使いやすい XML スタイルの方法であり、その記述をコンパイラとリンカによって Windows Installer の .msi データベースに変換するものである、ということです。この面において、WiX は Windows Installer のテクノロジを包んだ比較的薄いラッパーです。WiX がセットアップ開発者を支援する付加的な機能をいくつか提供しているのは事実ですが、WiX の能力は基礎となっているテクノロジによって決定されており、その制約も WiX 自身のものではなく Windows Installer 自体が持っている制約なのです。

1.1 ソフトウェア・パッケージ

最初の WiX サンプルは、非常に簡単な空想上のアプリケーションをインストールするものです。このアプリケーションは、実行ファイルと DLL ライブラリ、そしてユーザー・マニュアルから構成されています。この三つのファイルが、アプリケーション固有のフォルダにコピーされるものとします。また、スタート・メニューの通常の場所に二つのショートカットを出現させることにします。さらに、ユーザーのデスクトップにもアプリケーションを起動するショートカット・アイコンを追加しましょう。

このインストール・パッケージほど単純なものであっても、基礎となっている Windows Installer は非常に多くの機能を提供してくれます。たとえば、このプログラムも「コントロール・パネル > プログラムの追加と削除」の中に自動的に追加されます。Windows Installer が私たちのプログラムを確実に追跡して記録しておけるようにするために、私たちは何らかの識別方法を提供しなければなりません。全てのアプリケーションは人間が読める名前を持っていますが、Windows は私たちのパッケージのあらゆる部分を識別するために、単なる名前ではなく、もっとユニークな識別子である GUIDを必要とします。GUID とは、CE26AD1E-00D5-4E03-8CBE-6DA5C23AA833 のような、あの長ったらしい16進の数値です。GUID は無料で、いつでも、欲しいだけ生成することが出来ます。生成された GUID は一意であることが保証されていて、他の誰かが別のコンピュータで GUID を取得しても、あなたが取得する GUID と衝突することは決してありません。GUID を取得するユーティリティーは数多くあります。例えば、この単純な C プログラムは、どんな C/C++ コンパイラでもコンパイルすることが出来ます。別のプログラミング言語を使う場合でも、必要な Win32 関数 (CoCreateGuidStringFromCLSID) を呼び出すようにすれば、GUID を生成出来ます。もう一つの方法として、多くのプログラマ用エディタや統合開発環境では、要求に応じて新しく生成した GUID をソース・コードに挿入する機能を提供していますので、それを利用することも出来ます(Microsoft の Visual Studio であれば、このマクロをショートカットに割り当てて使うことが出来ます)。使用するツールが小文字の16進数文字を生成する場合は、WiX に渡す前に大文字に変換して下さい。

このチュートリアルの GUID は、ダウンロード出来るサンプルにあるものも含めて、すべて不正なものになっています — 最初のセクションが全て YOURGUID に変更されているのです。このことは、サンプルはそのままではビルドすることが出来ない、あなた自身が生成した GUID を最初に提供しなければならない、ということを意味しています(ビルドを試みると、"fatal error CNDL0027: The 'Id' attribute has an invalid value according to its data type." というエラーになります)。これはちょっとした不便ですが、万が一の危険を避けるためには必要な措置です。さもなくば、複数の人が、サンプルのどれかをそのまま使用し、GUID を変更するのを忘れて、衝突の危険を作って野に放つかも知れません。もう一つ注意が必要なのは、GUID の全体を置き換える必要があるということ、すなわち、別の GUID から取った部分を混ぜ合せてはいけないということです(換言すると、YOURGUID だけを置き換えるな、全部の数値を置き換えろ、ということです)。GUID は、生成されたままで使用される場合にだけ、一意性が保証されます。

始めに、二つの GUID が必要です。一つは製品(Product)のため、もう一つはインストーラ・パッケージ(Package)のためです。実務上のプロジェクトでは、UpgradeCode の GUID も必ず必要になるので、実際には三つです。 他の二つは、おそらく後で参照する必要が生じますので、ファイルに保持しておかなければなりませんが、Package の GUIDは、パッケージを生成するたびに違うものである必要があります。事を簡単にし、新しい GUID を発行するのを忘れないようにするために、アスタリスクをタイプして WiX に GUID を自動生成させることが出来ます — ただし、これはパッケージの GUID だけに当てはまることを忘れないで下さい。その他の全ての GUID は、一意性を保たなければならず、将来にわたって記録されなければなりません。これらの GUID と製品に関するその他のテキスト情報が SampleFirst.wxs の一番最初の部分に来ます。

<?xml version='1.0' encoding='windows-1252'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>

  <Product Name='Foobar 1.0'
           Id='YOURGUID-86C7-4D14-AEC0-86416A69ABDE'
           UpgradeCode='YOURGUID-7349-453F-94F6-BCB5110BA4FD'
           Language='1033' Codepage='1252'
           Version='1.0.0' Manufacturer='Acme Ltd.'>

    <Package Id='*' Keywords='Installer'
             Description="Acme's Foobar 1.0 Installer"
             Comments='Foobar is a registered trademark of Acme Ltd.'
             Manufacturer='Acme Ltd.' InstallerVersion='100'
             Languages='1033' Compressed='yes' SummaryCodepage='1252' />

製品の名前と説明は、当然、あなたの意向によって決ります。Version 属性は、標準的な major.minor.build 形式に従って下さい。Windows Installer は、リビジョン(revision)を区別する別の方法を提供しているため、最後に来る第4のフィールド、.revision を無視します。

ご存じのように、XML は形式については非常に自由です。適切だと思われるなら、段落下げや空行を使って下さい。属性の値はすべて引用符で囲みますが、単一引用符か二重引用符かは自由に選ぶことが出来ます。この規則を使うと、引用符を含む文字列値を書くことが簡単に出来ます(上記の Description を参照して下さい)。その文字列を囲むのに、もう一方の引用符を使うようにだけ気を付ければ良いのです。

XML を作成するのには、UTF-8 も ANSI も使うことが出来ます。普通の ANSI 文字または標準 ANSI 文字セットのアクセント記号付き文字だけを使う予定であれば、この例に示されているように windows-1252 の設定で十分です。ユーザー・インタフェイスにおいてもっと多数の文字または違う文字セットを使う必要がある場合は、UTF-8 に変更して、適切な言語とコードページの数値を使って下さい。例えば、日本語であれば、次のように変更します。

<?xml version='1.0' encoding='utf-8'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>

  <Product Name='ほげ 1.0'
           Id='YOURGUID-86C7-4D14-AEC0-86416A69ABDE'
           UpgradeCode='YOURGUID-7349-453F-94F6-BCB5110BA4FD'
           Language='1041' Codepage='932'
           Version='1.0.0' Manufacturer='ぴよソフト'>

    <Package Id='*' Keywords='インストーラ'
             Description="ぴよソフト's ほげ 1.0 インストーラ"
             Comments='ほげはぴよソフトの登録商標です。'
             Manufacturer='ぴよソフト' InstallerVersion='100'
             Languages='1041' Compressed='yes' SummaryCodepage='932' />

† 訳註:これ以降、原文のソフト名 "Foobar" と会社名 "Acme Ltd." は、慣例に従って、"ほげ(Hoge)" および "ぴよ(Piyo)ソフト" と訳出します。

1.2 中に入れるファイル

次のステップとして、インストールに使用するメディアを指定しなければなりません。CD と DVD の時代においては、インストール・ファイルを複数のメディアに分ける必要はほとんど有りませんが、可能性が無いわけではありません(メディアを分ける必要がある場合は、ここで提供されるメディアの Id を使って、個々のディスクを参照することが出来ます。DiskPrompt は、ユーザーがどのディスクを挿入すべきかを決めることが出来るように、インストール・メディアのユニットを示すためのいろんなテキスト記述を含むことが出来ます。Windows Installer はこの記述をディスクのプロンプトとして使用します)。

    <Media Id='1' Cabinet='Sample.cab' EmbedCab='yes' DiskPrompt='CD-ROM 1枚目' />
    <Property Id='DiskPrompt' Value="ぴよソフト's ほげ 1.0 インストーラ [1]" />

EmbedCab 属性を使って、インストール・ファイルのキャビネット(アーカイブ)を .msi パッケージ・ファイル自体の一部として埋め込むか、それとも独立したファイルにするかを決定することが出来ます。最終的なインストール・パッケージでは、埋め込みにするのが普通の判断です(その結果として、単一の自己完結的なファイルが、ダウンロードやメディアでの出荷に供されることになります)。CabinetEmbedCab も指定されない場合は、ソース・ファイルはそのままの状態に留められて、インストーラの .msi ファイルと一緒に、配布メディアに直接コピーされることになります。

前書きで強調したように、Windows Installer は以前のプログラム的な手法から宣言的・記述的な手法に移行しました。私たちは階層的に入れ子になった XML の構造を使って、ソース・フォルダの階層構造を記述します。そして、インストーラに対して、ユーザー・マシンへのインストールの際に、ソース・フォルダの構造を再作成するように要求します。Windows Installer は、一番外側のフォルダ、すなわちインストール全体のルート・デスティネーション・フォルダから開始するように私たちに求めます。ルート・デスティネーション・フォルダは TARGETDIR という定義済みの識別子を持っていて、これがインストール・パッケージのソース・キャビネット・ファイルまたはソース・ファイル・ツリーを保持するルート・ディレクトリに対置されます。ソースのルート・ディレクトリも、SourceDir という定義済みの名前を持っています。この関係が、どこからインストールするか、と、どこへインストールするか、の基本的なリンクを提供します。

    <Directory Id='TARGETDIR' Name='SourceDir'>

このルート・フォルダの内側で、実際の構造について記述を進めます。インストールされるファイルは、制定されたガイドラインに従って、あらかじめ定められた場所に入っていきます。たとえば、アプリケーションは \Program Files\Company\Product の下に入らなければいけません。ショートカット、デスクトップ・アイコン、ユーザー別設定項目、その他の全てについて、それぞれにあらかじめ定められたインストール場所が有ります。好都合なことに、既定のインストール場所全てに対して、インストーラ環境が定義済みの名前を提供してくれていますので、それらを参照することは非常に簡単に出来ます(そして、このことは、私たちを地域化(ローカライゼーション)の問題からも解放してくれます。と言うのは、英語版以外の Windows では、既定のインストール先フォルダは全く違う地域化された名前を持ち得るからです)。私たちの現在の例では、三つの定義済みの名前、すなわち、ProgramFilesFolder, ProgramMenuFolder および DesktopFolder を使います。これらの定義済みの名前はフル・パスを参照することに注意して下さい。たとえ、デスクトップ・フォルダが C:\Documents and Settings\User\Desktop という数階層深い場所にあるとしても、単一の Directory タグだけでそれを参照するのに十分なのです。しかし、私たち自身の入れ子になったフォルダについては、それぞれのレベルを個別に定義しなければなりません。

      <Directory Id='ProgramFilesFolder' Name='PFiles'>
        <Directory Id='Piyo' Name='Piyo'>
          <Directory Id='INSTALLDIR' Name='Hoge 1.0'>

全ての要素に対して Id 識別子を提供しなければならないことに注意して下さい(この事は WiX を使用するについて全体にわたって当てはまることです)。ほとんどの場合、これらの識別子は、WiX のソース・ファイルの至る所から相互参照されるために、一意である必要があります。ですから、理解しやすい命名規則を用意しておくように気をつけて下さい。Id として、たとえば ProgramFilesFolder のような、定義済みの名前を使うことが出来る場合もあります。また、INSTALLDIR のようなプロパティ名(文字列変数とほぼ同じもの)を使う場合もあります。このプロパティ名については、後でもう一度言及することになります。

コンポーネント(component)の概念とその使い方を支配する規則は、おそらく、Windows Installer テクノロジの最も重要な概念です。この規則を遵守しないことが、セットアップが失敗する主要な原因です。従って、最初のサンプルについて先に進む前に、コンポーネントについて十分な理解を得ておくことは、非常に重要な事です。

コンポーネント は、インストールされる事物の最小単元です。コンポーネントは、常に一つの単位としてインストールされるべき複数のリソース(ファイル、レジストリ・キー、ショートカット、またはその他何でも)から成り立ちます。あるコンポーネントをインストールするときに、決して、他のコンポーネントに影響を及ぼしてはいけません。また、あるコンポーネントを削除するときに、決して、他のコンポーネントに害を及ぼしたり、あるいは、どのコンポーネントにも属さない孤児化したリソースを対象のコンピュータに残したりしてはいけません。当然の帰結として、コンポーネント間でファイルを共有することは出来ません。同一の場所にインストールされる同一のファイルが、二つ以上のコンポーネントに含まれることは、決して有ってはいけません。

どういうファイルが製品を構成するかについて考えなければならないのは、コンポーネントのレベルではありません。ある製品が、一つの EXE と、三つの DLL と、二~三のデータ・ファイルを必要とするとしても、そのことは、それら全てのファイルが単一のコンポーネントに入らなければならない、という意味ではありません。全く逆です。後ほど、階層のもっと高いレベルにおいて、どういうコンポーネントが一緒になって製品の独立した部分を形成するかを決定します。

もう一度言います。コンポーネントは、結びつきが非常に強くて常に一緒にインストールしたり削除したりする必要があるアイテムだけを含むべきです。もしそれがファイル一個だけを意味するのであれば、コンポーネントはそれぞれファイルを一個だけ含むことになります。そのようにするのは普通のことであるだけでなく、まさしく、そうするように要求されていることでもあります。恐がってはいけません。Windows Installer は、数千、いや、必要であれば、それ以上の数のコンポーネントを効率的に処理することが出来ます。

という事で、私たちは一つのコンポーネントに三つのアイテム(一つのファイルと、それを指し示す二つのショートカット)を含めることにします。コンポーネントは、それ自身の Id 識別子、および、それ自身の一意の GUID を持たなければなりません(WiX のコンパイラとリンカは、この二つのうち、どちらかを間違って再利用すると、警告を発します)。このことは非常に重要です — これらの GUID は Windows Installer がさまざまなコンポーネントを追跡して記録しておくための唯一の方法です。このコンポーネントの規則を破ると、悲惨な結果が生じることになります。例えば、削除時にリソースがどのコンポーネントにも属さない孤児になってマシン上に残されたり、別のアプリケーションがまだ必要としているのに共有リソースが間違って削除されたり、既存の製品を再インストールするときに機能の回復に失敗したり、新しいバージョンのアプリケーションをインストールすると以前のバージョンが破壊されたり、という障害が生じることになります。

            <Component Id='MainExecutable'
                       Guid='YOURGUID-83F1-4F22-985B-FDB3C8ABD471'>

ファイルはその名前で特定されます。実際のファイル名以外に、いくつか別の属性を使って、ファイルの特性を記述することが出来ます。Vitalno に設定すると、このファイルのインストールは決定的な重要さを持たない、という事をインストーラに教えることが出来ます。通常は、どのようなファイルであれ、どのような理由であれ、ファイルのインストールが失敗すると、ユーザーが問題を無視することは許されず、インストールは中止されます。その他の属性としては、ReadOnly, Hidden, System 等があります。これらは、すべて、ファイルをインストールするときに適切な属性を設定するためのものです。

すべてのコンポーネントには、キー・パスが必要です。この項目は、コンポーネントが実際にインストールされているかどうかを Windows Installer が後でチェックするときに使うものです。まずもってインストールする方法を学習しているという現時点では、これはあまり重要なことだとは思われないかも知れません。しかし、インストーラのアンインストール機能と修復機能をサポートするためには、使用する全てのコンポーネントにキー・パスを指定することが重要なことになります。それに、キー・パスを指定しないとコンパイラが文句を言います ...

              <File Id='HogeEXE'
                    Name='HogeAppl10.exe' DiskId='1'
                    Source='HogeAppl10.exe' KeyPath='yes'>

ショートカットも名前を持っていますが、その他に、作業フォルダやアイコン指定のような他の重要な項目も持っています。Directory (スタート・メニューやデスクトップのように、ショートカットが配置される場所)と WorkingDirectory(ショートカットが指し示す場所)との違いに注意して下さい。後者は任意項目で、省略された場合のデフォルト値は、予想される通り、親のファイルがインストールされるフォルダとなります。Icon 属性を使うと、実際のファイル名の代りに、ソースのどこか他の場所で定義されている Icon タグの Id を指定することが出来ます(.exe という拡張子である必要はないように見えるかも知れませんが、アイコンの識別子は参照している実際のファイルと同じ拡張子を持っていなければなりません)。INSTALLDIR というプロパティを再利用していることに気付くと思いますが、これは、予想される通り、私たちがインストール先にしようとしているフォルダ、すなわち、Program Files\Piyo\Hoge 1.0. を参照します。その他のフォルダの記述も、後からソース・コードに出てくるかも知れません。

ショートカットは、ノン・アドバタイズド(non-advertised : ショートカットのプロパティ・ダイアログで、ファイルへの単純なリンクになっているもの)か、アドバタイズド(advertised : リンクがグレイ・アウトされているもの)かを選べます。第二の形式の場合は、ショートカットが指し示すファイルが欠けているときに、Windows Installer が修復インストールをしてファイルを修復することが出来ます。

                <Shortcut Id="startmenuHoge10" Directory="ProgramMenuDir"
                          Name="ほげ 1.0" WorkingDirectory='INSTALLDIR'
                          Icon="Hoge10.exe" IconIndex="0" Advertise="yes" />
                <Shortcut Id="desktopHoge10" Directory="DesktopFolder"
                          Name="ほげ 1.0" WorkingDirectory='INSTALLDIR'
                          Icon="Hoge10.exe" IconIndex="0" Advertise="yes" />
              </File>
            </Component>

さらに二つ、一意の IdGuid を持つ別のコンポーネントを定義します。

            <Component Id='HelperLibrary'
                       Guid='YOURGUID-6BE3-460D-A14F-75658D16550B'>
              <File Id='HelperDLL' Name='HogeHelper.dll' DiskId='1'
                    Source='HogeHelper.dll' KeyPath='yes' />
            </Component>

            <Component Id='Manual'
                       Guid='YOURGUID-574D-4A9A-A266-5B5EC2C022A4'>
              <File Id='Manual' Name='Manual.pdf' DiskId='1'
                    Source='Manual.pdf' KeyPath='yes'>
                <Shortcut Id='startmenuManual' Directory='ProgramMenuDir'
                          Name='取扱説明書' Advertise='yes' />
              </File>
            </Component>

あなたが予想される通り、数百さらには数千のファイルを持つアプリケーションにとっては、このようなやり方は、数百または数千のコンポーネントが必要になる、という事を意味します。そうです、それが普通であり、それが期待されている方法なのです。恐がることはありません。パフォーマンス上の問題は生じません。Windows Installer はこのやり方で問題なく動作するように作られています。

そういう数百または数千のコンポーネントを WiX ソース・ファイルに打ち込むということは、もちろん、もう一つ別の困難な課題を提起するものです。ツールセットには、この事に関して支援をする小さなユーティリティーがあります(後で詳細に述べます)が、本当の解決は、考え方を変更することにあります。セットアップ・プログラムはメインのアプリケーションがほとんど完成した時に大急ぎで書くべき独立したアプリケーションである、という考え方をやめるのです。WiX のソース・ファイルも、ツールセット自体も、開発環境に統合することが容易に出来るのですから、常に同期状態を保つべきです。新しいモジュールの開発に着手したり、新しいレジストリ参照をプログラムに追加したりしたら、ただちに、対応する WiX ソース・ファイルを同時に修正するのです。そうすれば、セットアップはアプリケーション自体と一緒に完成されて、後からインストールに必要なすべてのファイルとその他の情報の断片を抽出する必要が無くなります。WiX プロジェクトはモジュラー化できます(後で詳細に述べます)ので、この手法は、一人の開発者ではなく、大きなチームでアプリケーションを開発する場合でも、うまく機能します。

そして、次に、ディレクトリ要素を閉じるタグが来ます — まだ終りではありませんので、開始のタグより一つ少なくします。最初の TARGETDIR ディレクトリの内側にとどまって、あと二つ、インストーラの定義済みの名前を使ってフル・パスのフォルダを定義します。一つはスタート・メニューのショートカットのためであり、もう一つはデスクトップ・アイコンのためです。その後で、ようやく、一番外側の Directory タグが閉じられます。

製品がアンインストールされるときにプログラム・フォルダを削除する必要がありますので、第四のコンポーネントを作成する必要があります。RemoveFolder タグが私たちの意図を記述します。On 属性によってフォルダが削除される時が決定されます(使用できる値は、install, uninstall および both です)。既に述べたように、すべてのコンポーネントは自分自身のキー・パスを持たなければなりません。この場合、キー・パスは追加の RegistryValue タグです。このタグは、この最初のレッスンの範囲を超えていますので、後でもう一度説明します。KeyPath 属性をコンポーネントかフォルダに設定してもうまく機能するかも知れませんが、リンカが警告を発する結果になります。ですから、当面、コンパイラとリンカの警告メッセージを避けるために、一時的に説明抜きで、この解決方法を受け入れておいて下さい。

          </Directory>
        </Directory>
      </Directory>

      <Directory Id="ProgramMenuFolder" Name="Programs">
        <Directory Id="ProgramMenuDir" Name="ほげ 1.0">
          <Component Id="ProgramMenuDir"
                     Guid="YOURGUID-7E98-44CE-B049-C477CC0A2B00">
            <RemoveFolder Id='ProgramMenuDir' On='uninstall' />
            <RegistryValue Root='HKCU'
                           Key='Software\[Manufacturer]\[ProductName]'
                           Type='string' Value='' KeyPath='yes' />
          </Component>
        </Directory>
      </Directory>

      <Directory Id="DesktopFolder" Name="Desktop" />
    </Directory>

これら二つのフォルダを特定するのに使用した Id 識別子(ProgramMenuDirDesktopFolder)に注意して下さい。これらは、前に、ショートカットの Directory 属性として使用した名前であり、ショートカットの場所を実際のフォルダに関連づけたものです。

最後に、しかし重要なこととして、私たちがどんな機能をインストールしたいのかを、インストーラに指示します。機能(Feature)はアプリケーションの独立した部分であり、インストールするかしないかを選ぶように私たちがユーザーに提案するものです。詳細はもちろん特定のソフトウェア・パッケージに依存しますが、通常の組合せは以下のようなものになります。

最初のサンプルでは、そのような機能分けはしません。まず、インストールしようとする三つのファイルをいろんな機能に分割することはほとんど不可能です。次に、機能を分けるためには、ユーザーが機能を ON/OFF するためのユーザー・インタフェイスも必要になります。これについては次回のレッスンで説明しますが、今のところは、機能は一つにしておきます(というのは、少なくとも一つは機能が必要だからです)。この機能としてインストールしたいコンポーネントを Id 識別子を使って参照します。

    <Feature Id='Complete' Level='1'>
      <ComponentRef Id='MainExecutable' />
      <ComponentRef Id='HelperLibrary' />
      <ComponentRef Id='Manual' />
      <ComponentRef Id='ProgramMenuDir' />
    </Feature>

ショートカットに使用したいアイコンも含めなければなりません。アイコンの Id 識別子は、対象のファイルと同じ拡張子、この場合は、.exe を持たなければならないことに注意して下さい。

    <Icon Id="Hoge10.exe" SourceFile="HogeAppl10.exe" />

このようにすると、ソース・ファイルは最終のインストーラ・パッケージの中で、独立して保存されます(つまり、メインの実行ファイルを参照している場合、二つのコピーが保存されます)。ファイルのサイズが大きすぎて問題になる場合は、アイコンだけを含んだ小さな .exe.ico を作成して下さい。

    <Shortcut Id="desktopHoge10" Directory="DesktopFolder"
              Name="ほげ 1.0" WorkingDirectory='INSTALLDIR'
              Icon="Hoge10.ico" IconIndex="0" />
    ...
    <Icon Id="Hoge10.ico" SourceFile="HogeAppl10.ico" />

以上で、残されていることは、まだ開いている二つのタグに、閉じるタグを提供することだけになりました。

  </Product>
</Wix>

行った作業を要約しましょう。最初に、人間が読めるテキストと要求される GUID の両方で、私たちのアプリケーションの記述を行いました。第二に、インストールに使いたいメディアを定義しました。次に、インストールされるファイルのフォルダ構造を定義しました。インストールされるファイルは、付随するリソースと一緒に、すべて適切なコンポーネントに入れました。そして最後に、個々のコンポーネントを参照して、インストールしたい機能を記述しました。

1.3 使ってみる

これらの断片をすべてコピーしてまとめたら(または、ちょっとした力仕事を省くために、SampleFirst のアーカイブ をダウンロードしたら)、以下のコマンドでインストーラ・パッケージをビルドすることが出来ます。

† 訳註:SampleFirst の日本語版(Sample-1-3-First.zip)を用意しましたので、ご利用下さい。

candle.exe SampleFirst.wxs
light.exe SampleFirst.wixobj

どちらのツールからもエラー・メッセージは出ない筈で、完了後には SampleFirst.msi というファイルがフォルダの中に出来ている筈です。そうでない場合は、タイプ・ミスがどこかに無いか、チェックして下さい。また、.wxs ファイルに要求される文法が、WiX ツールセットの成長に伴って、小さな変更を受けているかも知れないことに注意して下さい。このチュートリアルは常にツールセットの最新版を前提としており、サンプルはすべて「箱から出したまま」の状態で(前に述べたように不正な GUID を修正さえすれば)エラー・メッセージ無しにコンパイル出来る筈になっています。もしコンパイラのエラーや警告が出る場合は、使っているツールセットのバージョンとダウンロードできる最新バージョンとを比較してみて下さい。

最初のインストーラをテストするために、それをクリックしてみて下さい。インストーラは「ようこそ」のメッセージも表示せず、オプションの選択も提供せず、数秒の間、進捗ダイアログを表示するだけです。しかし、エラー無しにインストールが終ると同時に、三つのファイルが無事に \Program Files\Piyo\Hoge 1.0 の中に入っているのを確認できる筈です。実際に配置されるファイルは三つとも1バイトのプレースホルダ・ファイルであることに注意して下さい。インストールされた「アプリケーション」は、当然のことながら、起動はしません ...

† 訳註:と言うのでは少し寂しいので、SampleFirst の日本語版には、実際に起動するアプリケーションを付属させました。ただし、毒にも薬にもならない、起動するだけが取り柄のアプリケーションです。

この巨大なアプリケーションを削除するには、「コントロール・パネル > プログラムの追加と削除」を開いて、リストの中から Hoge 1.0 を見つけ出し、「削除」をクリックして下さい(「変更」はこの段階では何もしません)。三つのファイルと Piyo\Hoge 1.0 サブフォルダの両方が消滅する筈です。

何か問題があった場合は — 単に面白半分でも構いませんが — ロギングのスイッチを ON にしてインストーラを起動して見て下さい。

msiexec /i SampleFirst.msi /l* SampleFirst.log

あるいは、さらに、

msiexec /i SampleFirst.msi /l*v SampleFirst.log

でも構いません。出力されるログは(特に第二の場合は)冗長すぎる嫌いがありますが、インストールが失敗する原因となりうるエラーを正確に指摘します。

テスト中は、マウスを使って「プログラムの追加と削除」まで行くことなく、パッケージを削除できる方が便利でしょう。代りに、以下のコマンドを入力してください。

msiexec /x SampleFirst.msi

1.4 便利な追加機能

言うまでもない事ですが、単に二~三のファイルをコピーするよりも、はるかに多くのことが出来ます。まず最初に、起動条件を付けることが出来ます。何らかのグローバルな設定をチェックして、もし条件が合わなければ、インストールを中止することが出来るのです。前のサンプルのどこか、機能(Feature)の中ではないところ(例えば、Package タグと Media タグの間)に、下記の行を追加して下さい。そうすると、あなたがマシンの管理者でない場合は、パッケージは起動しなくなります。

<Condition Message="この製品をインストールするためには、管理者である必要があります。">
  Privileged
</Condition>

Vista の場合は、こうです。

<Condition Message="この製品をインストールするためには、管理者である必要があります。">
  AdminUser
</Condition>

注意すべき事は、開始タグと終了タグの間に書かれた条件がと評価されるときに、メッセージが表示されて、インストールが中止されるということです。言い換えると、エラーになる条件を定義するな、インストールを続行したい条件を定義しろ、ということです。

<Condition Message="この製品をインストールするためには、管理者ではない必要があります。">
  NOT Privileged
</Condition>

同様の条件として使える標準的なプロパティはかなり沢山ありますが、最も重要なものはセットアップを起動しようとしている Windows のバージョンを特定するものでしょう。Version9X は、Windows 95, 98 および ME の場合に真になります。VersionNT は、NT 4.0 以降で真になります。VersionNT64 は、64-bit オペレーティング・システムを示します。

<Condition Message='このアプリケーションは、Windows 95/98/ME でのみ動作します。'>
  Version9X
</Condition>

実際には、これらのプロパティは真偽値ではなく整数値であり、さらに詳細な変種をチェックするために使うことも可能です。

<Condition Message='Windows 95'>
  Version9X = 400
</Condition>
<Condition Message='Windows 95 OSR2.5'>
  Version9X = 400 AND WindowsBuild = 1111
</Condition>
<Condition Message='Windows 98'>
  Version9X = 410
</Condition>
<Condition Message='Windows 98 SE'>
  Version9X = 410 AND WindowsBuild = 2222
</Condition>
<Condition Message='Windows ME'>
  Version9X = 490
</Condition>
<Condition Message='Windows NT4'>
  VersionNT = 400
</Condition>
<Condition Message='Windows NT4 SPn'>
  VersionNT = 400 AND ServicePackLevel = n
</Condition>
<Condition Message='Windows 2000'>
  VersionNT = 500
</Condition>
<Condition Message='Windows 2000 SPn'>
  VersionNT = 500 AND ServicePackLevel = n
</Condition>
<Condition Message='Windows XP'>
  VersionNT = 501
</Condition>
<Condition Message='Windows XP SPn'>
  VersionNT = 501 AND ServicePackLevel = n
</Condition>
<Condition Message='Windows XP Home SPn'
  >VersionNT = 501 AND MsiNTSuitePersonal AND ServicePackLevel = n
</Condition>
<Condition Message='Windows Server 2003'>
  VersionNT = 502
</Condition>
<Condition Message='Windows Vista'>
  VersionNT = 600
</Condition>
<Condition Message='Windows Vista SP1'>
  VersionNT = 600 AND ServicePackLevel = 1
</Condition>
<Condition Message='Windows Server 2008'>
  VersionNT = 600 AND MsiNTProductType = 3
</Condition>
<Condition Message='Windows 7'>
  VersionNT = 601
</Condition>

このようなプロパティが他にも沢山あり、起動条件として使用できます。例えば、MsiNTProductType を使うと、ワークステーション、ドメイン・コントローラ、サーバーを区別することが出来ます。その他の MsiNT プロパティも、必ずチェックしておいて下さい。

1.5 どこにインストールするか?

単独で動作するアプリケーションは、Program Files 以下の自分自身のフォルダにインストールされます — どうやってそれを実現するかは既に見たとおりです。しかし、プラグインやアドオンに類する追加的な製品、すなわち、単独での使用を意図せず、既にシステムにインストールされている他のプログラム(あなた自身のものであるか他の第三者のものであるかは問いません)に付属させて使う製品の場合は、いつ、そして、どこにインストールするべきかをまず知らなければなりません。この情報をユーザーに提供するように求めることは、エレガントでないのと同時に、おそらく多くの場合は危険でもあるでしょう。そのため、何をなすべきかを決定するために、レジストリに問い合わせたり、既にシステムに存在している .ini ファイルを調べたり、あるいは実際のフォルダやファイルを探す方法が必要になります。

これらの項目を検索する場合、結果はプロパティ(文字列変数)に保存されます。従って、プロパティを定義することから始めます(ここで使う Id が、最初のサンプルでインストール先のフォルダを意味するものとして使った名前と同じものであることに注意して下さい)。Property タグの中で、レジストリ・サーチを実行します。それぞれの属性の意味は自ずから明らかでしょう。

<Property Id="INSTALLDIR">
  <RegistrySearch Id='PiyoHogeRegistry' Type='raw'
                  Root='HKLM' Key='Software\Piyo\Hoge 1.0'
                  Name='InstallDir' />
</Property>

レジストリ・サーチが成功した場合(すなわち、指定されたレジストリ・エントリが実際に存在している場合)は、その値が INSTALLDIR プロパティに割り当てられ、私たちの目的に使用することが出来るようになります。このことを確認するために、上記の行を最初のサンプルの Media タグの後ろに追加して、SampleRegistry.wxs として保存します(または、前と同じように、SampleRegistry をダウンロードします)。コンパイルした後、インストールを開始する前に、レジストリに入って HKEY_LOCAL_MACHINE\SOFTWARE\Piyo\Hoge 1.0 キーを作成して下さい。そして、InstallDir という名前の文字列値を新規作成して、システム上のどこかに作成した空っぽのフォルダを指すように設定します。それから、ロギングを有効にしてインストーラを走らせて下さい。

† 訳註:SampleRegistry の日本語版は Sample-1-5-Registry.zip です。

すべて間違い無く出来ていれば、三つのサンプル・ファイルがこの新しいフォルダの中に出現します。また、(スタート・メニューとデスクトップにある)ショートカットも、今度は、この新しい場所を指し示すようになっていることも注目して下さい。

同様の情報が他のソースから来る場合もあります。\Windows\SampleRegistry.ini が以下のようになっていると仮定しましょう(システム・フォルダにあるこのようなファイルは、読み出すことしか出来ません)。

[Sample]
InstallDir=C:\InstallHere

前のセクションを以下の新しいものに置き換えます。

<Property Id="INSTALLDIR">
  <IniFileSearch Id='PiyoHogeIniFile' Type='directory'
                 Name='SampleRegistry.ini' Section='Sample'
                 Key='InstallDir' />
</Property>

場合によっては、単にフォルダ名を知るだけでは十分でなく、フォルダの中を見て、指定されたファイルがそこに存在することを確認しなければならないこともあります。Depth = n を使うと、指定されたPath から n レベル下まで探すように、インストーラに指示を与えることが出来ます。Depth 属性がゼロであるか、省略されている場合は、指定されたフォルダの中だけで探す(サブフォルダは見ない)ことを意味します。Path の中で角括弧を使って、インストーラに INSTALLDIR プロパティのを使うように指示しています — 角括弧で囲まれた名前は検索されて、見つかれば、実際の値で置き換えられます。見つからなかった場合は、文字列はそのまま変更されません。

<Property Id="FILEEXISTS">
  <DirectorySearch Id="CheckFileDir" Path="[INSTALLDIR]" Depth="0">
    <FileSearch Id="CheckFile" Name="Lookfor.txt" />
  </DirectorySearch>
</Property>

もしファイルが見つかれば、そのフル・パスが FILEEXISTS プロパティに割り当てられます。そうでなければ、FILEEXISTS プロパティは、割り当てられないままになります。このサンプル(前の RegistrySearch と、この断片の両方が必要になります)をビルドして、ロギングを有効にして走らせてみると、このことが確認出来ます。レジストリで指定したフォルダに Lookfor.txt を置いてから走らせると、ログの中に、ファイルのフル・パスを値として持つ FILEEXISTS への参照が出てくるようになります。

私たちはまだユーザー・インタフェイスを持っていませんが、ユーザーとの何らかの相互作用から値を受け取ってインストール・ロジックに引き渡すためのプロパティ(ユーザーが選択するインストール先フォルダや機能を示すプロパティなど)は、public なプロパティである必要がある、ということは、もう覚えておいても良いでしょう。プロパティを public なものにするためには、名前を全て大文字にする必要があります。

同時に、Windows Installer は、製品をアップグレードするための方法として、以前のインストールの特定のレジストリ・エントリを探すよりも、もっと良い方法を持っている、ということも知っておくべきです。可能なときは常にそちらの機能を使用するべきです — しかし、説明がそこに行き着くまで、もう少し辛抱して下さい。

1.6 条件付きインストール

私たちは既に起動条件を取り上げました。起動条件を使うと、指定された条件が偽である場合はインストール全体が中止されます。条件の使い方にはもう少し洗練されたやりかたもあります。プロセス全体を無効にせず、何をすべきかを決定するのに条件を使用するのです。条件付きインストールを使うために、以前の単一の機能を二つに分割します — そうすれば、どちらをインストールするべきかを条件によって決定することが出来ます。

<Feature Id='Complete' Level='1'>
  <Feature Id='MainProgram' Level='1'>
    <ComponentRef Id='MainExecutable' />
  </Feature>

  <Feature Id='Documentation' Level='1'>
    <ComponentRef Id='Manual' />
  </Feature>
</Feature>

今このサンプルをそのままビルドしても、何も面白い事は起きません。MainProgram 機能が EXE と DLL および関連するショートカットをインストールし、Documentation 機能が残りの PDF と関連するショートカットをインストールします。つまり、結局のところ、同じファイルがインストールされます。しかし、今まで言及しなかった Level 属性に注目して下さい。0 でない値はインストールするを意味し、0 の値はインストールしないを意味します。そして、既に取り上げた Condition を使用して、親の機能(条件を直接包含している Feature タグ)のレベルをその場で変更することが出来ます。すなわち、以下の例の場合であれば、条件がと評価される場合に、インストールを無効化することが出来ます。

<Feature Id='Documentation' Level='1'>
  <ComponentRef Id='Manual' />
  <Condition Level="0">NOT FILEEXISTS</Condition>
</Feature>

PDF とそのショートカットは、レジストリ・エントリが示しているフォルダに Lookfor.txt というファイルが見つかった場合にだけ、インストールされます。このことをあなたのコンピュータで確認するためには、SampleCondition をダウンロードして下さい。

† 訳註:SampleCondition の日本語版は Sample-1-6-Condition.zip です。

既に見た全ての条件も同じように使うことが出来ます。例えば、管理者でないユーザーに対して機能を無効化するためには、次のようにします。

<Feature Id='Documentation' Level='1'>
  <ComponentRef Id='Manual' />
  <Condition Level="0">NOT Privileged</Condition>
</Feature>

1.7 ファイル以外

外の実世界では、おそらく、最終的な格納場所にファイルをコピーするだけでなく、より多くのことが必要になってくるでしょう。レジストリのエントリーを作成するためには、コンポーネントの中に RegistyKey 要素を含めて、その中で RegistryValue を定義します。

<RegistryKey Id='HogeRegInstallDir'
             Root='HKLM' Key='Software\Piyo\Hoge 1.0'
             Action='createAndRemoveOnUninstall'>
  <RegistryValue Type='string' Name='InstallDir'
                 Value='[INSTALLDIR]'/>
  <RegistryValue Type='integer' Name='Flag'
                 Value='0'/>
</RegistryKey>

RegistryKeyAction 属性は、create または createKeyAndRemoveKeyOnUninstall です。そして Type は、string, integer, binary, expandable および multiString のどれかです。

multiString の場合は、一つまたは複数の RegistryValue を子要素に使って、それぞれの文字列を定義します。

<RegistryKey Id='HogeRegInstallDir'
             Root='HKLM' Key='Software\Piyo\Hoge 1.0'
             Action='createAndRemoveOnUninstall'>
  <RegistryValue Type='multiString' Name='InstallDir'
                 Value='[TARGETDIR]'/>
  <RegistryValue Type='multiString' Name='InstallDir'
                 Value='[INSTALLDIR]' Action='append'/>
  <RegistryValue Type='multiString' Name='InstallDir'
                 Value='[ProgramFilesFolder]' Action='append'/>
</RegistryKey>

アプリケーションが固有のファイル・データ・タイプを扱う場合は、そのためにファイルの関連付けを登録する必要があります。ProgId をコンポーネントの中に含めます。そして、FileId で、この拡張子のファイルを取り扱うように意図されているファイルを記述する File 要素の Id を参照するようにします。

<ProgId Id='PiyoHoge.xyzfile' Description='ぴよソフト ほげ データ・ファイル'>
  <Extension Id='xyz' ContentType='application/xyz'>
    <Verb Id='open' Command='Open' TargetFile='FileId' Argument='"%1"' />
  </Extension>
</ProgId>

このファイル・タイプにアイコンを割り当てるためには、アイコンをどこから取得すべきかを定義しなければなりません。

<ProgId Id='PiyoHoge.xyzfile' Description='ぴよソフト ほげ データ・ファイル'
        Icon='Hoge.ico'>

また、複数のアイコンを持つ実行ファイルやリソース・ファイルからアイコンを取得する場合は、どのアイコンであるかを指定することが出来ます。

<ProgId Id='PiyoHoge.xyzfile' Description='ぴよソフト ほげ データ・ファイル'
        Icon='Hoge.exe' IconIndex='1'>

そして最後に、.ini ファイルを書きたい場合は — 今日ではレジストリの方が人気がありますが — コンポーネントの中に次のように書きます。.ini ファイルは、インストールの対象フォルダではなく、常にシステム・フォルダの中に作成されます。

<IniFile Id="WriteIntoIniFile" Action="addLine" Key="InstallDir"
         Name="Hoge.ini" Section="Paths" Value="[INSTALLDIR]" />

次のレッスンでは、ユーザーが何をどこへインストールするかを決める機会を得ることが出来るように、ユーザー・インタフェイスを実装する方法を調べます。

1.8 削除時の孤児化

アプリケーションは、稼働中に、元のインストーラ・パッケージに入っていなかったファイルを作成することがあります(ユーザーのデータ・ファイル、ユーザーの設定、ログ・ファイル、その他)。製品をアンインストールするときに、それらのファイルを削除する必要がある場合もあります。個々のファイルを削除するためには、RemoveFile を使用します。

<Component>
  ...
  <RemoveFile Id='LogFile' On='uninstall' Name='Hoge10User.log' />
</Component>

On 属性によって、ファイルをいつ削除するかを決定します(指定できる値は、install, uninstall そして both です)。Name には、ワイルドカード文字を含ませることが出来ます。ファイルは、コンポーネントそのものと同一のフォルダに位置していなければなりません。このフォルダをオーバーライドするためには、Directory 属性あるいは Property 属性を使うことが出来ます。後者を使えば、インストーラ・パッケージを作成するときにはまだパスが分らないフォルダからでも、ファイルを削除することが可能になります。

アンインストール時にフォルダを削除するように指示する方法については、すでに調べました。しかし、インストーラでなくアプリケーションによって作成されるフォルダに関しては、それらを個別に指定する必要があります。

<Component>
  ...
  <RemoveFolder Id='LogFolder' On='uninstall' />
</Component>

ここでも、フォルダがコンポーネント自体のフォルダではない場合には、Directory または Property を使うことが出来ます。

Copyright © 2004-2012, Gábor DEÁK JAHN, Tramontána
何よりもコメントと寄稿を歓迎します。
日本語翻訳 Copyright © 2010, 2013, Nobuo Kihara, softark
読みやすくて正確な翻訳を目指しましたが、解釈の誤りと技術的な間違いが含まれていないという保証は出来ません。間違いの指摘や修正案の提示を歓迎します。github のリポジトリ ( https://github.com/softark/wix-j ) をご利用下さい。