銀の光と碧い空

クラウドなインフラとC#なアプリ開発の狭間にいるエンジニアの日々

Open for PowerShell Cmdlet v1.1 をリリースしました

visualstudiogallery.msdn.microsoft.com

すでにインストールしている方はアップデートがかかるはずです。

v1.0ではモジュールをImportした状態でPowerShell Consoleが開きましたが、v1.1ではこのConsoleのプロセスにアタッチしてデバッグ可能になりました。

f:id:tanaka733:20150320010433p:plain

ちなみにコードとしてはこんな感じで数行でかけてしまいました。

//PowerShell Consoleのプロセスを起動
var p = new System.Diagnostics.Process
{
    StartInfo = new ProcessStartInfo
    {
        FileName = @"C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe",
        WorkingDirectory = dir,
        Arguments = $"-NoExit -Command \"& {{Import-Module '{outputPath}'}}\""
    }
};
p.Start();

//デバッガーがアタッチできるローカルのプロセス一覧の中から、起動したPowerShell ConsoleのプロセスIdと一致するものを取得
var dteProcess = DTE.Debugger.LocalProcesses.Cast<EnvDTE.Process>().FirstOrDefault(prop => prop.ProcessID == p.Id);

if (dteProcess != null)
{
    //該当のプロセスにアタッチ
    dteProcess.Attach();
    DTE.Debugger.CurrentProcess = dteProcess;
}

引き続き機能要望などお待ちしております。

github.com

PowerShell Cmdlet のデバッグを楽にする、 OpenForPSCmdlet VS拡張

PowerShell Cmdlet のバイナリモジュールを作ったことがある方はわかるとおもうのですが、バイナリモジュールを実際にPowerShellで実行して動作を確認するためには、ビルドしたdllをImport-Moduleで読みこむ必要があります*1。さらに、一度 Import-Module すると、そのPowerShell ConsoleなりISEのプロセスが dll をファイルロックするため、一度 Consoleを閉じないと、リビルドできません*2

そのため、バイナリモジュールをデバッグするときは、ビルドする、PowerShell Console開く、Import-Moduleする、デバッグする。Console閉じる、という流れを繰り返す必要があります。これはめんどい!っていうのを前から思っていたのですが、ようやくVisual Studio拡張として作成しました。

*1:所定のフォルダに配置すればImport-Moduleは必要ないですが、開発中はプロジェクトディレクトリのbin以下に出力することが多いでしょう

*2:閉じずにリビルドやクリーンすると、dllを削除できないためエラーになる

続きを読む

PowerShell Cmdlet バイナリモジュールのプラクティスまとめ (1) スレッド・Rx編

今まで何度か、PowerShell Cmdlet バイナリモジュール(C#でdllを記述し、Import-Moduleで利用するCmdlet)について書いてきました。実際、Cmdletを実戦投入して運用していますが、実際に運用してみるとなかなかはまりどころが多いこともわかりました。何回かにわけて、実際にこうやって解決したという(ベストとは言えないけど)プラクティスを紹介したいと思います。

連載目次

  • スレッド・Rx
  • ロギング
  • 例外処理
  • 設定(Configファイル)

スレッド・Rx

いきなりスレッドなわけですが、これは以前下記の記事で紹介したものを修正したいからです。

このエントリでも触れましたが、PowerShell Cmdletは WriteObject メソッドと WriteError メソッドは、呼ばれたメソッド(たとえば、ProcessRecord メソッド)と同じスレッドから実行する必要があります。という観点でみると、実は一つ漏れがあって例外処理時にエラーが発生するのです。

以前載せていたものでは、Rxの Subscribe メソッドのonError の中で例外処理をしていましたが、このAction の中で直接ThrowTerminatingErrorを実行していました。ThrowTerminatingErrorは内部で WriteError メソッドを実行しているのですが、これが呼び出し元スレッドとは別のスレッドで実行されてしまい、Cmdletが強制終了してしまいます。

というわけで例外処理も含めて、すべての WriteObject メソッドと WriteError メソッドをメソッドの呼び出しスレッドと同じスレッドで実行するように修正したのが以下のコードになります。

今回はRxを使ったサンプルになりましたが、 async/await を使った場合やそのほかの別スレッドでの実行を行う処理も同様です。WriteObject メソッドと WriteError メソッドはメソッドの呼び出しスレッドと同じスレッドから呼びだすようにしないといけません。そして、SynchronizationContext.Current がCmdletクラス内では null になっていることにも注意する必要があります。