Visual Studio Installer ProjectsでMSIを配布していると、バージョンを上げてアップグレードしても「EXEは更新されるのにDLLが更新されない」ことがあります。これは拡張機能の不具合というより、Windows Installer(msiexec)の既存ファイル置換ルールが原因です。仕組みと、毎回確実にDLLを更新する現実的な対策をまとめます。
現象: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に含まれるDLL | Orca等で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 / FileVersion | Windowsのファイルバージョン表示、ビルド識別 | 効く(置換判定の中心になりやすい) | 毎リリースで必ず上げる(自動採番推奨) |
| 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)を増やすのが、最も安全で再現性の高い解決策です。

コメント