2つのインストーラーが同じファイルを同じ場所にインストールすると
同じ場所に同じファイル名で2つのインストーラーがファイルをインストールしたらどうなるでしょう。例えば、以下のようなケースです。
この例では製品Aと製品Bのどちらも、ProductCommon
フォルダにProduct.bin
ファイルをインストールします。そして、何も考えずに別々に両方のインストーラーを作ったとします。そして、製品Aと製品Bの両方をインストール後に一方をアンインストールすると、ProductCommon
フォルダは消えてしまいます。残された方の製品はファイルが足りないため正しく動作できなくなってしまいます。
こうなってしまうのは、コンポーネント作成時のルールに反しているからです。マイクロソフトのドキュメント、Organizing Applications into Componentsには、下記のような記述があります(機械翻訳してあります)。
リソースを同じ名前とターゲットの場所にインストールする 2 つのコンポーネントを作成しないでください。複数のコンポーネントでリソースを複製する必要がある場合は、コンポーネントごとにその名前またはターゲットの場所を変更します。このルールは、アプリケーション、製品、製品バージョン、および企業全体に適用する必要があります。
最後の一文はわかりにくい表現ですが、このルールが一つのインストーラーの中で守られていれば良いというわけではなく、一つのWindows環境で使用するすべてのMSI形式のインストーラー間で守られる必要がある、と言っています。このルールを守らなかった場合、Windows Installerの仕組みが破綻する、という恐ろしい結果を招きます。ですので、Windowsが持つ既存のフォルダにファイルを配置する際は、他のアプリと極力名前が重複しないよう気をつける必要があります。もし、誰か別の人が作ったインストーラーが同名のファイルを同じ場所に配置していたら、そこでインストーラーの仕組みが破綻してしまいます。これを避ける手段で多いのが、組織名や製品名を含むフォルダを先に配置し、その下にファイルを置く方法です。
コンポーネント設定を合わせるとファイルを共有できる
Windows Installerでこのような製品構成を実現するには、製品Aと製品BでProductCommon
フォルダをインストールするコンポーネントのすべての設定を合わせます。コンポーネントのGUIDや属性、インストールするファイルなど、すべて製品Aと製品Bで同じにします。WiXでインストーラーを作っているなら、一番手っ取り早い方法は、製品AのProductCommon
フォルダをインストールするComponentエレメント全体を、そのまま製品Bのソースにコピーすることです。メンテナンス性やミスの防止を考えるなら、製品間で共通のコンポーネントは別ファイルに分離して、双方から同じファイルを利用したほうが良いでしょう。
WiXを使った実例を示します。以下ソースの上側が製品A、下側が製品Bです。両者で同じ内容のcCommon
というIdのコンポーネントを利用しています。
このようにすることで、一つのPCに製品Aと製品Bがインストールされているときに、一方をアンインストールしても、ProductCommon
フォルダとその中のファイルが消えることがなくなります。
MSIを使わないインストーラーのファイル共有
ここまで説明してきた仕組みはWindows Installerの仕組みなので、MSI形式のインストーラーだけで利用可能です。しかし世の中には、Windows Installerを使わないインストーラーも存在します。こういったインストーラーが伝統的に利用してきたのが、レジストリに存在する shared DLL reference counter です。
64bit版のWindowsでは、64bitアプリ向けに下記のレジストリキーが、
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\SharedDLLs
32bitアプリ向けに下記のレジストリキーが
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\SharedDLLs
使われます。インストーラーはインストール時、このキーの下に共有登録したいファイルへのフルパスを名前にして項目を作成します。新規に追加するのであれば値は1
に、同じ名前がすでに存在するなら値に1
を加えます。例えば、以下のようになります。
そして、アンインストール時にこのレジストリの値が1
であればファイルを削除しレジストリ項目を削除、2
以上であればファイルを削除せずレジストリの値から1
を引きます。このレジストリキーの内容を確認すれば、値が2
以上のファイルは、2つ以上のインストーラーによってインストールされている、ということがわかります。
shared DLL reference counter を使用してファイルの共有管理をしているインストーラーは、このような仕組みで、他のインストーラーが同じファイルをインストールしているとき、アンインストールでファイルを削除しないようにしています。
この方式には欠点があります。アンインストール時にレジストリ項目の値から1
を引くことを忘れると、使われないファイルがPCに残ってしまいます。それもあって、アンインストールの実装には十分気をつける必要があります。1
MSIでも shared DLL reference counter を利用できる
MSIには、MSIを使わないインストーラーとファイル共有するために、shared DLL reference counter を利用する機能があります。ComponentエレメントでSharedDllRefCount="yes"
と指定すると、キーパスに指定したファイルがSharedDLLsレジストリに登録されます。例えば、このような指定をすると
<Component Id="prg" Guid="{466B1C91-5BFE-412A-9D9E-23A5745986CB}" Win64="yes" SharedDllRefCount="yes">
<File Id="fCalc" Source="C:\Windows\System32\calc.exe" KeyPath="yes" />
<File Id="fPaint" Source="C:\Windows\System32\mspaint.exe" />
</Component>
先ほど例示したようにcalc.exe
がレジストリに登録されます。
この仕組みを使う際には、アンインストール時に直感に反する動作をするので注意が必要です。レジストリのcalc.exe
項目の値が2
だった場合、直感的にはアンインストール後calc.exe
だけが残るように思ってしまいますが、実際にはコンポーネントでインストールするもの全体が残ります。この例ではcalc.exe
とmspaint.exe
の両方が残ります。MSIにとって shared DLL reference counter の内容はファイルに対してではなく、コンポーネントに対して働くのです(だからComponentエレメントに指定するんですね)。将来のミスや勘違いに配慮するなら、このレジストリでファイル共有する場合、コンポーネントには共有したいファイルを一つだけ置く方が良さそうです。
-
ちゃんとしたインストーラー作成ツールを利用していれば、自動的に処理が埋め込まれると思いますが。昔はインストーラーの不具合でこの数値が壊れて、正しくアンインストールできないケースがあった、と聞いたことがあります。 ↩