見出し画像

PowerShellでCOMオブジェクトを捨てるときの方法をまちがえていた件

こんにちは!雨が続いていますね。雨雲の動きを見ていると、日本列島に水蒸気を吹き付けているかのような状況です。みなさまのご無事を祈ります。

さて今回ですが、長年の間まちがえていたなぁと思うことがありましたので投稿することにしました。PowerShellでCOMを利用するときの操作についてです。きっと例話のこのご時世にPowerShellでCOMオブジェクトを操作するなどということはないかも、と思ったのですが、WMIを使うなどの操作をしようと思うとCOMと深く付き合うことになります。このあたりはPythonのCOM操作をするものやVBA、WSHでのものには該当しなさそうですのでご安心ください(?)。

何をまちがえていたな、と思ったかと言いますと、使用済みのCOMオブジェクトを破棄するときの動きです。以前は以下のようにしていました。

$xls = New-Object -ComObject Excel.Application
// do something.
$xls.Quit()
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($xls)

これでもまちがっているわけではありませんが、タスクマネージャーなどでExcelプロセスを見ると起動したまま残っていることがあります。たとえば、上述の例でQuitメソッドを呼び出す前に変数「xls」を評価してオブジェクトの内容を出力させたとします。このあとにQuitメソッドや上述のCOMオブジェクト解放メソッドを利用すると数値がゼロ以外で返却されてくることがわかると思います。これはCOMの参照カウントがまだ残っているということです。このときGet-Processコマンドレットで見てみるとExcelプロセスは残ったままになります。

そういうわけで、ほんとうはどうするべきかというと、参照をひとつ下のスコープに閉じ込めてしまって、その後にガベージコレクションを起動するというようにします。

& {
 $xls = New-Object -ComObject Excel.Application
 $xls
 $xls.Quit()
}
[System.GC]::Collect()

アンド記号につづいて波かっこで囲まれた部分がありますが、ここはPowerShellではスクリプトブロックと言います。スクリプトブロックの内容をアンド記号で実行しています。そしてガベージコレクションです。こうすると、Get-ProcessをしてみてもExcelプロセスは残らなくなります。

これは実行したスクリプトブロック内のスコープでCOMオブジェクトを参照しているところがなくなり、ガベージコレクションによってCOMオブジェクトが破棄されるためです。そこそこコストの高そうな記述だと思いますが、確実なのはこの方法のようです。こうしない場合は上述コードでいう変数「xls」にNULLを代入するなどの方法があります。Remove-Variableを使っては、と思った時期がわたしにもありましたが、これをしてしまうとCOM参照カウントは残ったままとなるのにPowerShellから変数へアクセスすることができなくなるので、Stop-Processをせざるを得なくなります。

Excelを操作するためのCOMを使うのであれば、素直にVBAなどを使ったほうがよいですね。PowerShellでもNPOIというのがあったと思いますので、そちらを利用してみるとか…

参考にしたページ


この記事が気に入ったらサポートをしてみませんか?