LoginSignup
1
1

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-10-18

前の記事へ  目次へ  次の記事へ

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

同じ場所に同じファイル名で2つのインストーラーがファイルをインストールしたらどうなるでしょう。例えば、以下のようなケースです。
Part34Files.png
この例では製品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のコンポーネントを利用しています。
Part34SourceComp.png
このようにすることで、一つの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を加えます。例えば、以下のようになります。
Part34SharedReg.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レジストリに登録されます。例えば、このような指定をすると

<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.exemspaint.exeの両方が残ります。MSIにとって shared DLL reference counter の内容はファイルに対してではなく、コンポーネントに対して働くのです(だからComponentエレメントに指定するんですね)。将来のミスや勘違いに配慮するなら、このレジストリでファイル共有する場合、コンポーネントには共有したいファイルを一つだけ置く方が良さそうです。

前の記事へ  目次へ  次の記事へ

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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1