Windows Installer手引書 Part.34 ２つのインストーラーで同じファイルを同じ場所にインストールする

[**前の記事へ**](https://qiita.com/tohshima/items/f509498fbf23b9facc8b)　　[**目次へ**](https://qiita.com/tohshima/items/dc4c4721d50a2ad70c7a)　　次の記事へ

## ２つのインストーラーが同じファイルを同じ場所にインストールすると

同じ場所に同じファイル名で２つのインストーラーがファイルをインストールしたらどうなるでしょう。例えば、以下のようなケースです。
![Part34Files.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83170/5b66aec2-432e-cd46-9015-4b57aab5103d.png)
この例では製品Ａと製品Ｂのどちらも、`ProductCommon`フォルダに`Product.bin`ファイルをインストールします。そして、何も考えずに別々に両方のインストーラーを作ったとします。そして、製品Ａと製品Ｂの両方をインストール後に一方をアンインストールすると、`ProductCommon`フォルダは消えてしまいます。残された方の製品はファイルが足りないため正しく動作できなくなってしまいます。

こうなってしまうのは、コンポーネント作成時のルールに反しているからです。マイクロソフトのドキュメント、[Organizing Applications into Components](https://learn.microsoft.com/en-us/windows/win32/msi/organizing-applications-into-components)には、下記のような記述があります（機械翻訳してあります）。

> リソースを同じ名前とターゲットの場所にインストールする 2 つのコンポーネントを作成しないでください。複数のコンポーネントでリソースを複製する必要がある場合は、コンポーネントごとにその名前またはターゲットの場所を変更します。このルールは、アプリケーション、製品、製品バージョン、および企業全体に適用する必要があります。

最後の一文はわかりにくい表現ですが、このルールが一つのインストーラーの中で守られていれば良いというわけではなく、一つのWindows環境で使用するすべてのMSI形式のインストーラー間で守られる必要がある、と言っています。このルールを守らなかった場合、Windows Installerの仕組みが破綻する、という恐ろしい結果を招きます。ですので、Windowsが持つ既存のフォルダにファイルを配置する際は、他のアプリと極力名前が重複しないよう気をつける必要があります。もし、誰か別の人が作ったインストーラーが同名のファイルを同じ場所に配置していたら、そこでインストーラーの仕組みが破綻してしまいます。これを避ける手段で多いのが、組織名や製品名を含むフォルダを先に配置し、その下にファイルを置く方法です。

## コンポーネント設定を合わせるとファイルを共有できる

Windows Installerでこのような製品構成を実現するには、製品Ａと製品Ｂで`ProductCommon`フォルダをインストールするコンポーネントのすべての設定を合わせます。コンポーネントのGUIDや属性、インストールするファイルなど、すべて製品Ａと製品Ｂで同じにします。WiXでインストーラーを作っているなら、一番手っ取り早い方法は、製品Ａの`ProductCommon`フォルダをインストールするComponentエレメント全体を、そのまま製品Ｂのソースにコピーすることです。メンテナンス性やミスの防止を考えるなら、製品間で共通のコンポーネントは別ファイルに分離して、双方から同じファイルを利用したほうが良いでしょう。

WiXを使った実例を示します。以下ソースの上側が製品Ａ、下側が製品Ｂです。両者で同じ内容の`cCommon`というIdのコンポーネントを利用しています。
![Part34SourceComp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83170/622eaa73-8270-66b1-591d-1abcea54eec1.png)
このようにすることで、一つのPCに製品Ａと製品Ｂがインストールされているときに、一方をアンインストールしても、`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`を加えます。例えば、以下のようになります。
![Part34SharedReg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83170/d75d3dc3-d4dc-fa48-ee31-d792bba8f101.png)
そして、アンインストール時にこのレジストリの値が`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レジストリに登録されます。例えば、このような指定をすると

```xml
<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エレメントに指定するんですね）。将来のミスや勘違いに配慮するなら、このレジストリでファイル共有する場合、コンポーネントには共有したいファイルを一つだけ置く方が良さそうです。

[**前の記事へ**](https://qiita.com/tohshima/items/f509498fbf23b9facc8b)　　[**目次へ**](https://qiita.com/tohshima/items/dc4c4721d50a2ad70c7a)　　次の記事へ

[^1]: ちゃんとしたインストーラー作成ツールを利用していれば、自動的に処理が埋め込まれると思いますが。昔はインストーラーの不具合でこの数値が壊れて、正しくアンインストールできないケースがあった、と聞いたことがあります。