tech.guitarrapc.cóm

Technical updates

Register-ObjectEvent を .Dispose() すると PowerShell.exe が Unhandled Error で終了するのを回避する

PowerShell を使っていると、突然の PowerShell.exe の死にあうことがあります。

今回は、Register-ObjectEvent の Dispose に関してです。

目次

PSEventJob のDispose() に失敗する例

さて、以下の指定したプロセスを実行する関数があります。Start-Process では困ることが多いので重宝するのです。

gist.github.com

こんな感じで呼び出します。

Invoke-Process -FileName 'git' -Arguments "pull" -WorkingDirectory "c:\GitHub\Repogitory"

しかし、これで git を呼び出すと Unhandled Error が起こって、例外で死ぬことなく PowerShell.exe ホストごと死ぬ場合があることに気づきました。さいてーです。

原因は、Unregister-ObjectEvent 後に .Dispose() をしているここにあります。

https://gist.github.com/guitarrapc/0ea30c730ea77efa0217#file-invaliddispose-ps1-L80-L81

PSEventJob のDispose() に成功する例

先ほどの例をわずかに変えるだけです。

gist.github.com

変更点は、.Dispose() 前に、StopJob() メソッドを呼び出しただけです。

https://gist.github.com/guitarrapc/dd05e671eea67059acb1#file-invoke-process-ps1-L80-L81

これだけで、例外が発生しなくなります。

どういう状況で発生しえるのか

MSDN で、.StopJob() メソッドをみてみましょう。

PSEventJob.StopJob Method (System.Management.Automation) | Microsoft Learn

Stops the action that is performed by the job. This method is introduced in Windows PowerShell 2.0.

そして、 .Dispose() は、通常の .NET での Disposeパターンにのっとって実装しているとあります。

Job.Dispose Method (System.Management.Automation) | Microsoft Learn

Releases the resources that are used by the Job object. These methods implement the Dispose pattern used to release managed and unmanaged resources. This method is introduced in Windows PowerShell 2.0. For an explanation of the Dispose pattern, see Implementing a Dispose Method.

実は例外が発生する場合は、Finished プロパティ や JobStateInfo が完了となっていないことがわかります。まだ、ジョブがうごいています。

Job.Finished Property (System.Management.Automation) | Microsoft Learn

今回の例では、プロセスが吐いた出力イベントを非同期に取得していました。が、出力が多すぎる場合にプロセス完了時にまとめてイベントを破棄しようとしても、イベント処理が完了していませんでした。

この例では、プロセス完了で出力を切りたかったので、StopJob()Dispose() 前に呼び出すことで、リソースを破棄できるようにしています。

まとめ

PowerShell でイベント難しいです..。

この症状は、GitContinuousPull モジュールを動かしていて稀に PowerShell.exe が死ぬため気づきました。

ver.1.6.4 で修正済みです。

github.com