2011/12/02
バックグラウンドジョブの使い方・基本編 [PS Advent Calendar '11]
はじめに
このたび、技術系アドベントカレンダーイベントの1つとして、PowerShell Advent Calendar 2011を企画しました。この記事はその2日目の記事となります。アドベントカレンダーについてはリンク先を参照してください。
今日のテーマはPowerShellのバックグラウンドジョブ機能の使い方についてのまとめです。
バックグラウンドジョブとは
バックグラウンドジョブ機能はその名の通り、ジョブ(具体的にはスクリプト)をバックグラウンドで非同期に実行するものです。PowerShell v2で追加された機能の一つです。インタラクティブシェルでStart-Jobコマンドレットを使用してバックグラウンドジョブ(以下、単に「ジョブ」と表記)を実行すると、新しくpowershell.exeのプロセスが起動しそのままシェルに制御が戻りユーザーは後続の処理を行うことができます。もちろんスクリプトからジョブを実行することも可能です。時間のかかる処理をバックグラウンドで走らせたり、数多くの処理を並列で実行したりするのに重宝します。
起動されたジョブは操作中のpowershell.exeとは別のジョブ用のプロセスで実行され、処理が完了すると呼び出し元でその結果をReceive-Jobコマンドレットを使って受け取ることができます。ジョブは並列して何個も同時に実行できます。なおPowerShellのジョブは1ジョブ=1プロセスです。スレッドではないので注意。
PowerShellのジョブシステムはリモート処理インフラストラクチャの上に構築されているので、たとえローカルPCでもジョブ実行するにはローカルPCをリモート用構成にしておく必要があります。詳しくはabout_Remote_Requirementsを参照のこと。
ジョブはローカルでもリモートでも走らせることができます。以下に具体的な方法を述べていきます。
ローカルコンピュータでのジョブ実行
ローカルコンピュータ上に新しくジョブを作成して開始するにはStart-Jobコマンドレットを用います。
Start-Job {ジョブとして実行したいコマンド、スクリプト}
とするとジョブを実行します。
$job=Start-Job {..}
のようにするとJobオブジェクト(System.Management.Automation.PSRemotingJob)を変数に格納してあとで利用できます。変数で受けない場合はJobオブジェクトの内容が表示されます。
存在するジョブを取得するにはGet-Jobコマンドレットを用います。
Get-Job
で現在実行中のジョブ一覧を表示します。以下に出力例を示します。
Id Name State HasMoreData Location Command -- ---- ----- ----------- -------- ------- 1 Job1 Completed True localhost "test" 3 Job3 Running True localhost start-sleep -sec 120;"...
以下の表は各項目の意味です。
Id | ジョブID番号 |
Name | ジョブの名前 |
State |
Running=実行中のジョブ Stopped=停止したジョブ Complete=完了したジョブ Failed=エラーが出たジョブ |
HasMoreData | 返却されたデータがあるかどうか |
Location | ジョブが実行されているコンピュータ名 |
Command | ジョブで実行されているコマンド、スクリプト |
ジョブの終了を待つにはWait-Jobコマンドレットを用います。
Get-Job|Wait-Job
とすると実行中のジョブすべてが完了するまで待ちます。-timeoutパラメータを使うと最大待ち時間(秒)を指定できます。
Get-Job|Wait-Job -any
とすると実行中のいずれかのジョブが完了するまで待ちます。正確には「対象のジョブが一つ以上完了するまで待つ」という効果なので、完了済みのジョブが1つ以上ある場合に新たにジョブを追加した場合などは想定の動作になりません。あらかじめRemove-Jobで完了済みのジョブを削除するか、Where-ObjectコマンドレットでRunningのみ対象にするようフィルタをかけるかしてください。
ジョブを中止するにはStop-Jobコマンドレットを用います。
Get-Job -id 1|Stop-Job
とするとジョブIDが1のジョブを中止します。
$jobにJobオブジェクトが格納されている場合は
$job|Stop-Job
でもOKです。
ジョブを削除するにはRemove-Jobコマンドレットを用います。
Get-Job|where {$_.state -eq "Completed" -or $_.state -eq "Stopped"}|Remove-Job
とすると完了済みと中止したジョブを削除します。実行中のジョブは削除できませんが-forceパラメータを使って強制削除することは可能です。
ジョブの実行結果データを取得するにはReceive-Jobコマンドレットを用います。
Get-Job|Receive-Job
とすると完了済みのジョブのうち、結果を返却しているもの(HasMoreDataがTrueのジョブ)があればその結果を表示します。-keepパラメータをつければ結果データを保持しますが付けてない場合は参照後破棄します。
*-Job系のコマンドレットの多くはJobオブジェクトを返却するので、パイプラインでどんどん繋げていけます。
Get-Job|Wait-Job -timeout 10|Receive-Job
のように。
ジョブの基本的な使い方に関して詳しくはabout_jobsを参照してください。
イベントサブスクライブ
PowerShell 2.0では.NET Frameworkのオブジェクトのイベントをサブスクライブすることができます。すなわちイベントハンドラを記述することができます。このイベントサブスクライブ機能もジョブ機能を元に構築されています。
たとえばTimerオブジェクトのElapsedイベントをサブスクライブし、タイマーの実行間隔(ここでは1秒)ごとにtest.txtファイルに乱数を追記していくサンプルは次のようになります。
$timer=new-object System.Timers.Timer $timer.Interval=1000 Register-ObjectEvent -EventName Elapsed -SourceIdentifier test -Action {get-random|add-content c:\users\daisuke\test.txt} -InputObject $timer $timer.Enabled=$true
Register-ObjectEventの結果、新しくジョブが生成しそのJobオブジェクトが返却されます。このジョブは-EventNameパラメータで指定したイベントが発生するたび、-Actionパラメータで指定したスクリプトブロックを実行します。
なお、イベントサブスクライブを解除するには
Unregister-Event test
のように-SourceIdentifierパラメータで指定した値を指定してUnregister-Eventコマンドレットを実行することで可能です。サブスクライブを解除してもジョブ自体は削除されない(StateがStoppedになるだけ)ので、必要であればRemove-Jobで削除します。
なお.NETオブジェクトの他にPowerShellスクリプトのカスタムイベント(Register-EngineEvent)、WMIオブジェクトのイベント(Register-WmiEvent)をサブスクライブすることもできます。これらのコマンドレットも同様にイベント発生時の処理をジョブとして登録します。詳しくは各コマンドレットのヘルプを参照してください。
リモートコンピュータでのジョブ実行
最初に述べたとおりPowerShellのジョブ機能はリモートインフラストラクチャの上に構築されています。よってローカルのみならずリモートコンピュータに対してジョブを実行することができます。もちろんリモートコンピュータにもリモート構成されていることが条件です。
基本はInvoke-Commandコマンドレットを用い、
$job=Invoke-Command -ComputerName リモートコンピュータ名 {リモートで実行するコマンド、スクリプト} -asjob
となります。これで{}内の処理がリモートコンピュータ上のPowerShellインスタンスで実行されます。-asJobパラメータをつけることでジョブとして(ローカルPCから見て)非同期に処理できますが、-asJobパラメータを省略すると同期的に実行されます。この場合ジョブは作成されず、リモートでの処理が終了するまでローカル側は待機することになります。
リモートコンピュータに接続するための資格情報を別途入力する必要がある場合は-credentialパラメータを使用します。
Invoke-Command -ComputerName リモートコンピュータ名 {リモートで実行するコマンド、スクリプト} -asjob -credential ユーザー名
とするとパスワードを入力するダイアログが表示されます。なお、スクリプトで動かすときなどあらかじめ入力したパスワードを指定したい場合の方法は以前書きました。
同じコマンドを複数のリモートPCで同時実行することも可能で、その場合は-computerNameパラメータにリモートコンピュータ名の配列を指定します(「,」区切り)。この場合ローカルPCで見えるジョブとしては1つですが、そのジョブにリモートコンピュータの数だけ子ジョブ(ChildJobs)が作成されています。
このように子ジョブが複数ある場合にReceive-Jobするときは
$job|Receive-Job -location リモートコンピュータ名
あるいは
$job.ChildJobs
として表示される子ジョブの名前(Name)を調べ、
Receive-Job -name 子ジョブの名前
とすることでリモートコンピュータごとに結果を取得できます。
すべての結果をまとめて取得するなら
Receive-Job $job
とします。
$job|Receive-Jobはなぜか駄目なようです。
固定セッションを用いたリモーティング
同じリモートPCに対して何度もコマンドを実行させたい場合、毎回リモートコンピュータ名を指定してセッションを張るのは非効率的なので、リモートセッションを確立したあとその固定セッションを何度も使用する方法が用意されています。新しく固定セッションを確立するにはNew-PSSessionコマンドレットを用い、
$session=New-PSSession リモートコンピュータ名
とすると固定セッションが確立され、$session変数にそのセッションオブジェクトが格納されます。あとは
Invoke-Command $session {リモートで実行するコマンド、スクリプト} -asjob
とすればそのたびにそのセッションを用いてリモートでコマンドを実行できるようになります。
ここまでの説明はリモートコンピュータでしてきましたが、ローカルコンピュータに対して固定セッションを張ることも可能です。
さらに、Enter-PSSessionコマンドレットを用いると作成したセッションに入ってリモートコンピュータ上のPowerShellを対話実行することも可能です。
Enter-PSSession $session
とすると、プロンプトが
PS カレントディレクトリ>
から
[リモートコンピュータ名]: PS カレントディレクトリ>
に変化し、以降リモートのPowerShellをローカルPCから対話実行できます。
なおこの状態から抜けるにはexitもしくはExit-PSSessionと入力して実行します。
ジョブ実行できるそのほかのコマンドレット
これまで述べたコマンドレット以外にも、いくつかのコマンドレットはジョブ実行(ローカルorリモート)することができます。ジョブ実行するには-asJobパラメータを使用します。以下にv2の段階で-asJobパラメータが定義されているそのほかのコマンドレットを示します。
- Get-WmiObject
- Invoke-WmiMethod
- Remove-WmiObject
- Set-WmiInstance
- Test-Connection
- Restart-Computer
- Stop-Computer
これらのコマンドレットはコマンドレット自体にジョブ実行機能がついているので、単独で実行するだけならStart-JobやInvoke-Commandを用いる必要がありません。v2ではWMIを扱うコマンドレットにのみ-asJobパラメータが存在するようです(ここに挙げたコマンドレットはすべてWMIの機能を呼び出すもの)。なお、-asJobパラメータが使用できるコマンドレットの一覧を取得するのに、fsugiyamaさんの1日目の記事の問15のスクリプトを使用させていただきました。
おわりに
PowerShell Advent Calendar 2011二日目は、PowerShellのバックグラウンドジョブ機能概要についてまとめてみました。実はバックグランドジョブ機能のTipsを書こうと思ってその前ふりとして書き始めたのですが、これだけでかなりの量になってしまったので概要だけ一記事としてまとめることにしました。おそらくPSアドベントカレンダーに私はあと何回か登場することになりそうですので、Tips編はその際に書こうと思います。
さて、明日三日目は@jsakamotoさんのご登場ですね。よろしくお願いします!
そして参加者はまだまだ募集中ですよ!→PowerShell Advent Calendar 2011
プライバシーポリシー