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

Lesson 4 アップグレードとモジュラー化

前回のレッスンを終って、インストーラ・パッケージに関する限りでは、考えられる全てのことについて、やり方を学びました。そうですよね? 結局の所、Windows Installer が直接的な解決を提供してくれない場合でも、昔なじみの信頼できるコンパイラに点火して自分で書けば良いのだ、と。そうですよね?

ある意味ではその通りです。しかし、ある意味ではそうではありません。私たちは元のインストーラ・パッケージを作る方法は知っていますが、アップグレードを出荷するとなったら何をすればよいのでしょうか? あるいは、パッチは? ユーザーに前のバージョンをアンインストールして新しいのを再インストールしてくれるように頼むのでしょうか? そして、ユーザーがソフトウェアを使っている間に設定したデータはどうなるのでしょう? 最初から全部をやり直してもらうのでしょうか?

もちろん、そうではありません。と言うわけで、Windows Installer がそういう問題を解決するのにどのような支援をしてくれるのか、見ていきましょう。Windows Installer は、製品の変更を三つのカテゴリに分けています。

メジャー・アップグレードを使うこと(換言すると、Product GUID を変更すること)を Windows Installer が強要する場合がいくつかあります — 古いバージョンと新しいバージョンをユーザーのコンピュータに共存させたい場合。どんな理由であれ、.msi ファイルの名前を変更する場合。パッケージに含まれるどれかの Component の GUID を変更する必要がある場合。コンポーネントを削除した場合。機能の階層構造に何らかの変更がある(子の機能が親から出て行ったり、親の機能が新しい子を追加したりする)場合 — これらの場合には、メジャー・アップグレードにしなければなりません。

という訳で、メジャー・アップグレードは輪郭がはっきりしていますが、スモール・アップデートとマイナー・アップグレードの境界線はいくらか不鮮明です。それについては、こういう風に考えて下さい。将来の製品において、現在のバージョンと新しいバージョンを区別する必要が少しでもあるなら、マイナー・アップグレードに一票を投じましょう。そして、いつものように、間違うなら安全な側に間違う方が良いですね。

GUID を変更するときは、古いのを記録しておいて下さい。Windows Installer の精巧なアップグレード機能を使う時に、前の GUID が必要になります。

4.1 古いのを調べる

何らかのアップデートやアップグレードをするときに最初にすることは、変更したい前のバージョンが有ることを確認することです。Windows Installer はそれを確認するために Product タグの UpgradeCode 属性を頼りにします。そのため、現在のプログラムを後でアップグレードする予定が無い場合であっても、常に製品に UpgradeCode を持たせておくことが非常に重要です。将来のことは決して分りませんし、一旦外に出してしまってからでは、もう出荷し直すことは出来ません。製品を同じアップグレード・バージョンでアップグレードしようとする限りは、同一の UpgradeCode GUID を保持してください。通常の場合、このことは、Version 1.x の全てに一つのコード、Version 2.x にもう一つ別のコード、、、ということを意味します。

<?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'
           Version='1.0.0' Manufacturer='ぴよソフト'
           Language='1041' Codepage='932'>

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

      ...

    </Package>
  </Product>
</Wix>

今回の SampleUpgrade は、二つのインストーラ・パッケージから成り立っています。両方とも、簡単な UI のインストーラ、SampleWixUI を元にしています。

† 訳註:SampleUpgrade の日本語版は Sample-4-1-Upgrade.zip です。

SampleUpgrade の第二のバージョンは、配置されたファイルの一つを新しいバージョンで置き換えるためのものです。私たちはこれをマイナー・アップグレードだと考えて、Version を変えています。外見上の変更が、人間が読むことが出来る NameDescription に対して加えられていることは言うまでもありません。

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

  <Package Id='*' Keywords='インストーラ'
           Description="ぴよソフト's ほげ 1.0.1 アップデータ" ... >

製品のどのバージョンをこのアップグレードによって置き換える予定なのか、ということについての記述も必要です。Upgrade タグの Id 属性が、元のインストーラ・パッケージ(この例では古い方の SampleUpgrade)の UpgradeCode GUID を参照しています。内部の UpgradeVersion タグは、更新対象となるバージョンの詳細を記述しています。OnlyDetect は、インストーラに、前の製品を削除しないように指示しています(思い出してください。私たちはマイナー・アップグレードをやっています。製品の古いバージョンを維持しながら、ファイルを一つだけ置き換えるのです。もし、メジャー・アップグレードをやっているのだとしたら、そうする代りに、1.0.0 を削除して、1.1.0 をインストールするでしょう)。

MinimumMaximum が、このアップグレード・パッケージで更新することになるバージョンの範囲を示しています。IncludeMaximumIncludeMinimum は、境界値が範囲に含まれるか否かを指定します(IncludeMaximum = yesMaximum で指定されたバージョンより低いか等しいバージョンを探すことを意味し、IncludeMaximum = noMaximum より低い ものだけを探すことを意味します)。これらの属性には既定値がありますが、ここでは、既定値には頼らず、常に明示的に記述することにします – 明解さと自己完結的な説明性のためには、省略せずに書く方が良いでしょう。

  <Upgrade Id='YOURGUID-7349-453F-94F6-BCB5110BA4FD'>
    <UpgradeVersion OnlyDetect='yes' Property='SELFFOUND'
                    Minimum='1.0.1' IncludeMinimum='yes'
                    Maximum='1.0.1' IncludeMaximum='yes' />
    <UpgradeVersion OnlyDetect='yes' Property='NEWERFOUND'
                    Minimum='1.0.1' IncludeMinimum='no' />
  </Upgrade>

† 訳註:実際にここで探そうとしているのは、更新する対象のバージョンではありません。そうではなくて、更新する必要のないバージョン、すなわち自分自身(SELFFOUND)、および、更新してはいけないバージョン、すなわち自分よりも新しいバージョン(NEWERFOUND)を探そうとしています。

ソース・ファイルの中で Upgrade タグを使うと、新しい標準的なアクション、FindRelatedProducts が導入されます。このアクションは、もし有れば、LaunchConditions の直後に走るようにスケジュールされます。必要な場合には、InstallExecuteSequence タグの中で、スケジュールを変更することも出来ます。

FindRelatedProductsUpgrade タグの中を全部見て、リスト・アップされている全てのバージョンを探します。該当するバージョンが存在した場合は、UpgradeVersion タグで指定されているプロパティ(この例では SELFFOUND または NEWERFOUND)に、そのバージョンの Product GUID が追記されます。言うまでもなく、Windows Installer が探すことが出来るのは、指定された UpgradeCode を持っている .msi パッケージでインストールされた製品だけです — UpgradeCode を常に指定することが重要だという意味がこれで分ったでしょう。

もし、地域化されたソフトウェア・パッケージを開発しているのであれば、UpgradeVersionProduct タグの両方で Language 属性を指定することが可能です。言語を指定するためには、いつものように Windows の言語識別子の数値を使って下さい。言語を指定すると、FindRelatedProducts は、該当する言語の製品だけを探します。

私たちは、チェックが走った後で、関連するプロパティの存在および値に基づいて、適切な行動を取ることが出来ます。

  <CustomAction Id='AlreadyUpdated'
                Error='[ProductName] は既に 1.0.1 に更新されています。' />
  <CustomAction Id='NoDowngrade'
                Error='[ProductName] の新しいバージョンが既にインストールされています。' />

  <InstallExecuteSequence>
    <Custom Action='AlreadyUpdated' After='FindRelatedProducts'>SELFFOUND</Custom>
    <Custom Action='NoDowngrade' After='FindRelatedProducts'>NEWERFOUND</Custom>
  </InstallExecuteSequence>

どういう理由かは知りませんが、スモール・アップデートとマイナー・アップグレードは、.msi ファイルをダブル・クリックするだけでは走らせることが出来ません。"この製品の別のバージョンが既にインストールされています" というエラーが出るのです。知っとるわぃ、ボケ ... とにかく、コマンド・ラインから起動しなくてはならないのです。

msiexec /i SampleUpgrade.msi REINSTALL=ALL REINSTALLMODE=vomus

どうやってこんな事を平均的なユーザーにやって貰おうか、などと尋ねるのはやめて下さいね。Autorun.inf ファイルから起動したり、起動用の Setup.exe ラッパー・シェルをひねり出したりする方が良いですよ。

ご覧のように、このアップグレード・バージョンは双方向に正しく動作します。これは自分より古いパッケージを置き換えますが、それだけでなく、将来、プログラムがもっと先まで(例えば 1.0.2 以降に)更新されていた場合でも、対処出来ます。より新しいバージョンを 1.0.1 にダウングレードして戻すようなことはしません。絶対確実に間違いが無いようにするためには、このことは前もって計画しておかなくてはいけません。一番初めに出荷するインストーラであっても、このセーフティー・ロックが組み込まれていなくてはなりません(その事を現在のサンプルの古い方のバージョンで確認して下さい)。

  <Upgrade Id='YOURGUID-7349-453F-94F6-BCB5110BA4FD'>
    <UpgradeVersion OnlyDetect='yes' Property='NEWERFOUND'
                    Minimum='1.0.0' IncludeMinimum='no' />
  </Upgrade>

  <CustomAction Id='NoDowngrade'
                Error='[ProductName] の新しいバージョンが既にインストールされています。' />

  <InstallExecuteSequence>
    <Custom Action='NoDowngrade' After='FindRelatedProducts'>NEWERFOUND</Custom>
  </InstallExecuteSequence>

† 訳註:この章で説明されている「セーフティー・ロック」機構は、実際には、プロダクト・コードが異なるパッケージ同士の間でなければ、期待している通りの動作はしません。

スモール・アップデートおよびマイナー・アップグレードの場合、すなわち、プロダクト・コードが同じパッケージ同士の間では、"msiexec /i SampleUpgrade.msi REINSTALL=ALL REINSTALLMODE=vomus" によってパッケージを起動する必要があることは、本文で述べられている通りです。この場合、Windows Installer はメンテナンス・モードで走ることになります。ところが、メンテナンス・モードにおいては、FindRelatedProducts何もせずに帰ってきてしまいます。つまり、SELFFOUNDNEWERFOUND も設定されず、結果として、'AlreadyUpdated''NoDowngrade' も、実行されることは決してありません。具体的に言うと、1.0.1 をインストールした後に 1.0.0 のインストーラを上記のコマンド・ラインで起動すると、何の警告も表示せずに、最後まで走ってしまうのです。

"REINSTALLMODE=vomus" の場合、Windows Installer がファイルのバージョン番号(ファイルにバージョン番号がある場合)や更新日付を見て、ファイル単位でダウングレードを防止しますので、致命的な実害は生じません。しかし、実際の動作と、ユーザーに対するフィードバックとの間に無視できないズレが生じますので、気持ちが悪いことは否定できません。

なお、プロダクト・コードが異なるパッケージ同士の間では、この「セーフティー・ロック」機構が有効に働きます。

4.2 自分自身を置き換える

もし私たちの意図がメジャー・アップグレード、つまり、前のバージョンを完全かつ自動的に削除して新しいバージョンを入れる、というものである場合、必要なことは、OnlyDetectno に設定することと、バージョン番号をそれに応じたものに変更することだけです。このとき Minimum は現在のバージョンによって置き換えることを許す最初のバージョンを指定します(この最小値は範囲に含めます)が、Maximum は現在のバージョン番号を指定することが出来ます(ただし範囲には含めません)。このようにすると、最初のバージョンから、現在の一つ前のバージョンまでのすべてのものが、現在のバージョンをインストールするときに自動的に削除されます。この同じインストーラを最初に使うインストーラとして使用することも出来る、ということにも注意してください。前のバージョンを見つけた場合は、前のバージョンを削除して現在のものをインストールします。しかし、クリーンなシステムで起動された場合は、単に現在のバージョンのアプリケーションをインストールするだけです。アップグレードのインストーラとフルセットのインストーラを個別に作る必要はありません。

<Upgrade Id='YOURGUID-7349-453F-94F6-BCB5110BA4FD'>
  <UpgradeVersion OnlyDetect='no' Property='PREVIOUSFOUND'
                  Minimum='1.0.0' IncludeMinimum='yes'
                  Maximum='3.0.0' IncludeMaximum='no' />
</Upgrade>

前のバージョンの削除は完全に自動的に行われます。どんな理由であれ、前のバージョンが削除される時に何らかの操作をする必要がある場合は、UPGRADINGPRODUCTCODE プロパティを条件にしたカスタム・アクションを書いて対応することが出来ます。Windows Installer は、自動的な削除が行われるときにだけ、このプロパティを設定します。プログラムの追加と削除によってアプリケーションが手作業で削除される場合には、このプロパティを設定しません。

<InstallExecuteSequence>
  <Custom Action=' ... ' After=' ... '>UPGRADINGPRODUCTCODE</Custom>
</InstallExecuteSequence>

† 訳註:本文では言及されていませんが、メジャー・アップグレードでは、RemoveExistingProducts カスタム・アクションを InstallExecuteSequence の中にスケジュールする必要があります。実際、そうしないと、コンパイルが通りません。

SDK ドキュメントによれば、RemoveExistingProducts をスケジュールするタイミングとしては、以下の四つの選択肢があります。すなわち、(1) InstallValidate の後で、InstallInitialize の前、(2) InstallInitialize の直後、(3) InstallExecute または InstallExecuteAgain の後で、InstallFinalize の前、そして、(4) InstallFinalize の直後の四つです。ドキュメントには、第四の位置が最も効率的であると述べられています。

(3) と (4) の場合は、新バージョンをインストールした後に旧バージョンをアンインストールします。このとき、新旧ともに同一のパスを持つファイルが有る場合は、インストール時には上書きで更新し、アンインストール時には削除しないようにします。このことを Windows Installer はコンポーネントに対する参照カウントを使うことによって実現しています。従って、メジャー・アップグレードにおいても、コンポーネントの一貫性は非常に重要です。同一名のファイルを持つコンポーネントの GUID を変更したり、逆に、含まれているファイルの名前を変更したのにも関わらずコンポーネントの GUID を以前と同じままにしたりすると、コンポーネントに対する参照カウントと実際のファイルとの関係に食い違いが生じて、インストールしたはずのファイルがインストールされていなかったり、削除した筈のファイルが孤児となって残っていたりする結果になることがあります。

前章で取り上げられたスモール・アップデートおよびマイナー・アップグレードが、作成においても使用においても、いろいろと厄介な問題がある形式だということを考えると、プログラムの更新を配布する最も手軽で現実的な方法は、ここで説明されているメジャー・アップグレードであると言って良いでしょう。ただし、旧バージョンから新バージョンへの移行時にどうやってユーザー・データを保護するか、という問題は残ります。

なお、前章で説明された「セイフティー・ロック」機構は、メジャー・アップグレードでは有効に機能します。本文では、自分より古いバージョンを探して置き換える、ということだけをやっていますが、自分より新しいバージョンが見つかればメッセージを表示して動作を中止する、ということも積極的に行うべきでしょう。ただし、「自分自身と同じバージョンが既にインストールされている場合にアップグレードを中止する」という機能は、実装しても働きません。その場合は、Package の GUID が同じなので、メンテナンス・モードで動作することになり、バージョンの比較は行われません。

前章の日本語版サンプル Sample-4-1-Upgrade.zip に、メジャー・アップグレードのサンプルを含めていますので、参照してください(ビルドするためには、WixUtilExtension をリンクする必要があります)。

4.3 パッチワーク

更新しなければならない小さなファイルが一つか二つ混じっているというだけの理由で、何メガバイトものファイルを入れたアップグレード・インストーラ・パッケージを作ることは、あまり効率的だとは言えないでしょう。従来から、そういう場合には、いつでも、パッチの方が良い解決策でした。パッチは、基本的には旧バージョンと新バージョンの異なる部分を入れたもので、ユーザーのコンピュータにある古いファイルを魔法のごとく自動的に新しいものに変えることが出来るものです。パッチ・パッケージには新規に配置するファイルを入れることも出来ます。

WiX ツールセットもパッチ・インストーラ・パッケージ(.msp ファイル)を作成することが出来ます。パッチ・インストーラ・パッケージは、二つの通常のインストーラ・パッケージ(一つは、バグのある古いファイルを持つ元のパッケージ、もう一つは、修正されたファイルを持つ新しいパッケージ)から作成されます。ダウンロードできる SamplePatch の中には、ユーザー・インタフェイスを持たない非常に簡単な二つのインストーラ・パッケージが入っています。両方ともファイルを一つだけインストールしますが、そのファイルが元のバージョンとパッチを当てられたバージョンで変ります。ソース・ファイルの詳細は、もう今では、完全に見慣れたものになっている筈です。Error.wxsFixed.wxs のソース・ファイルの唯一の違いは、Source のファイルの参照だけです。

† 訳註:SamplePatch の日本語版は Sample-4-3-Patch.zip です。

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

対するに、

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

パッチは第三のソース・ファイルから作成されます。これは、前と全く同じように、XML ファイルですが、その内容は今まで書いたファイルとは異なるものです。

<?xml version='1.0' encoding='utf-8'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
  <Patch AllowRemoval='yes' Manufacturer='ぴよソフト'
         MoreInfoURL='www.piyosoft.co.jp' DisplayName='ほげ 1.0.0 パッチ'
         Description='小さな更新パッチ' Classification='Update'
         Codepage='932'>

    <Media Id='5000' Cabinet='Sample.cab'>
      <PatchBaseline Id='Sample' />
    </Media>

    <PatchFamily Id='SamplePatchFamily' Version='1.0.0.0' Supersede='yes'>
      <ComponentRef Id='MainExecutable' />
    </PatchFamily>

  </Patch>
</Wix>

Classification 属性は、Hotfix, Security Rollup, Critical Update, Update, Service Pack または Update Rollup のどれかです。AllowRemoval は、ユーザーが後でパッチをアンインストールすることが出来るかどうかを決定します。

PatchFamily タグが、パッチを適用される項目を包み込んでいます。DiskId は、インストーラ・パッケージの中にあるどの Media/@Id よりも大きくなくてはいけません。そして、Supersede は、このパッチが同じファミリーに属する以前のすべてのパッチに取って代るものであるか否かを決定します。

ビルド作業は以前のプロジェクトよりも少し複雑になります。最初に、ベースになる二つのパッケージを通常の方法でビルドします。二つとも、それ自身のフォルダに入れます。

candle.exe Error.wxs
light.exe -out Error\Product.msi Error.wixobj

candle.exe Fixed.wxs
light.exe -out Fixed\Product.msi Fixed.wixobj

次に、別の WiX ツール、torch を使って、二つのインストーラ・パッケージの間のトランスフォームを作成します。コマンド・ラインの引数で、プログラムに対して、Windows Installer の形式(.msi.mst)ではなく、WiX 自身の形式である .wixpdb.wixmst を使うように指示を与えます。

torch.exe -p -xi Error\Product.wixpdb Fixed\Product.wixpdb -out Patch.wixmst

さらに、いつもの WiX のコンパイラとリンカを使ってパッチ・パッケージをビルドしなければなりませんが、今回は、出力形式が通常のものとは違う .wixmsp になります。リンカに対してこのファイル形式で出力するように指示する必要はありません。リンカ自身がソース・ファイルの内容に従って出力形式を自動的に決定します。

candle.exe Patch.wxs
light.exe Patch.wixobj

そして、最後に、前のステップの出力結果と、少し前に作ったトランスフォームから、実際の Windows Installer のパッチ・パッケージをビルドします。パッチ作成を担当する WiX ツールである Pyro に対しては、トランスフォーム・ファイルの名前だけでなく、対応する PatchBaseline/@Id 属性も、コマンド・ラインで指定してやる必要があります。

pyro.exe Patch.wixmsp -out Patch.msp -t Sample Patch.wixmst

Patch.msp が実際に配布されるパッチ・インストーラになります。これをテストするためには、最初にオリジナルのパッケージ(Error/Product.msi)をインストールし、次にパッチを当てます。

msiexec /p Patch.msp

... そしてファイルが本当に新しいバージョンに変っていることを確認して下さい。次に、プログラムの追加と削除 を開いて、更新プログラムの表示にチェックを入れ、最初にパッチを削除し(変更されたファイルは元のファイルに戻ります)、次にプログラムそのものを削除してください。

管理者であれば、//server/Patch.msi に置かれている管理ソース・イメージを、パッチによって新しいソース・イメージに更新することが出来ます。この新しいソース・イメージは、更新されたフルセットの配布メディアから管理インストールによって作成された場合と同一のソース・イメージになります。

msiexec /a //server/Patch.msi /p Patch.msp

プログラムを使用するワークグループのメンバーは、更新を受け取るために、新しい管理ソース・イメージからアプリケーションを再インストールしなければなりません。アプリケーションを完全に再インストールして、更新された .msi ファイルをユーザーのコンピュータにキャッシュとして保存するために、ユーザーは以下のコマンドを実行します。

msiexec /fvomus //server/Patch.msi

4.4 断片

フラグメント(Fragment)は、大きなインストール・プロジェクトを分割する方法を提供してくれます。基本的には、フラグメントは WiX ソースのひとかたまりを包み込むラッパーです。中身は、好みに応じて、どんなに簡単でも、どんなに複雑でも構いません。プログラム開発で使用するオブジェクト・ファイルやライブラリと同じように、フラグメントは他の製品のインストーラ・パッケージにリンクして組み込むことが出来ます。もし WiX の二つのツール、CandleLight をコンパイラとリンカであると考えるなら、フラグメントは、ちょうどソース・コード・モジュールのように、それぞれ独立してオブジェクト・コード(.wixobj ファイル)にコンパイル出来るものです。大規模なインストーラ・パッケージのリビルドは、おなじみの makefile の手法を使うことで、劇的に加速することが出来ます。makefile の手法では、最後のコンパイルの後で変化があった .wxs ソース・ファイルだけを再コンパイルし、.wixobj ファイルを、新しいものも、古いものも、まとめてリンクして、最終的なパッケージを作成します。

チュートリアルの中ではそんなに大規模で複雑なプロジェクトをサンプルにすることは出来ませんので、SampleFragment は多少わざとらしいものにならざるを得ません。最初のインストーラを再利用しますが、論点を説明するために、いくつかの部分を切り出して、独立したフラグメントに外部委託します。ユーザー・マニュアルのファイルをインストールするコンポーネントはメインのソースから削除しました。従って、下記の参照は指し示すものが無いものになります。

† 訳註:SampleFragment の日本語版は Sample-4-4-Fragment.zip です。

  <Feature Id='Complete' Title='ほげ 1.0' Description='完全パッケージ。'
    ...

    <Feature Id='Documentation' Title='説明書'
             Description='取扱説明書。' Level='1'>
      <ComponentRef Id='Manual' />
    </Feature>
  </Feature>

削除したコンポーネントは、それ自身のファイルに入れて、Fragment タグで囲みます。ここでは、メインのソース・ファイルで既に宣言している Directory に対しては、参照するだけにします。なぜなら、同じものを二つの箇所で宣言することは出来ないからです。フラグメントの中に代理を置くことが出来るものは、すべて変異形のタグを持っています。別の場所で定義された機能を参照するためには FeatureRef を使い、プロパティを参照するためには PropertyRef を使います。

<?xml version='1.0' encoding='utf-8'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
  <Fragment Id='FragmentManual'>
    <DirectoryRef Id='INSTALLDIR'>
      <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>
    </DirectoryRef>

  </Fragment>
</Wix>

私たちは既にこの二つのファイルをコンパイルし、リンクして、一つのインストーラ・パッケージにすることが出来ます。この二つのコンパイルの単位を一緒にリンクするためには、ソースには一行も加える必要が無い、ということに注意して下さい。一つのファイルからもう一つのファイルで定義されているコンポーネントを参照している、という事実さえあれば、期待する通りのリンクが生じます。フラグメントの中にある一つの要素を参照すると、フラグメント全体が開かれて、フラグメント内の全ての要素が直ちにアクセス可能になります。この動作は、いつものプログラミング言語で慣れているものとは異なっています。フラグメントをリンクすると、別の場所で定義されている要素を使うことが出来るようになるだけでなく、参照されているフラグメントの要素を一つでも使うと、常に、全部の要素が有効になるのです。

サンプルをビルドするためには、下記のコマンドを使います。

candle.exe SampleFragment.wxs Manual.wxs
light.exe -out SampleFragment.msi SampleFragment.wixobj Manual.wixobj

フラグメントの用途は多岐にわたっていて、単一のセットアップ・プロジェクトの中で使えるだけでなく、異ったプロジェクト間で共通の項目を共有するために使うことも出来ます。例えば、関連する複数のアプリケーションがあって、一つないし複数の共通ファイルを共有している場合(例えば、デバイス・ドライバーや、その他の DLL で提供する機能などを共有している場合)、一つのアプリケーションを削除しても、他のアプリケーションがまだ必要としている共通ファイルは決して削除しないようにする必要があるでしょう。

こういう場合、共通ファイルをそれ自身の独立したフラグメントに入れて、別々のアプリケーションの全てのセットアップから、そのフラグメントを参照して下さい。コンポーネントが同一である(従って、コンポーネント GUID が同一である)ため、Windows Installer は、この共通ファイルを必要とする全てのアプリケーションを記録しておくことが出来ます。さらに、新しいバージョンが決して古いもので上書きされないように、バージョン管理のルールを共通ファイルに対して適切に適用することが出来ます。

4.5 融合するもの

フラグメントを使用すると、大きなパッケージを管理可能なソース・コードの固まりに分割することが出来て、開発者の共同作業やコードの再利用が可能になります。従って、フラグメントは、WiX ソース・コードが共有可能であり、また、実際に共有されるであろう社内開発に最も適していると言えます。しかし、第三者に完全なインストーラ・パッケージを提供することが出来る、もっと強力なもう一つのメカニズムが存在します。すなわち、マージ・モジュールです。例えば、あなたの製品 A が他のベンダーの製品 B に依存しているとしましょう。彼らの製品をインストールするために作られたマージ・モジュールを使えば、彼らの製品をあなたの製品の一部として、同時に二つをインストールすることが出来るようになります。

マージ・モジュールを記述する方法は、今までやってきたスタンドアロンのソース・ファイルの場合と非常によく似ています。ただし、Product ではなく、Module タグを定義します。また、これまでのパッケージとは違って、一意の GUID は、私たち自身が定義しなければなりません。

<?xml version='1.0' encoding='utf-8'?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Module Id="File1" Language="1041" Codepage="932" Version="1.2.3">

    <Package Id="YOURGUID-8DEE-4410-990A-1802896C4209" InstallerVersion="100"
             Languages="1041" Manufacturer="ぴよソフト" SummaryCodepage="932"
             AdminImage="no" />

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

              <Component Id="File1" Guid="YOURGUID-CF0E-40AB-ACC5-0E9A5F112628">
                <File Id="File1.txt" Name="File1.txt"
                      Source="File1.txt" KeyPath='yes' />
              </Component>

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

  </Module>
</Wix>

サンプルでは、第二のマージ・モジュールも使います。ソース・ファイルは前のものとほとんど同じですが、次の点で違っています。すなわち、配置すべきファイルとして違うファイルを参照し、この第二のモジュールが第一のモジュールに依存していることを示すために Dependensy タグを含めます。この依存関係を示すために、Module ID 識別子に Package GUID を追加したものを使います — Package GUID は、元のハイフンをアンダースコアで置き換えたものにしなければいけません。

  <Module Id="File2" Language="1041" Codepage="932" Version="1.2.3">
    ...
    <File Id="File2.txt" Name="File2.txt" Source="File2.txt" KeyPath='yes' />
    ...
    <Dependency RequiredId="File1.YOURGUID_8DEE_4410_990A_1802896C4209"
                RequiredLanguage="1041" RequiredVersion="1.2.3" />
  </Module>

結合されたインストーラ・パッケージを作るために、通常のスタンドアロンの WiX ソース・ファイルを書きます。

<?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' />

    <Media Id="1" Cabinet="product.cab" EmbedCab="yes" />

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

通常のコンポーネントの代りにマージ・モジュールを参照します。SourceFile.msm ファイルを指し示します。

            <Merge Id="file1" Language="1041"
                   SourceFile="Module1.msm" DiskId="1" />
            <Merge Id="file2" Language="1041"
                   SourceFile="Module2.msm" DiskId="1" />

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

    <Feature Id="Msm" Title="Msm" Level="1">
      <MergeRef Id="file1" />
      <MergeRef Id="file2" />
    </Feature>

  </Product>
</Wix>

完全な SampleMergeModule をダウンロードすることが出来ます。マージ・モジュールは、個別にビルドする必要があります。

† 訳註:SampleMergeModulet の日本語版は Sample-4-5-MergeModule.zip です。

candle.exe Module1.wxs
light.exe Module1.wixobj

candle.exe Module2.wxs
light.exe Module2.wixobj

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