日程Fit|「いつ空いてますか?」の往復はもう不要。候補日を選んでURLを送るだけ|登録不要|今すぐ無料で使う →

Excel VBAのWorkbook.Closeで異常終了・全ブック巻き添えを防ぐ:非対話実行時クラッシュの原因と安全な回避策・実装テンプレート

「マクロを走らせたあとに Workbook.Close で閉じるだけ」のはずが、非対話モードでは Excel 全体が落ちる——。本稿では、Excel VBA をバックグラウンド実行したときに発生する Workbook.Close 由来のクラッシュを、再現条件・原因の見立て・暫定回避・恒久対策・検証方法まで体系的に整理し、現場にすぐ持ち込める実装テンプレートと運用設計を示します。

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

事象の全体像(環境・症状・再現性)

観点内容
対象製品Microsoft Excel Microsoft 365
バージョンバージョン 2506 / ビルド 18925.20216(報告例が集中)
実行形態対話型(VBE から手動実行)では安定。
タスク スケジューラ、.vbsWScript など 非対話・UI なし 実行で高頻度再現。
典型症状wb.Close SaveChanges:=False 呼び出し直後に Excel 自体が強制終了 対象ブックだけでなく すべてのブックがまとめて閉じる 戻り値・エラー ハンドルを通さずプロセスが消滅(イベント ログには EXCEL.EXE のアプリケーション エラー)
無効だった対策Application.DisplayAlerts = Falsewb.Saved = True アドイン無効化・セーフモード ActiveWorkbook 非使用(明示参照)

なぜ落ちるのか(技術的背景の見立て)

Excel は本質的に UI スレッド駆動のクライアント アプリ です。非対話モードで動かすと、内部で完了していない UI 依存処理(再計算・Power Query/接続の非同期リフレッシュ・オートリカバリ・クラウド同期・イベント発火キュー等)が残存したまま Workbook.Close が実行され、未完了処理の破棄COM オブジェクトの寿命競合イベントの再入 で例外を誘発します。特に該当バージョン帯では、非表示/最小化ウィンドウでの Close に伴う 回帰バグ を疑わせる振る舞いが観測されます。

加えて、次の要素が重なると顕著です。

  • 非対話セッション(「ユーザーがログオンしているかどうかにかかわらず実行する」)で UI サブシステムが不在
  • 別ブックのイベントWorkbook_BeforeClose 等)が連鎖発火
  • OneDrive/SharePoint 上のファイルでバックグラウンド同期中
  • 非同期クエリ(Power Query/ODBC/OLE DB)やピボットキャッシュのリフレッシュ中
  • 既存インスタンスにアタッチGetObject)して 他ブックと同一プロセス で動かしている

最優先の運用指針(結論)

  1. VBA 内で閉じない。 マクロは処理だけ行って 終了処理は外部スクリプト(VBS/PowerShell)に移譲する。
  2. どうしても VBA で閉じるなら、遅延クロージャApplication.OnTime)か 可視化してから閉じる
  3. Excel のインスタンスを専用化CreateObject("Excel.Application"))。既存インスタンスに混在させない。
  4. 短期はワークアラウンド、並行して Office の更新/オンライン修復/ビルド ロールバック を検討。
  5. 長期は COM 依存のバッチを卒業(OpenXML/EPPlus 等)してクラッシュ耐性を上げる。

止血ワークアラウンド:閉じ処理は外部でやる

最も安定するのは「マクロ内で Close を呼ばない」構成です。VBA は処理だけ行い、外部スクリプトが 保存フラグを立ててから Workbook.CloseApplication.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 = FalseQuit の成功率を上げます。
  • タスク スケジューラは「ユーザーがログオンしている場合のみ実行する」で開始。ログオン不問だと 非対話セッションとなり再現率が上がります。

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.exeVBS/PS スクリプトで外部閉鎖
引数"C:\Jobs\run-excel-job.vbs"フルパス
開始(作業フォルダー)ブック所在フォルダー相対参照の失敗回避

クラッシュを増幅する要素と回避

  • OneDrive/SharePoint 同期中のクローズ:夜間バッチは ローカル固定パス で一時保存し、最後に同期先へ移動。
  • 保護ビュー/マクロ警告:信頼できる場所へ配置するか、署名/ポリシーで無人実行に最適化。
  • 既存インスタンスの流用GetObject でほかのブックと混在させない。常に CreateObject で専用インスタンス。
  • イベント ハンドラの設計Workbook_BeforeCloseApplication.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.CalculationStatexlDone
非同期クエリの残り接続の 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

ダンプが採れれば、アドイン/接続/イベントのどこで落ちているか追えます(運用ではダンプ無効化を忘れないこと)。

設計テンプレート:処理と終了を分離する

「処理」と「終了」を別プロセスに分けるだけで信頼性が段違いに上がります。

  1. Runner(VBS/PS):Excel 専用インスタンス起動 → マクロ実行 → 保存フラグ → クローズ → Quit。
  2. 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 すると、非表示プロンプトが裏で固まり例外化。
  • イベント内で CloseBeforeClose などで 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_BeforeCloseApplication.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()

この記事を書いた人

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

コメント

コメントする

目次