PowerShellでExcelを操作する際の解放処理、書かなきゃダメ?
COMオブジェクト解放(ReleaseComObject)が必要ってよく言われているけど、CUIから実行する場合は要らないんじゃないの?
Power Automate Desktop(以下PAD)からPowerShellでExcelを操作する目線の記事です。
はじめに
2023年10月、VBScriptの非推奨が発表されました。
VBScriptを使ってPADからExcelを操作しているので他人事じゃなかったりします。
他の手段も用意しなきゃなあという感じです。
候補としてPowerShellが上がると思います。
とはいえ、全く使おうとは思っていませんでした。
理由はこれしかありません。COMの明示的な解放がベストプラクティスとされているから。
ガベージ コレクトの強制とかでっかい抵抗あります。
こんなに長いコードが必要とか、じゃあいいわ。としか思わないです。
ベストプラクティスのポイント
- 作成されたRCWはReleaseComObjectで解放を行う
- Officeのアプリケーションの終了前後にガベージコレクトを実行する
Excelの操作におけるCOMオブジェクト解放って本当に必要なの?
- しなくても解放しているよ?
というのがこの記事で言いたいことです。以下、説明します。
問題が大ありとされているコード
$app = New-Object -ComObject Excel.Application
$books = $app.Workbooks
$book = $books.Open("%NewVar%")
Write-Host $book.Sheets["Sheet1"].Cells[1,1].Text
$book.Close()
$app.Quit()
起動したExcelのプロセスは終了せずタスクマネージャーに残り続けるとされているコードです。
実行後すぐにタスクマネージャーから消えます。
自分で解放処理を行っているベストプラクティスなコード
$app = New-Object -ComObject Excel.Application
$books = $app.Workbooks
$book = $books.Open("%NewVar%")
$sheets = $book.Sheets
$sheet = $sheets["Sheet1"]
$cells = $sheet.Cells
$cell = $cells[1,1]
Write-Host $cell.Text
$book.Close()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($cell) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($cells) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheet) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheets) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($book) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($books) | Out-Null
$cell = $null
Remove-Variable cell -ErrorAction SilentlyContinue
$cells = $null
Remove-Variable cells -ErrorAction SilentlyContinue
$sheet = $null
Remove-Variable sheet -ErrorAction SilentlyContinue
$sheets = $null
Remove-Variable sheets -ErrorAction SilentlyContinue
$book = $null
Remove-Variable book -ErrorAction SilentlyContinue
$books = $null
Remove-Variable books -ErrorAction SilentlyContinue
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
[System.GC]::Collect()
$app.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($app) | Out-Null
$app = $null
Remove-Variable app -ErrorAction SilentlyContinue
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
[System.GC]::Collect()
違うところありました?
問題のあるコードを続けて実行
1つめのコードに下記のコードを続けて実行してみます。新規ブックを開いて閉じる処理の追加。
$app = New-Object -ComObject Excel.Application
$books = $app.Workbooks
$book = $books.Open("%NewVar%")
Write-Host $book.Sheets["Sheet1"].Cells[1,1].Text
$addBooks = $app.Workbooks
$addBook = $addBooks.Add()
$book.Close()
$addBook.Close()
$app.Quit()
エラー時におけるオブジェクトの解放漏れによる影響についてはもっと意識高く対策しなければいけないでしょうが、動画を見る限りは、わざわざ明示的に解放しなくてもプロセスは終了するし、もうこれでいいのでは?
「Office オートメーションで割り当てたオブジェクトを解放する – Part1」より、
なお、コンソール アプリケーション等では、処理が一通り実行された後、プロセスが終了する際に CLR ランタイムが終了するのに合わせて、Application オブジェクトの解放および EXCEL.EXE プロセスの終了が実施されます。そのため、解放漏れがあったとしても、プログラム終了時に解放されるため、ほとんどの場合大きな影響がない傾向があります。
プロセスが終了する際に解放処理が実施されるので、少なくともPADから実行する分には意識低くても構わない。
以上
その他のパターン
以下のOKなコードはいずれも実行後にプロセス終了します。
$app = New-Object -ComObject Excel.Application
Write-Host $app.Workbooks.Count
$app.Quit()
0
$app = New-Object -ComObject Excel.Application
$book = $app.Workbooks.Add()
Write-Host $app.Workbooks.Count
Write-Host $book.Worksheets.Count
$app.Quit()
1
1
# 保存しないで閉じる。プロセス終了する。
$app = New-Object -ComObject Excel.Application
$book = $app.Workbooks.Add()
$sheet = $book.Worksheets.Add()
Write-Host $app.Workbooks.Count
Write-Host $book.Worksheets.Count
Write-Host $sheet.Name
$book.Close($false)
$app.Quit()
1
2
Sheet2
# 保存して閉じる。プロセス終了する。
$app = New-Object -ComObject Excel.Application
$book = $app.Workbooks.Open("%NewVar%")
$sheet = $book.Worksheets.Add()
Write-Host $sheet.Name
$book.Close($true)
$app.Quit()
Sheet2
# 非表示にしていても終了時にダイアログは表示される。
# 操作後にプロセス終了する。
$app = New-Object -ComObject Excel.Application
$book = $app.Workbooks.Add()
$sheet = $book.Worksheets.Item(1)
$sheet2 = $book.Worksheets.Add()
Write-Host $sheet.Name
Write-Host $sheet2.Name
$app.Quit()
Sheet1
Sheet2
# 全然ダメ。
# ワークシートを追加したときに変数に入れていない。
$app = New-Object -ComObject Excel.Application
$book = $app.Workbooks.Add()
$book.Worksheets.Add()
Write-Host $app.Workbooks.Count
Write-Host $book.Worksheets.Count
$app.Quit()
PowershellOutput:
Application : Microsoft.Office.Interop.Excel.ApplicationClass
Creator : 1480803660
Parent : System.__ComObject
CodeName :
_CodeName :
Index : 1
Name : Sheet2
Next : System.__ComObject
OnDoubleClick :
OnSheetActivate :
OnSheetDeactivate :
PageSetup : System.__ComObject
Previous :
ProtectContents : False
ProtectDrawingObjects : False
ProtectionMode : False
ProtectScenarios : False
Visible : -1
Shapes : System.__ComObject
TransitionExpEval : False
AutoFilterMode : False
EnableCalculation : True
Cells : System.__ComObject
CircularReference :
Columns : System.__ComObject
ConsolidationFunction : -4157
ConsolidationOptions : {False, False, False}
ConsolidationSources :
DisplayAutomaticPageBreaks : False
EnableAutoFilter : False
EnableSelection : 0
EnableOutlining : False
EnablePivotTable : False
FilterMode : False
Names : System.__ComObject
OnCalculate :
OnData :
OnEntry :
Outline : System.__ComObject
Rows : System.__ComObject
ScrollArea :
StandardHeight : 18
StandardWidth : 8.08
TransitionFormEntry : False
Type : -4167
UsedRange : System.__ComObject
HPageBreaks : System.__ComObject
VPageBreaks : System.__ComObject
QueryTables : System.__ComObject
DisplayPageBreaks : False
Comments : System.__ComObject
Hyperlinks : System.__ComObject
_DisplayRightToLeft : False
_AutoFilter :
DisplayRightToLeft : False
Scripts : System.__ComObject
Tab : System.__ComObject
MailEnvelope : System.__ComObject
CustomProperties : System.__ComObject
SmartTags : System.__ComObject
Protection : System.__ComObject
ListObjects : System.__ComObject
EnableFormatConditionsCalculation : True
_Sort : System.__ComObject
PrintedCommentPages : 0
CommentsThreaded : System.__ComObject
AutoFilter :
Sort : System.__ComObject
NamedSheetViews : System.__ComObject
エディション Windows 11 Pro
バージョン 23H2
インストール日 2022/09/28
OS ビルド 22631.3007
エクスペリエンス Windows Feature Experience Pack 1000.22681.1000.0
-----
Microsoft® Excel® for Microsoft 365 MSO (バージョン 2401 ビルド 16.0.17231.20036) 64 ビット
-----
Power Automate Desktop
version 2.39.00239.23332
Microsoft Store version 11.2312.164.0
あとがき
最後にごめんなさい。ps1のファイルをターミナルから実行したらプロセス残ります。解放処理必要ですね。
……間違った記述で予期せぬ動作になるというのは、その通りだと思います。
関連記事
Office オートメーションで割り当てたオブジェクトを解放する – Part1
Office オートメーションで割り当てたオブジェクトを解放する – Part2
Discussion