PowerShellからVBAを実行すると
Excelが切断される原因
複数のExcelマクロを順番に実行していたところ、終了処理でExcelとの接続が切れました。Workbook.CloseとApplication.Quitを誰が実行するのか、役割を整理します。
PowerShellからExcelを操作し、複数のマクロブックと月別ファイルを順番に処理していたとき、次のエラーが発生しました。
The object invoked has disconnected from its clients.
HRESULT: 0x80010108
RPC_E_DISCONNECTED日本語では「呼び出されたオブジェクトがクライアントから切断されました」と表示されます。変数は残っていても、その変数が参照していたExcelはすでに終了しており、PowerShellから操作できない状態です。
今回の中心は、VBAとPowerShellの両方にブックとExcelの終了処理があったことです。処理回数よりも、Excelを閉じる担当が重複していた点を確認します。
AUTOMATION FLOW
複数の月別ファイルを順番に処理していた
当時のPowerShellは、計画系のファイルでマクロを実行した後、別フォルダにある同名・別内容の月別ファイルを順番に開く構成でした。それぞれについて3つのマクロを実行し、変更を保存してから次のファイルへ進みます。
- 01Excelを起動PowerShellがExcel.Applicationを作成する。
- 02対象ブックを開くフォルダごとに対応する月別ファイルを開く。
- 03マクロを連続実行Application.Runで3つの処理を順番に呼び出す。
- 04保存して閉じる各ブックの処理後、次の対象へ移動する。
ところが、VBA側にもWorkbook.CloseとApplication.Quitがあり、PowerShell側にもClose()とQuit()がありました。
DISCONNECTED OBJECT
Application.Quitの後もPowerShellは処理を続ける
Workbook.Closeは指定したブックを閉じます。一方、Application.QuitはExcelアプリケーションを終了します。
PowerShellが作成したExcelに対して、実行中のVBAがApplication.Quitを呼ぶと、PowerShellが保持している$excelや$workbookは有効な接続先を失います。その後で保存や終了を実行すると、切断エラーになります。
Sub ExecuteMain()
On Error GoTo ErrorHandler
'集計や転記などの処理
ThisWorkbook.Close SaveChanges:=True
Application.Quit
Exit Sub
ErrorHandler:
Application.Quit
End Sub正常終了でもエラー終了でも、VBAがExcel全体を閉じています。この後にPowerShellが$workbook.Save()や$excel.Quit()を呼ぶと、すでに終了したCOMオブジェクトへ再びアクセスすることになります。
Excelを閉じる処理は、VBAとPowerShellのどちらか一方だけが担当する。
ONE OWNER
PowerShellが全体を管理する場合の役割分担
複数ブックをPowerShellから順番に処理するなら、Excelの開始から終了までをPowerShellへ統一すると流れが分かりやすくなります。
集計・転記・書式変更を行い、完了したらPowerShellへ制御を戻します。
ブックを開く、マクロを呼ぶ、保存する、閉じる、最後にExcelを終了します。
- VBAから
Application.Quitを呼ばない - VBAとPowerShellで同じブックを二重に
Closeしない - エラー時も、Excelの終了はPowerShellの
finallyで行う Quit()はすべてのブック処理が終わった後に一度だけ実行する
MINIMUM SAMPLE
VBAは処理だけ、PowerShellが閉じる
VBAでは、ブックやExcelを閉じず、処理に失敗した場合はエラーを呼び出し元へ返します。
Option Explicit
Public Sub ExecuteMain()
Dim errorNumber As Long
Dim errorDescription As String
On Error GoTo ErrorHandler
'集計や転記など、ブック内の処理だけを行う
Exit Sub
ErrorHandler:
errorNumber = Err.Number
errorDescription = Err.Description
On Error GoTo 0
Err.Raise errorNumber, "ExecuteMain", errorDescription
End SubPowerShell側は、マクロが正常終了した場合に保存し、finallyでブックとExcelを閉じます。次は役割分担を示すための最小構成です。
$excel = $null
$workbook = $null
try {
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
$excel.DisplayAlerts = $false
$workbook = $excel.Workbooks.Open($bookPath)
$macroName = "'$($workbook.Name)'!ExecuteMain"
$excel.Run($macroName)
$workbook.Save()
}
finally {
if ($null -ne $workbook) {
$workbook.Close($false)
$workbook = $null
}
if ($null -ne $excel) {
$excel.Quit()
$excel = $null
}
}複数ブックを処理するときは、ブックのOpen・Run・Save・Closeをループ内で行い、ExcelのQuit()だけをループの外へ置きます。実際のパスやマクロ名は環境に合わせて変更してください。
SUMMARY
終了処理を重ねず、Excelの管理者を決める
Workbook.Closeはブック、Application.QuitはExcel全体を終了する- VBAがExcelを終了すると、PowerShell側のCOM参照は使えなくなる
- 終了済みのオブジェクトへ再度アクセスすると、
RPC_E_DISCONNECTEDが発生する - PowerShellが連続実行を管理するなら、VBAはブック内の処理だけにする
- 保存・Close・Quitの担当をPowerShellへまとめ、Quitは最後に一度だけ実行する
今回のように、最初のVBAエラーをきっかけにエラー処理がApplication.Quitを実行すると、PowerShell側では別の切断エラーとして見えることがあります。最初のエラーと終了時のエラーを分けて考えることが、原因の特定につながります。
MicroSoft公式
Microsoft Learn:Application.Quit method
Microsoft Learn:Workbook.Close method
Microsoft Learn:PowerShellでCOMオブジェクトを操作する