Visual Studio Installer ProjectsでMSIアップグレード時にDLLが更新されない原因と対策|Windows Installerの既存ファイル置換ルール

Visual Studio Installer ProjectsでMSIを配布していると、バージョンを上げてアップグレードしても「EXEは更新されるのにDLLが更新されない」ことがあります。これは拡張機能の不具合というより、Windows Installer(msiexec)の既存ファイル置換ルールが原因です。仕組みと、毎回確実にDLLを更新する現実的な対策をまとめます。

日程Fit。無料・登録不要。「いつ空いてる?」を、ひとつのリンクで。リンクを送って、○△×でかんたん日程調整。無料で日程を作る。
目次

現象:MSIアップグレードでEXEだけ更新される

今回の状況を整理すると、次のような「メジャーアップグレードに見えるのに、特定のDLLだけ残る」パターンです。

項目内容
利用ツールMicrosoft Visual Studio Installer Projects(例:1.02)
設定DetectNewerInstallationVersion=True、RemovePreviousVersions=True(※環境によっては DetectNewerInstalledVersion という表記)
やっていることMSI(ProductVersion)を上げてアップグレード
結果EXEは置き換わるが、参照DLLが置き換わらない
例外DLL側のバージョン(FileVersion / AssemblyFileVersion)を上げると更新される

「MSIのバージョンを上げたのに中身が全部入れ替わらないのは変では?」と感じやすいのですが、Windows Installerは“製品(Product)”よりも“コンポーネント(Component)とファイル”の規則で動くため、ここでギャップが起きます。

結論:原因はWindows Installerの「既存ファイル置換ルール」

結論から言うと、挙動の本体は拡張機能の独自仕様というより、Windows Installer(msiexec)が持つ「同名ファイルが既にあるときに、どちらを残すか」のルールです。特に、EXE/DLLのような“バージョン情報を持つファイル”は、原則として「より新しいバージョンのときだけ置き換える」という設計になっています。

Microsoftの説明でも、インストーラはまずコンポーネントの“キー ファイル(key file)”が既に存在するかを見て、存在するならバージョン・日付・言語などを比較し、ファイル置換ルールで「そのコンポーネントを入れ直すか」を決める、とされています。

つまり「MSIを新しくした=必ず全ファイルがコピーされる」ではなく、「コピーする必要があると判定されたファイルだけが置き換わる」という動きになります。ここでDLLのバージョンが据え置きだと、“置き換える必要なし”と判定されやすくなります。

なぜ「EXEは更新されてDLLは更新されない」のか

一見不思議ですが、次のロジックで説明できます。

  • EXEは毎回ビルド番号などでFileVersionが変わっている → Windows Installerが「新しい」と判断して置換する
  • DLLはFileVersion(またはAssemblyFileVersion)が固定 → Windows Installerが「同じ(または新しくない)」と判断して置換しない

Windows Installerの「File Versioning Rules」では、基本方針として “Highest Version Wins(高いバージョンが勝つ)” が示されています。逆に言えば、バージョンが上がっていない限り勝てない(=置き換わらない)場面が起きます。

また、バージョン情報がない(非バージョン)ファイルの場合は、日付(作成日・更新日)などで判断しますが、Windows Installerは非バージョンファイルを“ユーザーデータの可能性がある”として扱い、更新日が作成日より新しいと上書きしない、といったルールもあります。設定ファイルやDBファイルが勝手に置き換わらないのは、まさにこの思想です。

まずやるべき切り分け:本当に「MSIに新しいDLLが入っているか」

「インストールしても更新されない」系のトラブルは、原因が1つとは限りません。Windows Installerの置換ルールが原因であることが多い一方、そもそもMSIに新しいDLLが入っていないケースも現場では普通に起こります。

チェックポイント確認方法見たいもの
ビルド成果物(bin/Release等)DLLのプロパティ(詳細)やPowerShellで確認FileVersionが期待どおり変わっているか
MSIに含まれるDLLOrca等でMSIのFileテーブルを見る(または展開ツールで抽出)Version列が実ファイルのバージョンと一致しているか
インストール後の配置先配置先DLLのプロパティ/PowerShell旧バージョンのまま残っていないか
インストールログmsiexecのログを採取して確認「コピーした/スキップした」根拠

PowerShellでFileVersionを見るなら、次が手早いです。

powershell -NoProfile -Command "(Get-Item 'C:\Program Files\YourApp\YourLib.dll').VersionInfo.FileVersion"

インストールログは、問題の“確定診断”に近い情報が取れます。

msiexec /i "YourApp.msi" /l*v "%TEMP%\YourApp_install.log"

ログを見て「古いDLLを残した」と書いてある場合、置換ルールが原因である可能性が高くなります(バージョンが同じ、または新しくない等)。

Windows Installerの置換ルールを“実務目線”で理解する

ドキュメントのまま読むと難しく感じるので、よく遭遇するパターンに絞って表にします。

ファイルの種類判断材料上書きされやすい条件今回の症状との関係
バージョン付き(EXE/DLLなど)主にバージョン(+言語)新しいバージョンのときDLLのFileVersionが固定だと上書きされない
非バージョン(json/config/txt等)作成日・更新日など“ユーザーが変更していない”と判定されるとき設定ファイルが上書きされないのは仕様になりやすい
Companion file(紐付けられた非バージョン)親(versioned)の判断に追従親が置き換わるとき親が更新されないと子も更新されない

Windows Installerは「同名ファイルがある場合、バージョン・日付・言語で置換するかを決める」というルールを持ち、さらに“非バージョンファイルはユーザーデータ扱い”の考え方もあります。これが「バージョンを上げていないDLLが残る」「設定ファイルが勝手に戻らない」などの挙動につながります。

最も確実な解決策:MSIに含めるDLLも毎回FileVersionを上げる

「毎回DLLも更新したい」という要件に対して、Windows Installerの設計思想に一番沿っていて、トラブルが少ない現実解はこれです。

  • 配布対象のEXE/DLLは、毎リリースでFileVersion(AssemblyFileVersion)を増やす
  • 必要ならAssemblyVersionは固定し、FileVersionだけ増やす(参照互換性を崩しにくい)

.NETの場合、AssemblyVersionとAssemblyFileVersion(FileVersion)は役割が違います。AssemblyVersionは参照・ロードに関わり、AssemblyFileVersionは主にファイルシステム上の“識別情報”として表示されます。運用上は「参照互換性は保ちたいが、配布物は毎回新しくしたい」というとき、AssemblyVersionを固定してFileVersionだけ更新する方針が取りやすいです。

.NET(SDKスタイル)でFileVersionを毎回更新する例

SDKスタイルのプロジェクト(.csproj)なら、プロパティで制御できます。Microsoftの説明では、GenerateAssemblyInfoがtrue(デフォルト)の場合、FileVersion / AssemblyVersion / InformationalVersionなどのプロパティが属性に変換され、さらにAssemblyVersionとFileVersionは$(Version)(サフィックス除外)を既定値にする、とされています。

「アプリのバージョン(Version)」と「ファイル更新判定に使いたいFileVersion」を分けたい場合は、明示的に指定します。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <!-- 人間が見る製品バージョン(SemVerでもOK) -->
    <Version>1.4.0</Version>

    <!-- 参照互換性を保ちたいなら固定にする運用もあり -->
    <AssemblyVersion>1.4.0.0</AssemblyVersion>

    <!-- MSIの置換判定を確実に通すため、毎回変わる値を入れる -->
    <FileVersion>1.4.0.1234</FileVersion>

    <!-- ビルド情報を入れたい場合 -->
    <InformationalVersion>1.4.0+build.1234</InformationalVersion>
  </PropertyGroup>
</Project>

「1234」の部分は、CIのビルド番号(GitHub Actionsのrun number、Azure DevOpsのBuild.BuildIdなど)を流し込むのが定番です。人手で更新しない運用にすると、更新漏れ(=DLLが更新されない)がほぼ消えます。

.NET Framework(AssemblyInfo.cs)での例

.NET Framework系でAssemblyInfo.cs運用をしている場合は、AssemblyVersion / AssemblyFileVersionを属性で指定します。Microsoftの解説でも、AssemblyVersionとAssemblyFileVersionは別用途であり、AssemblyFileVersionはエクスプローラー表示用で参照に使われない、と説明されています。

// Properties/AssemblyInfo.cs
using System.Reflection;

[assembly: AssemblyVersion("1.4.0.0")]       // 参照互換性を重視するなら固定も検討
[assembly: AssemblyFileVersion("1.4.0.1234")] // 毎回更新(MSIの置換判定に効く)

どのバージョンを上げればいいか迷ったときの早見表

項目主な用途Windows Installerの置換判定に効く?上げ方のおすすめ
AssemblyVersion.NETの参照・ロード(特にStrong Name時)間接的(ファイルのバージョンとは別物として運用されやすい)大きな互換性変更のときだけ上げる運用が無難
AssemblyFileVersion / FileVersionWindowsのファイルバージョン表示、ビルド識別効く(置換判定の中心になりやすい)毎リリースで必ず上げる(自動採番推奨)
InformationalVersion表示用(SemVer+コミットSHA等)通常は直接効かない追跡性のために入れると便利

AssemblyVersionが絡む参照互換性の話は長くなりがちですが、「MSIで確実に上書きしたい」という目的に限れば、FileVersion(AssemblyFileVersion)を毎回上げるが最短距離です。

MSI側のバージョン運用も整理する

Visual Studio Installer Projectsで「Versionを上げたらProductCode変更を促される」流れはメジャーアップグレードの王道ですが、ここで混乱しやすいのが“MSIのバージョン”と“ファイルのバージョン”の違いです。

MSIのProductVersionは、形式と上限が決まっています(major.minor.build、最大値は255.255.65535、4桁目は無視される)。この制約の中で、製品の世代管理(コントロールパネル表示やUpgrade判定)をします。

バージョンの種類どこで使われる役割今回の問題への効き方
MSI(ProductVersion)Upgrade判定、アプリと機能の追加と削除の表示“新しい製品かどうか”の判定MSIは新しくなるが、DLLの置換は別ルール
EXE/DLL(FileVersion)Windows Installerのファイル置換、エクスプローラー表示“このファイルが新しいか”の判定DLLが更新されるかどうかの核心

MSIのProductVersionを上げるのは必要条件になりやすい一方で、DLLが置き換わる条件を満たすにはFileVersionの更新が必要、という二段構えだと捉えると、再発防止の設計がしやすくなります。

「以前はDLLのバージョンを上げなくても更新された気がする」理由

過去にたまたま動いていた背景として、実務上よくあるのは次のどれかです。

  • 実はDLLのFileVersionもビルドで変わっていた(ワイルドカードや自動採番が入っていた、テンプレが変わった等)
  • DLLが非バージョン扱いになっていて、タイムスタンプで置き換わっていた(ただし“ユーザーデータ扱い”のルールでスキップされることもある)
  • コンポーネント構成が変わって、結果として再コピーされていた(コンポーネントGUIDが変わる/キー ファイルが変わる等)

特に3つ目は「よく分からないが直ってた」を生みがちです。Windows Installerはコンポーネント単位で“既にあるか”を見て動くため、プロジェクト構成の変更で偶然挙動が変わることがあります。

バージョンを上げずに“強制上書き”したい場合の選択肢と副作用

要件として「DLLのFileVersionは増やしたくない(社内ルール、互換性ポリシー等)」がある場合もあります。ただし、Windows Installerの思想に逆らう方向になるので、副作用を理解したうえで限定的に使うのが前提です。

選択肢:REINSTALL / REINSTALLMODEで再インストールを強制する

Windows Installerには、再インストールの動作を指定するためのREINSTALLMODEがあります。既定は “omus” で、オプション “a” を含めると「チェックサムやバージョンに関係なく全ファイルを再インストール(強制上書き)」という挙動になります。

たとえば、運用ツール(社内の更新バッチなど)で“修復に近い再インストール”として実行するなら、次のようなコマンドを取ることがあります。

msiexec /i "YourApp.msi" REINSTALL=ALL REINSTALLMODE=amus /qn

ただし、これは設定ファイルやユーザーが編集したファイルまで巻き込んで上書きする危険があり、製品の種類によっては事故の原因になります。実際、Microsoft Q&AでもREINSTALLMODE=amusでダウングレードを通した例がある一方で、“意図せず古いものに置き換える可能性があるので慎重に”という注意がされています。

選択肢:コンポーネントを別物として扱わせる(コンポーネントGUID変更など)

「ファイルバージョンを上げないなら、別コンポーネントとして入れ直す」発想です。しかしこれはWindows Installerのコンポーネント設計ルールから外れやすく、修復(Repair)・パッチ適用・将来のアップグレードで破綻しやすい典型ルートです。短期的に“今だけ直す”には見えるものの、長期運用に向きません。

選択肢:より柔軟なインストーラーへ移行(WiX等)

Visual Studio Installer Projectsは手軽な一方、細かい制御(コンポーネント設計の明示、メジャーアップグレードの調整、細粒度の上書きポリシー等)を深掘りすると限界が出ます。「毎回必ず上書き」「特定ファイルだけは保護」などを同時に満たしたい場合は、WiX Toolsetや商用ツール等に切り替えた方が、結果的に運用コストが下がることもあります。

運用のベストプラクティス:更新漏れを起こさないためのルール

最終的に大事なのは、インストーラーの小技よりも「更新漏れが起きないルール化」です。おすすめの落とし所を、現場で回しやすい形にまとめます。

おすすめ運用ルール(最小構成)

  • MSIのProductVersionを上げる(メジャーアップグレードのための前提)
  • 配布する全EXE/DLLのFileVersionを必ず上げる(CIで自動化する)
  • AssemblyVersionは互換性方針に合わせる(固定運用も検討)
  • ログ採取(/l*v)を標準化し、問い合わせ時に即原因が追えるようにする

チェックリスト(リリース前に5分で潰す)

チェックOKの目安NG例(今回の症状)
EXEのFileVersion前回より増えている増えている(ここはOKになりやすい)
DLLのFileVersion前回より増えている固定のまま
MSIのProductVersion前回より増えている(形式も適正)同じ/4桁目で管理している(無視される)
アップグレード後の配置先EXE/DLLとも期待どおりのバージョンDLLだけ旧バージョン
ログコピーされたことが確認できるŧ“同等/新しくないので残す”系の記録

よくある質問

DLLのバージョンだけ毎回上げると、参照や互換性が怖い

不安がある場合は、「AssemblyVersionは固定」「AssemblyFileVersion(FileVersion)だけ更新」という運用が取りやすいです。AssemblyVersionは参照やロードに関わり、AssemblyFileVersionは表示・識別用という整理ができるため、ビルド識別をFileVersionに寄せると運用が安定します。

Visual Studio Installer Projects側で「常に上書き」みたいな設定はない?

Windows Installerの置換ルールは“グローバルに適用される”とされ、安易に「いつでも上書き」をするとユーザーデータを破壊しやすい設計です。強制上書きはREINSTALLMODEなどで可能ですが、通常のアップグレード運用に持ち込むのはリスクが上がります。

どうしてもバージョンを変えられないDLLがある場合は?

そのDLLが本当に“毎回上書きされるべきもの”かを切り分けるのが先です。たとえば、ユーザーが差し替えるプラグインDLLや、外部提供の固定版DLLなら、そもそもインストーラーの“更新対象”から外す(または別フォルダ管理)方が安全なケースもあります。要件として「必ず更新」なら、強制上書き系の手段(REINSTALLMODE等)を検討できますが、テスト範囲と事故対応をセットで考える必要があります。

まとめ:MSIのアップグレードでは「MSIのバージョン」だけでは足りない

Visual Studio Installer ProjectsでMSIのバージョンを上げてアップグレードしても、DLLが更新されないのは珍しい現象ではありません。Windows Installerは“既存ファイル置換ルール”に従って、主にFileVersionなどを見て置換可否を決めます。したがって、確実にDLLも毎回更新したいなら、配布対象のDLLも毎回FileVersion(AssemblyFileVersion)を増やすのが、最も安全で再現性の高い解決策です。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次