「ASP.NET WebForms のサイトを発行(デプロイ)したら、サーバーに入っている Crystal Reports 13.0.2 が消えてしまうのでは?」――運用中の社内システムで誰しも一度は抱く不安です。本記事はその懸念に明確な答えを出しつつ、バージョン不整合を回避して安全に公開するための実践手順、検証観点、トラブル時の対処までを網羅的にまとめました。
質問の背景と前提
対象は ASP.NET WebForms(VB.NET)で構築された既存の業務アプリ。複数の .NET Framework バージョンのプロジェクトを含み、帳票は Crystal Reports 13.0.2(以降、CR 13.0.2)に依存しています。開発 PC 側には古い Crystal の環境がなく、コード修正後に公開した場合、サーバーにインストール済みの Crystal ランタイムが影響を受けないかが主な懸念です。
結論:Visual Studio の「発行」はサーバーのインストール済みソフトを変更しない
- 発行(Publish)は、IIS サイトのアプリケーションフォルダー配下(
bin、*.aspx、web.configなど)にある配置ファイルのコピー/置き換えを行う操作です。 - CR 13.0.2 のランタイムは OS レベルにインストールされたプログラム(
CRRuntime_13_0_x.msi由来)であり、発行によってアンインストールされたり上書きされたりすることはありません。 - Web Deploy(msdeploy)で「宛先の追加ファイルを削除」オプションを有効にしても、削除対象はサイトの配下フォルダー内のファイルに限られ、Windows にインストールされた製品(Crystal ランタイムなど)には及びません。
したがって、「発行でランタイムが消える」ことはありません。とはいえ、次章のとおりバージョン不整合には注意が必要です。
重要:バージョン不整合は簡単に起きる
CR の世界で最も多いトラブルが「参照 DLL とサーバーのランタイムの食い違い」です。例えば開発 PC に新しい CR の開発版(Developer Edition)を導入すると、プロジェクトの参照(CrystalDecisions.*)が新バージョンへ更新されます。これをそのままビルドし、従来どおりの CR 13.0.2 ランタイムしかないサーバーへ発行すると、実行時にロード失敗や型解決エラーが発生します。
よくある症状
Could not load file or assembly 'CrystalDecisions.CrystalReports.Engine, Version=13.0.x.x'Method not found/TypeLoadException(型やメソッドのシグネチャが変わっている)- 帳票ビューアが空白のまま、イベントログに Binding 失敗が出力
| 現象 | 主因 | 対処 |
|---|---|---|
| CrystalDecisions.* の読み込みに失敗 | 参照 DLL のバージョンがサーバーのランタイムより新しい | 開発機の参照を 13.0.2 に固定する/またはサーバーのランタイムを開発機と同一に更新 |
| 32/64bit で BadImageFormatException | アプリプールのビット数とランタイムのビット数が不一致 | アプリプールの「32 ビット アプリケーションの有効化」を調整/x64 ランタイムへ入れ替え |
| PDF 出力のみ失敗 | 一時フォルダー権限不足やプリンター依存コード | アプリケーションプール ID に TEMP への書込権限を付与、プリンター依存を排除 |
開発機とサーバーの「Crystal を揃える」基本戦略
- 最優先は同一バージョン化:開発機にも CR 13.0.2 を導入し、参照 DLL を揃えます。入手困難でどうしても新しい開発版しか使えない場合は、サーバー側のランタイムを同じバージョンに更新するのが王道です(テストを必ず先行)。
- 参照の固定:
*.vbprojの参照でSpecificVersionをTrue、HintPathを CR 13.0.2 の DLL に固定。NuGet 管理でないため、誤更新を防ぐにはソリューションレベルの取り決めが有効です。 - 自動 Binding Redirect に過信しない:
web.configのassemblyBindingで旧→新のリダイレクトはできますが、ランタイムが対応していない API には無力です。根本は「同一バージョン」。
発行前のチェックリスト(実務向け)
| 項目 | 確認・推奨アクション |
|---|---|
| ランタイムのバージョン | サーバーの「アプリと機能」で Crystal Reports runtime のバージョン(13.0.2 など)とビット数(x86/x64)を控える |
| ビルド検証 | 一旦「フォルダー発行」でローカル配備し、bin に必要 DLL が揃っているか、レポート表示・PDF 出力まで実行テスト |
| バックアップ | サーバーのアプリフォルダーと web.config を ZIP で保存(ロールバック用) |
| テスト環境 | 本番と同構成のテストサイト/別 VM に先行発行し、帳票印刷やエクスポートを実際に試す |
| アプリプール設定 | 対象サイトのアプリプールで「32 ビット アプリケーションの有効化」をランタイムのビット数に合わせる |
| 発行オプション | Web Deploy 使用時は「宛先の追加ファイルを削除」を必要時のみ有効化。構成ファイル変換(Web.config Transform)を適切に設定 |
Crystal Reports 13.0.2 と WebForms の構成を理解する
CR の WebForms 連携では、以下の DLL 群が登場します:
CrystalDecisions.CrystalReports.Engine.dllCrystalDecisions.Shared.dllCrystalDecisions.ReportSource.dllCrystalDecisions.Web.dll- (場合により)
CrystalDecisions.Enterprise.*系
多くの場合、これらはサーバーの GAC(グローバル アセンブリ キャッシュ)にランタイムが供給するバージョンが存在し、アプリ側参照と一致していれば問題なく動作します。参照が新しすぎると GAC 解決に失敗します。開発機での参照固定=実行時の安定です。
Web Deploy / ファイルシステム発行の注意点
- 削除オプションの範囲:「宛先の追加ファイルを削除」はサイトフォルダー以下に限定されます。OS にインストールされた CR ランタイムには作用しません。
- app_offline.htm の活用:公開前にサイトルートへ
app_offline.htmを配置すると、IIS は一時的にアプリを停止し安全に入れ替えられます。 - プリコンパイルの可否:WebForms では「更新可能なサイトとしてプリコンパイル」を選ぶかどうかで配置物が変わります。巨大プロジェクトではビルド時間短縮とメモリ消費の観点でテストが必要です。
参考:発行時に用意しておく app_offline.htm(例)
<!doctype html>
<meta charset="utf-8">
<title>メンテナンス中</title>
<h1>ただいま更新作業中です。数分後に再度お試しください。</h1>
アーキテクチャ別・安全なビット数設定
| シナリオ | アプリプール設定 | 必要ランタイム | 注意点 |
|---|---|---|---|
| 既存 x86 ランタイム固定 | 32 ビットアプリケーション有効化 = true | x86 版 CR ランタイム | AnyCPU ビルドは実質 32bit で動作 |
| x64 へ移行したい | 32 ビットアプリケーション有効化 = false | x64 版 CR ランタイム | 一部外部コンポーネントも x64 へ揃える必要 |
運用で効くベストプラクティス
- バックアップを儀式化:発行前に「アプリフォルダー ZIP」「IIS 構成の appcmd エクスポート」「DB スナップショット」を定型化。
- アプリプール専用ユーザー:書き込み先(
%TEMP%、ログ、エクスポート置き場)へ必要最小限の権限を付与。 - ログ設計:
System.Diagnostics/ イベントログにアセンブリ解決失敗や帳票エラーを詳細記録。初動を速くする投資です。 - 監視:W3WP のメモリ、CPU、ハンドル数、例外数を定期的に可視化。PDF 大量出力時のスパイクを把握。
トラブルシューティング:最速で原因を切り分ける
1) 実際にどのバージョンの DLL を拾っているかを確認
アプリ起動直後に以下のコードでバージョンをログへ出力すると、参照解決の実体が即座にわかります。
' Global.asax.vb Application_Start など
Dim asm = GetType(CrystalDecisions.CrystalReports.Engine.ReportDocument).Assembly
Dim v = asm.GetName().Version
System.Diagnostics.Trace.WriteLine($"Crystal Engine v={v}")
2) assemblyBinding での整合(応急)
一時回避として、参照側が期待するバージョンへリダイレクトできます。ただし 実装の差分は吸収できない点に注意。
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="CrystalDecisions.CrystalReports.Engine" publicKeyToken="692fbea5521e1304" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-13.0.2000.0" newVersion="13.0.2000.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="CrystalDecisions.Shared" publicKeyToken="692fbea5521e1304" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-13.0.2000.0" newVersion="13.0.2000.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
3) 一時フォルダー権限の確認
PDF・Excel などのエクスポートでの典型課題です。アプリプール ID(例:DefaultAppPool の ID)に対し、%SystemRoot%\Temp およびアプリの一時出力先へ書き込み権限を付与します。
4) 32/64bit の齟齬を確認
イベントログの BadImageFormatException、または w3wp が即落ちする場合、ビット数不一致の疑いが濃厚です。アプリプール設定とランタイムのビット数を表に従って再点検します。
「開発機に 13.0.2 がない」場合の現実解
プロジェクトを最新の Crystal でビルドせざるを得ない状況は現場で起こりがちです。その場合の選択肢は次の 2 つだけです。
- 開発機をサーバーに合わせる:13.0.2 を導入し、参照を固定する(最小リスク)。
- サーバーを開発機に合わせる:テスト環境で動作確認のうえ、同じランタイムへ更新する(影響範囲が大きい分、受け入れテストを厳密に)。
どちらにしても「両者を合わせる」ことが唯一の正解です。Binding Redirect のみで突き進むのは避けましょう。
発行の手順モデル(チェックリスト付き)
- 現状把握:サーバーの CR バージョンとビット数、IIS アプリプール設定、.NET Framework バージョンを記録。
- 開発機整備:同一 CR を導入し、プロジェクト参照を 13.0.2 に固定。Web.config に本番向けの Transform を用意。
- ローカル検証:フォルダー発行→IIS Express/ローカル IIS で帳票表示・PDF/Excel 出力を確認。
- テスト環境発行:本番と同構成のテストサイトへ Web Deploy(
app_offline.htm併用)。DB・プリンター設定も本番相当で検証。 - 本番バックアップ:アプリフォルダー ZIP、Web.config のバックアップ、IIS 設定エクスポート。
- 本番発行:
app_offline.htm→ 発行 → 動作確認 →app_offline.htm削除。 - 監視:イベントログ、アプリログを 24~48 時間注視。異常があれば即座にロールバック。
「ファイルは残す/設定は変える」Web Deploy の理解
Web Deploy は差分同期ツールです。既定ではファイルを転送・削除しますが、Windows に登録された製品のインストール状態は一切変更しません。削除が及ぶのはサイトルート配下のみ。これを理解しておけば「公開で Crystal が消える」という誤解は解けます。
実装サンプル:帳票最小コードと例外の握り
最小限のコードで「参照解決」「ビューワ描画」「エクスポート」を検証できます。実案件でも最初に動作確認用ページを持つと、トラブル時に切り分けが高速です。
' SampleReport.aspx.vb
Imports CrystalDecisions.CrystalReports.Engine
Imports CrystalDecisions.Shared
Partial Class SampleReport
Inherits System.Web.UI.Page
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
Try
Dim doc As New ReportDocument()
doc.Load(Server.MapPath("~/Reports/Sample.rpt"))
doc.SetDatabaseLogon("user", "pass", "server", "db")
CrystalReportViewer1.ReportSource = doc
' PDF 検証
Dim bytes = doc.ExportToStream(ExportFormatType.PortableDocFormat)
' 必要なら bytes.Length をログ出力
Catch ex As Reflection.ReflectionTypeLoadException
' 参照解決エラーの詳細を出す
For Each le In ex.LoaderExceptions
System.Diagnostics.Trace.TraceError(le.ToString())
Next
Throw
Catch ex As Exception
System.Diagnostics.Trace.TraceError(ex.ToString())
Throw
End Try
End Sub
End Class
付録:サーバーの CR ランタイム判定 PowerShell
インベントリ自動化に役立つワンライナー。レジストリの製品名・バージョンと、GAC の代表 DLL バージョンを合わせて確認します。
# 実行は管理者 PowerShell 推奨
Get-ItemProperty "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*" `
| Where-Object { $_.DisplayName -like "*Crystal Reports*" -and $_.DisplayName -like "*runtime*" } `
| Select-Object DisplayName, DisplayVersion
# GAC 代表 DLL のバージョンを表示(存在すれば)
$paths = @(
"$env:WINDIR\Microsoft.NET\assembly\GAC_MSIL\CrystalDecisions.CrystalReports.Engine*\CrystalDecisions.CrystalReports.Engine.dll",
"$env:WINDIR\assembly\GAC_MSIL\CrystalDecisions.CrystalReports.Engine*\CrystalDecisions.CrystalReports.Engine.dll"
)
Get-ChildItem $paths -ErrorAction SilentlyContinue `
| Select-Object FullName,@{n="FileVersion";e={(Get-Item $_.FullName).VersionInfo.FileVersion}}
中長期の保守戦略:レポート依存を局所化する
将来の .NET 最新化に備え、以下の構成を強く推奨します。
- 帳票専用サイトの分離:
本体アプリは .NET 6/8 相当へ刷新、帳票だけを .NET Framework 4.8 + Crystal に残し別サイトとして公開。URL パラメータまたは簡易 REST で本体から呼び出します。 - Crystal 以外への段階移行:
RDL ベース(ReportViewer、SSRS)や FastReport 等への置き換えを PoC から開始。新旧混在期は帳票種別ごとの出力先を UI で明示。 - CI/CD で参照ロック:
ビルド時にCrystalDecisions.*のバージョン検査を入れて差分を検知。誤って新 DLL をコミットした場合はビルドを失敗させます。
チェックポイントの早見表
| チェック | OK の条件 | NG のときの影響 | 初動対応 |
|---|---|---|---|
| 参照 DLL のバージョン | 開発・本番で 13.0.2 に一致 | ロード失敗/型解決エラー | 参照固定/サーバー更新/Binding Redirect |
| ビット数 | アプリプールとランタイムのビット数一致 | BadImageFormatException | アプリプール切替/ランタイム入替 |
| 一時フォルダー権限 | アプリプール ID に書込可 | PDF/Excel 出力失敗 | 権限付与、出力先の明示 |
| バックアップ | ZIP + IIS 設定の控えあり | ロールバック不能 | 公開前の儀式化 |
Q&A(現場のよくある疑問)
Q. 発行先で 「宛先の追加ファイルを削除」にチェックを入れると、Crystal ランタイムも消えますか?
A. 消えません。削除されるのはサイト配下ファイルのみ。ランタイムは OS のインストール製品です。
Q. CrystalDecisions.* を bin にコピーしてしまえばランタイムは要りませんか?
A. 非推奨です。ランタイムはビューワやエクスポートなど多くのネイティブ依存を伴います。正式にはサーバーへ該当バージョンのランタイムをインストールしてください。
Q. Binding Redirect で全て丸く収まりますか?
A. いいえ。API の差分は救えません。動作保証の観点では同一バージョンを揃えるのが唯一の確実策です。
まとめ
- Visual Studio の発行は「ファイル更新」であり、Crystal Reports ランタイムのアンインストールは起こりません。
- 最大のリスクはバージョン不整合。開発機とサーバーの CR を同一バージョンに揃え、参照 DLL を固定しましょう。
- 公開前のバックアップ・テスト発行・権限確認を徹底すれば、WebForms + CR 13.0.2 の運用は十分に安全です。
- 中長期は「帳票専用サイトの分離」や「他レポート基盤への移行」で技術的負債を局所化。移行までの橋渡しとして本記事のチェックリストを活用してください。
付録:実践フロー(テンプレートとしてコピペ可)
1) サーバー現状の控え
- CR ランタイム名・バージョン・ビット数を記録
- アプリプール設定(.NET CLR、32bit 有効化、ID)を記録
2. 開発機の整備
* CR 13.0.2 を導入、参照を固定(SpecificVersion=True)
* Web.config の変換(接続文字列、トレース、デバッグ=false)
3. ローカル検証
* フォルダー発行 → IIS Express で動作確認(表示/PDF)
4. テスト環境発行
* app_offline.htm 置き → Web Deploy → 動作確認
* エクスポート・印刷・並列アクセスを確認
5. 本番公開
* バックアップ → app_offline.htm → 発行 → 確認 → 片付け
6. 監視と振り返り
* 24〜48 時間はログ・リソース監視を強化
* 気付きを運用 Runbook に反映
最後に:今日からできるミスゼロ対策
- バージョン台帳を作る(Excel でも OK):アプリ、CR、.NET、IIS、OS の対応表を 1 枚に。
- 「参照 DLL のハッシュ」を CI で検査:想定外の更新を検知してビルドを失敗させる。
- 「最小再現ページ」を常設:帳票だけを読み込む検証ページを本番にも(認可で保護)。
- ロールバック手順を前日に読む:いざというとき体が勝手に動くように。

コメント