「マクロを走らせたあとに Workbook.Close で閉じるだけ」のはずが、非対話モードでは Excel 全体が落ちる——。本稿では、Excel VBA をバックグラウンド実行したときに発生する Workbook.Close 由来のクラッシュを、再現条件・原因の見立て・暫定回避・恒久対策・検証方法まで体系的に整理し、現場にすぐ持ち込める実装テンプレートと運用設計を示します。
事象の全体像(環境・症状・再現性)
| 観点 | 内容 |
|---|---|
| 対象製品 | Microsoft Excel Microsoft 365 |
| バージョン | バージョン 2506 / ビルド 18925.20216(報告例が集中) |
| 実行形態 | 対話型(VBE から手動実行)では安定。 タスク スケジューラ、 .vbs、WScript など 非対話・UI なし 実行で高頻度再現。 |
| 典型症状 | wb.Close SaveChanges:=False 呼び出し直後に Excel 自体が強制終了 対象ブックだけでなく すべてのブックがまとめて閉じる 戻り値・エラー ハンドルを通さずプロセスが消滅(イベント ログには EXCEL.EXE のアプリケーション エラー) |
| 無効だった対策 | Application.DisplayAlerts = False、wb.Saved = True アドイン無効化・セーフモード ActiveWorkbook 非使用(明示参照) |
なぜ落ちるのか(技術的背景の見立て)
Excel は本質的に UI スレッド駆動のクライアント アプリ です。非対話モードで動かすと、内部で完了していない UI 依存処理(再計算・Power Query/接続の非同期リフレッシュ・オートリカバリ・クラウド同期・イベント発火キュー等)が残存したまま Workbook.Close が実行され、未完了処理の破棄・COM オブジェクトの寿命競合・イベントの再入 で例外を誘発します。特に該当バージョン帯では、非表示/最小化ウィンドウでの Close に伴う 回帰バグ を疑わせる振る舞いが観測されます。
加えて、次の要素が重なると顕著です。
- 非対話セッション(「ユーザーがログオンしているかどうかにかかわらず実行する」)で UI サブシステムが不在
- 別ブックのイベント(
Workbook_BeforeClose等)が連鎖発火 - OneDrive/SharePoint 上のファイルでバックグラウンド同期中
- 非同期クエリ(Power Query/ODBC/OLE DB)やピボットキャッシュのリフレッシュ中
- 既存インスタンスにアタッチ(
GetObject)して 他ブックと同一プロセス で動かしている
最優先の運用指針(結論)
- VBA 内で閉じない。 マクロは処理だけ行って 終了処理は外部スクリプト(VBS/PowerShell)に移譲する。
- どうしても VBA で閉じるなら、遅延クロージャ(
Application.OnTime)か 可視化してから閉じる。 - Excel のインスタンスを専用化(
CreateObject("Excel.Application"))。既存インスタンスに混在させない。 - 短期はワークアラウンド、並行して Office の更新/オンライン修復/ビルド ロールバック を検討。
- 長期は COM 依存のバッチを卒業(OpenXML/EPPlus 等)してクラッシュ耐性を上げる。
止血ワークアラウンド:閉じ処理は外部でやる
最も安定するのは「マクロ内で Close を呼ばない」構成です。VBA は処理だけ行い、外部スクリプトが 保存フラグを立ててから Workbook.Close→Application.Quit します。
VBS テンプレート(推奨)
' run-excel-job.vbs
Option Explicit
Dim excel, wb, macroFile, macroName
macroFile = "C:\Jobs\Book1.xlsm" ' 対象
macroName = "Main.DoWork" ' マクロの入り口
Set excel = CreateObject("Excel.Application")
excel.Visible = False ' 実運用は True も選択可(可視化すると安定する環境あり)
excel.DisplayAlerts = False
excel.UserControl = False
Set wb = excel.Workbooks.Open(macroFile, False, False)
' マクロ実行(戻り値は Variant 受け。例外は On Error で補足)
excel.Run macroName
' --- 閉じ処理:VBA ではなく VBS が握る ---
On Error Resume Next
wb.Saved = True ' 変更済みフラグを消す
wb.Close ' SaveChanges は指定しない(Saved が決定因子)
excel.Quit
' COM リリース(リーク対策)
Set wb = Nothing
Set excel = Nothing
PowerShell テンプレート(ログとリリースを厳格化)
# run-excel-job.ps1
$ErrorActionPreference = 'Stop'
$macroFile = 'C:\Jobs\Book1.xlsm'
$macroName = 'Main.DoWork'
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
$excel.DisplayAlerts = $false
$excel.UserControl = $false
$wb = $excel.Workbooks.Open($macroFile, $false, $false)
$excel.Run($macroName)
# --- 閉じ処理 ---
$wb.Saved = $true
$wb.Close()
$excel.Quit()
# COM オブジェクトの明示解放
[void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($wb)
[void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($excel)
ポイント
Saved = Trueを先に立ててCloseに 保存判定をさせない(プロンプト抑止)。UserControl = FalseはQuitの成功率を上げます。- タスク スケジューラは「ユーザーがログオンしている場合のみ実行する」で開始。ログオン不問だと 非対話セッションとなり再現率が上がります。
VBA で閉じざるを得ない場合の堅牢化
以下を組み合わせるとクラッシュ確率を大きく下げられます。
遅延クロージャ(OnTime で 2 秒後)
' Module1.bas
Option Explicit
Public Sub CloseLater()
Application.OnTime Now + TimeValue("00:00:02"), "CloseWorker", , True
End Sub
Public Sub CloseWorker()
On Error Resume Next
With Application
.EnableEvents = False
.DisplayAlerts = False
.ScreenUpdating = False
.Calculation = xlCalculationManual
.Visible = True ' 非対話時でも可視化してから閉じると安定する
End With
```
Dim wb As Workbook
Set wb = ThisWorkbook ' 明示参照
Call WaitForIdle(wb) ' バックグラウンド完了待ち
wb.Saved = True
wb.Close ' SaveChanges 指定は避け、Saved を真にする
```
Cleanup:
With Application
.EnableEvents = True
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
End With
End Sub
Private Sub WaitForIdle(ByVal wb As Workbook)
Dim t As Single: t = Timer
Do
DoEvents
' 再計算/非同期の完了待ち
If Application.CalculationState = xlDone _
And Application.Ready Then
If Not HasRefreshingConnections(wb) Then Exit Do
End If
If Timer - t > 15 Then Exit Do ' タイムアウト(秒)
Loop
End Sub
Private Function HasRefreshingConnections(ByVal wb As Workbook) As Boolean
Dim c As WorkbookConnection
For Each c In wb.Connections
On Error Resume Next
If c.ODBCConnection.Refreshing Or c.OLEDBConnection.Refreshing Then
HasRefreshingConnections = True
Exit Function
End If
Next
End Function
可視化してから閉じる
Application.Visible = True
Windows(ThisWorkbook.Name).Visible = True
DoEvents
ThisWorkbook.Saved = True
ThisWorkbook.Close
UI のない状態でのクローズで落ちる環境は、可視化するだけで安定します。実行中にチラついても問題ない夜間バッチなら現実解です。
イベントの再入を止める
With Application
.EnableEvents = False
.DisplayAlerts = False
End With
' BeforeClose/SheetChange 等が無限再入しないように制御フラグ
ThisWorkbook.Names.Add Name:="**CLOSING**", RefersTo:="=TRUE"
' ...閉じ処理...
安全なクローズ関数(使い回し用)
Public Function CloseWorkbookSafely(ByVal wb As Workbook, Optional ByVal makeVisible As Boolean = True) As Boolean
On Error GoTo EH
With Application
.EnableEvents = False
.DisplayAlerts = False
.ScreenUpdating = False
.Calculation = xlCalculationManual
If makeVisible Then .Visible = True
End With
```
WaitForIdle wb
wb.Saved = True
wb.Close
CloseWorkbookSafely = True
```
ExitHere:
With Application
.EnableEvents = True
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
End With
Exit Function
EH:
CloseWorkbookSafely = False
Resume ExitHere
End Function
タスク スケジューラ設定の勘所(非対話を避ける)
| 設定項目 | 推奨値 | 理由 |
|---|---|---|
| ユーザーがログオンしている場合のみ実行する | オン | UI なし実行を避ける |
| 最上位の特権で実行する | オフ | 権限差で COM が不安定になるのを防ぐ |
| 開始(プログラム/スクリプト) | wscript.exe / powershell.exe | VBS/PS スクリプトで外部閉鎖 |
| 引数 | "C:\Jobs\run-excel-job.vbs" | フルパス |
| 開始(作業フォルダー) | ブック所在フォルダー | 相対参照の失敗回避 |
クラッシュを増幅する要素と回避
- OneDrive/SharePoint 同期中のクローズ:夜間バッチは ローカル固定パス で一時保存し、最後に同期先へ移動。
- 保護ビュー/マクロ警告:信頼できる場所へ配置するか、署名/ポリシーで無人実行に最適化。
- 既存インスタンスの流用:
GetObjectでほかのブックと混在させない。常にCreateObjectで専用インスタンス。 - イベント ハンドラの設計:
Workbook_BeforeCloseでApplication.Quitといった「全体終了」ロジックを入れない。 - アドイン:COM アドイン/Excel アドインは無効で検証。特に PDF/BI 系が引き金になる例が多い。
Office のメンテナンス(更新・修復・ロールバック)
今すぐ更新
Excel を開き「ファイル > アカウント > 更新オプション > 今すぐ更新」。バグ修正が取り込まれている可能性があります。
オンライン修復
Windows の「アプリと機能」から Microsoft 365 を選び「変更」→「オンライン修復」。破損 DLL/設定不整合を是正します。
ビルドのロールバック(Click-to-Run)
REM 管理者のコマンド プロンプト
cd /d "%ProgramFiles%\Common Files\Microsoft Shared\ClickToRun"
OfficeC2RClient.exe /update user updatetoversion=16.0.XXXXX.YYYYY
ロールバック後は自動更新をいったん停止し、安定性を確認してから更新ポリシーを段階適用します。
診断:再現条件の見える化とログ採取
簡易チェックリスト
| チェック | 方法 |
|---|---|
| 対話/非対話の判定 | VBA:Debug.Print Environ("SESSIONNAME")(Console 以外なら非対話の可能性) |
| 再計算の残り | Application.CalculationState が xlDone か |
| 非同期クエリの残り | 接続の Refreshing を全走査 |
| イベント再入 | 制御フラグを Names 等で可視化 |
| クラッシュ署名 | イベント ビューアー(アプリケーション ログで EXCEL.EXE の障害モジュール) |
Windows Error Reporting でダンプ採取
REM 既定のローカルダンプ設定(管理者)
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\EXCEL.EXE" /v DumpType /t REG_DWORD /d 2 /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\EXCEL.EXE" /v DumpFolder /t REG_EXPAND_SZ /d "C:\Dumps" /f
ダンプが採れれば、アドイン/接続/イベントのどこで落ちているか追えます(運用ではダンプ無効化を忘れないこと)。
設計テンプレート:処理と終了を分離する
「処理」と「終了」を別プロセスに分けるだけで信頼性が段違いに上がります。
- Runner(VBS/PS):Excel 専用インスタンス起動 → マクロ実行 → 保存フラグ → クローズ → Quit。
- Worker(VBA):純粋処理のみ。
Close/Quitは呼ばない。
Worker 側(VBA)の雛形
' Main.bas
Option Explicit
Public Sub DoWork()
On Error GoTo EH
Dim app As Application: Set app = Application
With app
.EnableEvents = False
.ScreenUpdating = False
.Calculation = xlCalculationManual
.DisplayAlerts = False
.AskToUpdateLinks = False
.StatusBar = "Processing..."
End With
```
' === ここに業務処理 ===
app.StatusBar = False
```
ExitHere:
With app
.EnableEvents = True
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
.DisplayAlerts = True
End With
Exit Sub
EH:
' ログ出力など
Resume ExitHere
End Sub
比較表:要件別の推奨手段
| 要件 | 推奨アプローチ | 備考 |
|---|---|---|
| 最優先は安定稼働 | 外部スクリプトでクローズ | 本記事のイチ押し |
| VBA 内で完結したい | OnTime 遅延 + 可視化 + Idle 待ち | 落ち方が変わる環境でも効きやすい |
| ユーザー画面を出せない | OnTime 遅延 + Idle 待ち | 可視化が使えないときの次善策 |
| 高速終了したい | Runner/Worker 分離 | COM 解放を確実に |
| クラッシュ調査をしたい | WER ダンプ + イベントログ | 再現手順の固定化が鍵 |
よくある落とし穴
- ActiveWorkbook 依存:非対話ではアクティブが意図せず別ブックに移る。常に
Dim wb As Workbookで明示参照。 - 保存確認のプロンプト:
wb.Saved = Trueを忘れてCloseすると、非表示プロンプトが裏で固まり例外化。 - イベント内で Close:
BeforeCloseなどでCloseを呼ぶ再入パターンは極力避ける。 - 既存インスタンスの共用:ユーザーが開いている Excel と同じプロセスに入ると「全ブック閉鎖」現象の温床。
- クラウド同期:同期アイコンが回っていると閉じ際にハング。ローカル一時ファイルを使う。
長期的な解決策(COM 依存からの脱却)
夜間/サーバー バッチでは、Excel を 開かずに ブックを生成・編集できる非 COM ライブラリ(OpenXML SDK、EPPlus、ClosedXML など)へ移行すると根本的に安定します。ピボットや数式の再計算は工夫が要りますが、集計→出力の 静的レポート化 へ設計を寄せると移行が容易です。どうしても Excel の計算エンジンが必要な箇所は、最小ユースケースだけを Excel に残して Runner/Worker 分離で被害半径を抑えましょう。
トラブル対応の最終手段(やむを得ない場合のみ)
バッチの停止を避けたい場合、最後の砦としてプロセス強制終了があります。
taskkill /f /im excel.exe
ただし未保存データ消失・ファイルロック残存のリスクがあるため、例外時のみ に限定し、次回実行時にクリーンアップ手順(テンポラリ削除、ロック確認)を入れてください。
FAQ
DisplayAlerts を False にしても落ちます。 本件は「プロンプトの有無」ではなく「未完了の内部処理と Close の競合」が主因です。Saved=True だけでは足りません。遅延/可視化/Idle 待ちを組み合わせてください。 なぜ手動実行だと正常なのですか? 対話セッションでは UI スレッドが存在し、内部処理が UI を通じて順次完了するため、競合が発生しにくいからです。 どのくらい遅延すべきですか? 経験上 1〜3 秒で十分なことが多いですが、非同期接続が多い場合は 5 秒程度まで検討します。過度に長い遅延は不要です。 「すべてのブックが閉じる」のはなぜ? 同一プロセス内のほかのブックも Workbook_BeforeClose や Application.Quit に巻き込まれるためです。専用インスタンスを徹底しましょう。
チェックリスト:実装・運用に入れる前に
- Runner/Worker 分離に切り替えた
- タスクは「ログオン時のみ実行」にした
- ブックは信頼できる場所/ローカルに配置した
- OnTime 遅延・可視化・Idle 待ちの 3 点セットを用意した
- 更新/オンライン修復/ロールバックの計画を作成した
- 障害時のダンプ採取とロールバック手順書を整備した
まとめ
本質的な問題は、Excel を UI なしで動かした状態の Workbook.Close が内部の未完了処理と競合しやすく、該当ビルドではその傾向が顕著であることです。短期的には、VBA 内で閉じない(外部スクリプトでクローズ)か、OnTime 遅延 + 可視化 + Idle 待ちで安定化させます。並行して Office の更新/修復/ロールバックでビルド依存の不具合を回避し、長期的には COM 依存の自動化からの脱却を段階的に進めましょう。これらを実践すれば、夜間バッチの「たまに落ちる」を現実的なコストでゼロに近づけられます。
付録:コピペで使える最小構成
VBA(マクロ本体:処理だけ)
Public Sub Entry()
' 業務処理だけ。Close/ Quit は呼ばない
Call DoWork
End Sub
VBS(Runner:閉じ処理を握る)
Option Explicit
Dim x : Set x = CreateObject("Excel.Application")
x.Visible = False : x.DisplayAlerts = False : x.UserControl = False
Dim wb : Set wb = x.Workbooks.Open("C:\Jobs\Book1.xlsm", False, False)
x.Run "Entry"
wb.Saved = True : wb.Close
x.Quit
PowerShell(Runner の別案)
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false; $excel.DisplayAlerts = $false; $excel.UserControl = $false
$wb = $excel.Workbooks.Open('C:\Jobs\Book1.xlsm', $false, $false)
$excel.Run('Entry')
$wb.Saved = $true; $wb.Close(); $excel.Quit()

コメント