コマンドレットのパラメータを列挙してみる。2011の19日目の記事、そしてこれが私の記事では3回目となります。今回も前々回、前回からの引き続きでバックグラウンドジョブについての話題です。2.0におけるバックグラウンドジョブの機能の使い方を解説してきましたが、今回はPowerShellの次期バージョンである3.0に追加される予定の機能のうち、ジョブ関係のものをピックアップしてみます。Framework(WMF)3.0のCTP2が公開されています。3.0が含まれています。注意:本記事で取り上げた内容は製品のプレビュー版をもとに記述しています。そのためリリース版では内容が一致しない可能性があることをご承知おきください。using:ラベル前回、ジョブに値を渡す方法について解説しましたが、-argumentListに引数として渡すというのは正直めんどうです。呼び出し元のグローバル変数を直接ジョブ側から参照したいですよね。v3では新たに変数に付けるusing:ラベルというのが追加されました。このラベルをジョブのスクリプトブロック内で使うと、呼び出し元の変数を参照することができます。3.0」と表示され、たしかにジョブのスクリプトブロックから呼び出し元の変数を参照できていることがわかります。残念ながらこの方法を使ってもスクリプトブロックをジョブに渡すことはできないようです。相変わらず文字列にキャストされてしまいました。-asJobで複数リモートコンピュータに対してジョブを走らせた場合、そのジョブに対して$job|Receive-Jobがなぜか機能しない、と書きましたがこの問題が解決されています。そもそもなんでこの問題が発生していたのか、面白いのでちょっと解説します。(ByPropertyName)」フラグがついていたのが原因でした。複数コンピュータに対して実行したジョブは子ジョブを複数持ちますが、親ジョブ自体は配列ではありません。そのLocationプロパティには子ジョブが実行されているコンピュータ名が"remote01,remote02,remote03"のようなカンマ区切りの文字列として格納されています。よってこのジョブオブジェクトをパイプラインを通じてReceive-Jobコマンドレットに渡すと、ValueFromPipelineByPropertyName属性が付いている-locationパラメータにジョブオブジェクトのLocationプロパティの値が渡されますが、その値はカンマ区切りの文字列なので正しく解釈されず、結果として期待の動作をしなかったわけです。-locationのValueFromPipelineByPropertyName属性が取り除かれ、問題なく動作するようになりました。他の変更点としてはReceive-Jobにジョブが完了するまで待つための-waitパラメータが追加されました。$job|Wait-Job|Receive-Jobと違いが分からないかも...。Get-Jobコマンドレットの変更点Get-Jobに-filterパラメータが追加されました。連想配列でジョブにフィルタをかけられるものです。@{State="Completed";Location="localhost"}where-objectを使わずともフィルタできるので便利、かも。個人的には-filterパラメータはいろんなコマンドレットで定義されているものの、使い方がそれぞれ異なるのがとてもとてもイヤです。覚えられないのでヘルプを引くところから始まっちゃいますので。パフォーマンスの関係上、Where-Objectを使うよりコマンドレット内部でフィルタしたほうが速くなるというのはわかるのですが、もう少しフィルタ方式に統一性を持たせられなかったんだろうかとか思いますね。Get-Jobにはほかに-afterと-beforeというパラメータが追加されています。これは後述するPSScheduledJobの完了時刻をDateTimeで範囲指定し、フィルタするものです。Workflowです。文字通り、PowerShellでワークフローが記述できるようになります。Workflowは関数の一種なのですが、長時間を要するタスクやリモート実行や並列実行などで使うことを主目的としているようです。functionキーワードの代わりにworkflowキーワードでワークフローを定義すると、自動的に実行対象コンピュータ名や資格情報といったパラメータが複数定義されるので、これらのパラメータを特に定義なしで利用することができます。workflow内ではparallelブロックを定義でき、その中に記述された各行は並列に実行されます。for/foreachステートメントで-parallelパラメータが利用可能になり、繰り返し処理やコレクションの列挙を並列して行うことができるようになります。自動定義されるパラメータに-asJobがあり、これを利用するとworkflowをジョブとして実行できます。このジョブは通常のジョブとは違い、新たに追加されたSuspend-JobコマンドレットとResume-Jobを使うことによって、ジョブの一時中断と再開ができます。このジョブの中断と再開は、リモートコンピュータ上でワークフローを走らせてるときでも可能ですし、中断後リモートセッションが切断されたあとに再開することもできますし、リモートコンピュータがシャットダウンしても再起動後にジョブを再開することまでできてしまいます。これらはWMFにおけるリモート基盤を支えているWinRMの最新バージョン、WinRM3.0が実現している機能です。connectionと称しているようです。Workflowに変換したり(逆もできる?)、Invoke-Expressionでxamlを実行したりできるらしいです。WF側でもPowerShellの多くの機能がアクティビティとして使用できたりして、WFとPowerShellがWMFというシステム管理フレームワークの主要なパーツとして密に連携していくようです。Workflowではできないことがとてもたくさんあります。forの中でbreakやcontinueステートメントが使えないとかStart-Sleepは-Secondパラメータしか指定できない(ミリ秒単位でスリープかけられない)とか色々あります。WorkflowとPSスクリプトの違いというドキュメントが公開されるんじゃないかと思います。ちなみにWinRM3.0のおかげでワークフローではない通常のリモートジョブでも、New-PSSessionで作成したセッションの中でジョブを実行した場合、そのジョブが動作しているコンピュータへのセッションを切断(Disconnect-PSSession)したあと、セッションに再接続(Connect-PSSessionやReceive-PSSession)すればジョブの結果を取得したりすることができます。セッションを作製したインスタンス(powershell.exe)でそのセッションを切断すると、それ以降は別のインスタンスやコンピュータからそのセッションにConnect-PSSessionで接続することができます。8でも2000を超える膨大な量になります。8では従来のコマンドプロンプトから実行するコンソールexeコマンドのほとんどすべてをPowerShellコマンドレットに置き換える措置のためです。従来のコマンドは互換性のために残されますが、netsh.exeなど一部のコマンドではPowerShellへの移行を促すメッセージが表示されたりするようになるようです。8に新しく追加されるモジュールの一つで、schtasks.exeを置き換えるものとなります。これまでPowerShellでタスクスケジューラを扱うにはschtasks.exeを使うか、WMIのWin32_ScheduledJobを使う必要があり面倒でしたが、このモジュールに含まれるコマンドレットを用いるとそれが容易に行えるようになります。「notepad.exeを毎日朝10:00に起動する。testとすれば可能であるはずです。Preview版ではこのコードは機能しません。タスクのトリガを作成するNew-ScheduledTaskTriggerコマンドレットが正しいオブジェクトを作ってくれないのです。これは将来のバージョンできっと修正されるかと思います。トリガを定義する部分をはずせば(あんまり意味はないですが)このコードは動作するので、やり方はたぶんあってると思います。Register-ScheduledTaskコマンドレットには-asJobパラメータがあり、タスクスケジューラへの登録をジョブとしてバックグラウンドで行うことができます。ScheduledTasksモジュールはWMIを利用してタスクスケジューラを操作するので、ほかのWMI関係のコマンドレットと同様ですね。Discoveryという機能によりImport-Moduleは実行しなくてもScheduledTasksモジュールに含まれるコマンドレットを利用することができます。Discoveryとは現在読み込まれていて実行可能なコマンドレットの中にない、未知のコマンドレットを実行しようとしたとき、Modulesフォルダに存在するモジュールから同名のコマンドレットが定義されているものを探し出し、発見できたらそのモジュールを読み込んだうえでコマンドレットを実行するという優れた機能です。Discoveryによってモジュールがシェルに読み込まれればあとは快適にコマンドレットを実行できるようになります。PSScheduledJobモジュールScheduledTasksモジュールは-asJobパラメータが定義されているくらいで実はそれほどPowerShellのジョブとは関係ないのですが、ScheduledTasksモジュールが内包しているPSScheduledJobモジュールはPowerShellのジョブ機能と大いに関係があります。hoge.ps1"を指定して、みたいなまわりくどいことをする必要がありました。新しく追加されるPSScheduledJobモジュールに含まれるコマンドレット群はこの問題を解消します。PowerShellスクリプト(.ps1)あるいはスクリプトブロックをPSScheduledJobとして直接タスクスケジューラに登録できるようになり、PowerShellとタスクスケジューラのシームレスな連携を実現します。3.0に付属のモジュールなので、Win7などでも使用可能になる予定です。使用例を見ていきましょう。ScheduledJobTest1まずNew-JobTriggerコマンドレットによってトリガー(具体的には実行時刻など)を定義します。ここでは決められた時刻に1回実行するものと、毎日同じ時刻に実行するものの2つを定義してみました。これらの時刻に実行したい内容をスクリプトブロックに記述し、これらをRegister-ScheduledJobコマンドレットで登録してやります。するとこのスクリプトブロックはタスクスケジューラに登録され、指定時刻になると指定したスクリプトブロックの内容が実行されます。ライブラリ\Microsoft\Windows\PowerShell\ScheduledJobs」に登録されています。このタスクのアクションは具体的には次のようになっています。Wait-Job"これによると、指定時刻に実際にタスクスケジューラによって実行されるのはpowershell.exeであり、Start-Jobコマンドレットを使って登録したスケジュールをPowerShellのジョブとして実行していることがわかります。Start-Jobコマンドレットの-DefinitionNameパラメータなどはPSScheduledJobのために追加されたもので、これによりRegister-ScheduledJobが出力したPSScheduledJob定義をファイルから読み込んでジョブとして実行できるようになっています。PSScheduledJob定義とジョブの出力は-DefinitionPathで指定されているフォルダの下にxmlファイルとして保存されているので興味がある方は覗いてみるといいかもしれません。スケジュールしたジョブの実行結果はどうやって受け取ればいいのでしょうか。ScheduledJobTest1とすることでJobオブジェクトとして取得することができるようになります。あとは通常のジョブと同じ取り扱いができるので、$job|Receive-Jobなどで実行結果を取得できます。ちなみにPSScheduledJobはそれを定義したインスタンス以外でも参照することができます。PSScheduledJobしたあとGet-Job|Receive-JobしてPSScheduledJobの結果を参照、みたいなことができます。ここで紹介した一連の操作ではスクリプトブロックをPSScheduledJobにしましたが、Register-ScheduledJobコマンドレットの-FilePathパラメータを用いれば.ps1ファイルをPSScheduledJobとして登録することも可能です。現行バージョンのPowerShellはとにかく起動が遅いため、タスクスケジューラにスクリプトを登録しても実行が始まるまで何十秒も待たされるなどはざらでしたが、PSv3は起動がずいぶん速くなり、スペックや状況にもよるとは思いますがpowershell.exeの起動後ほんの数秒でスクリプトが走り始めます。この速度のおかげもあってPSScheduledJobはきっととても有効に機能するんじゃないかと思います。3.0で増強されるバックグラウンドジョブ関係の機能をまとめてみました。これらの新機能のおかげで、時間のかかる処理や定期実行する処理を扱うのが飛躍的にやりやすくなりそうです。3.0で追加される機能は他にもたくさんあって、このブログでもいつか全部紹介したいと思ってるのですが、今回取り上げたジョブ関係はその中でもかなり重要な機能増加を多く含んでいると言えるでしょう。8のリリースに備えてジョブ関係から予習しておくのは悪くないと思いますよ。なんか25日のアドベントカレンダーのうち3回もバックグラウンドジョブネタをやって、PSアドベントカレンダーというより私だけ一人でPSジョブアドベントカレンダーをやってる感じでちょっと申し訳ないんですが、どうか許してください。前回は今回で終了するって言ってたんですが、実はまだジョブ関係の小ネタが残ってるので最終日25日にさせてください。今日のところはこのへんで。明日はwaritohutsuさんの登場です。2011を企画しました。この記事はその2日目の記事となります。アドベントカレンダーについてはリンク先を参照してください。今日のテーマはPowerShellのバックグラウンドジョブ機能の使い方についてのまとめです。バックグラウンドジョブとはバックグラウンドジョブ機能はその名の通り、ジョブ(具体的にはスクリプト)をバックグラウンドで非同期に実行するものです。v2で追加された機能の一つです。インタラクティブシェルでStart-Jobコマンドレットを使用してバックグラウンドジョブ(以下、単に「ジョブ」と表記)を実行すると、新しくpowershell.exeのプロセスが起動しそのままシェルに制御が戻りユーザーは後続の処理を行うことができます。スクリプトからジョブを実行することも可能です。時間のかかる処理をバックグラウンドで走らせたり、数多くの処理を並列で実行したりするのに重宝します。起動されたジョブは操作中のpowershell.exeとは別のジョブ用のプロセスで実行され、処理が完了すると呼び出し元でその結果をReceive-Jobコマンドレットを使って受け取ることができます。ジョブは並列して何個も同時に実行できます。PowerShellのジョブは1ジョブ=1プロセスです。スレッドではないので注意。PowerShellのジョブシステムはリモート処理インフラストラクチャの上に構築されているので、たとえローカルPCでもジョブ実行するにはローカルPCをリモート用構成にしておく必要があります。詳しくはabout_Remote_Requirementsを参照のこと。ジョブはローカルでもリモートでも走らせることができます。以下に具体的な方法を述べていきます。ローカルコンピュータでのジョブ実行ローカルコンピュータ上に新しくジョブを作成して開始するにはStart-Jobコマンドレットを用います。{ジョブとして実行したいコマンド、スクリプト}とするとジョブを実行します。{..}のようにするとJobオブジェクト(System.Management.Automation.PSRemotingJob)を変数に格納してあとで利用できます。変数で受けない場合はJobオブジェクトの内容が表示されます。存在するジョブを取得するにはGet-Jobコマンドレットを用います。Get-Jobで現在実行中のジョブ一覧を表示します。以下に出力例を示します。120;"...以下の表は各項目の意味です。ジョブで実行されているコマンド、スクリプトジョブの終了を待つにはWait-Jobコマンドレットを用います。Get-Job|Wait-Jobとすると実行中のジョブすべてが完了するまで待ちます。-timeoutパラメータを使うと最大待ち時間(秒)を指定できます。-anyとすると実行中のいずれかのジョブが完了するまで待ちます。正確には「対象のジョブが一つ以上完了するまで待つ」という効果なので、完了済みのジョブが1つ以上ある場合に新たにジョブを追加した場合などは想定の動作になりません。Remove-Jobで完了済みのジョブを削除するか、Where-ObjectコマンドレットでRunningのみ対象にするようフィルタをかけるかしてください。ジョブを中止するにはStop-Jobコマンドレットを用います。1|Stop-JobとするとジョブIDが1のジョブを中止します。$jobにJobオブジェクトが格納されている場合は$job|Stop-JobでもOKです。ジョブを削除するにはRemove-Jobコマンドレットを用います。"Stopped"}|Remove-Jobとすると完了済みと中止したジョブを削除します。実行中のジョブは削除できませんが-forceパラメータを使って強制削除することは可能です。ジョブの実行結果データを取得するにはReceive-Jobコマンドレットを用います。Get-Job|Receive-Jobとすると完了済みのジョブのうち、結果を返却しているもの(HasMoreDataがTrueのジョブ)があればその結果を表示します。-keepパラメータをつければ結果データを保持しますが付けてない場合は参照後破棄します。*-Job系のコマンドレットの多くはJobオブジェクトを返却するので、パイプラインでどんどん繋げていけます。10|Receive-Jobのように。ジョブの基本的な使い方に関して詳しくはabout_jobsを参照してください。Frameworkのオブジェクトのイベントをサブスクライブすることができます。イベントハンドラを記述することができます。このイベントサブスクライブ機能もジョブ機能を元に構築されています。TimerオブジェクトのElapsedイベントをサブスクライブし、タイマーの実行間隔(ここでは1秒)ごとにtest.txtファイルに乱数を追記していくサンプルは次のようになります。$timer$timer.Enabled=$trueRegister-ObjectEventの結果、新しくジョブが生成しそのJobオブジェクトが返却されます。このジョブは-EventNameパラメータで指定したイベントが発生するたび、-Actionパラメータで指定したスクリプトブロックを実行します。testのように-SourceIdentifierパラメータで指定した値を指定してUnregister-Eventコマンドレットを実行することで可能です。サブスクライブを解除してもジョブ自体は削除されない(StateがStoppedになるだけ)ので、必要であればRemove-Jobで削除します。.NETオブジェクトの他にPowerShellスクリプトのカスタムイベント(Register-EngineEvent)、WMIオブジェクトのイベント(Register-WmiEvent)をサブスクライブすることもできます。これらのコマンドレットも同様にイベント発生時の処理をジョブとして登録します。詳しくは各コマンドレットのヘルプを参照してください。最初に述べたとおりPowerShellのジョブ機能はリモートインフラストラクチャの上に構築されています。よってローカルのみならずリモートコンピュータに対してジョブを実行することができます。リモートコンピュータにもリモート構成されていることが条件です。-asjobとなります。これで{}内の処理がリモートコンピュータ上のPowerShellインスタンスで実行されます。-asJobパラメータをつけることでジョブとして(ローカルPCから見て)非同期に処理できますが、-asJobパラメータを省略すると同期的に実行されます。この場合ジョブは作成されず、リモートでの処理が終了するまでローカル側は待機することになります。リモートコンピュータに接続するための資格情報を別途入力する必要がある場合は-credentialパラメータを使用します。ユーザー名とするとパスワードを入力するダイアログが表示されます。スクリプトで動かすときなどあらかじめ入力したパスワードを指定したい場合の方法は以前書きました。同じコマンドを複数のリモートPCで同時実行することも可能で、その場合は-computerNameパラメータにリモートコンピュータ名の配列を指定します(「,」区切り)。この場合ローカルPCで見えるジョブとしては1つですが、そのジョブにリモートコンピュータの数だけ子ジョブ(ChildJobs)が作成されています。子ジョブの名前とすることでリモートコンピュータごとに結果を取得できます。$job|Receive-Jobはなぜか駄目なようです。固定セッションを用いたリモーティング同じリモートPCに対して何度もコマンドを実行させたい場合、毎回リモートコンピュータ名を指定してセッションを張るのは非効率的なので、リモートセッションを確立したあとその固定セッションを何度も使用する方法が用意されています。リモートコンピュータ名とすると固定セッションが確立され、$session変数にそのセッションオブジェクトが格納されます。-asjobとすればそのたびにそのセッションを用いてリモートでコマンドを実行できるようになります。ここまでの説明はリモートコンピュータでしてきましたが、ローカルコンピュータに対して固定セッションを張ることも可能です。Enter-PSSessionコマンドレットを用いると作成したセッションに入ってリモートコンピュータ上のPowerShellを対話実行することも可能です。カレントディレクトリ>に変化し、以降リモートのPowerShellをローカルPCから対話実行できます。この状態から抜けるにはexitもしくはExit-PSSessionと入力して実行します。ジョブ実行できるそのほかのコマンドレットこれまで述べたコマンドレット以外にも、いくつかのコマンドレットはジョブ実行(ローカルorリモート)することができます。ジョブ実行するには-asJobパラメータを使用します。以下にv2の段階で-asJobパラメータが定義されているそのほかのコマンドレットを示します。Get-WmiObjectInvoke-WmiMethodRemove-WmiObjectSet-WmiInstanceTest-ConnectionRestart-ComputerStop-Computerこれらのコマンドレットはコマンドレット自体にジョブ実行機能がついているので、単独で実行するだけならStart-JobやInvoke-Commandを用いる必要がありません。v2ではWMIを扱うコマンドレットにのみ-asJobパラメータが存在するようです(ここに挙げたコマンドレットはすべてWMIの機能を呼び出すもの)。-asJobパラメータが使用できるコマンドレットの一覧を取得するのに、fsugiyamaさんの1日目の記事の問15のスクリプトを使用させていただきました。2011二日目は、PowerShellのバックグラウンドジョブ機能概要についてまとめてみました。実はバックグランドジョブ機能のTipsを書こうと思ってその前ふりとして書き始めたのですが、これだけでかなりの量になってしまったので概要だけ一記事としてまとめることにしました。PSアドベントカレンダーに私はあと何回か登場することになりそうですので、Tips編はその際に書こうと思います。明日三日目は@jsakamotoさんのご登場ですね。LANを使ってリモートコンピュータを起動するスクリプトを投稿しました。ActiveDirectoryの特定OUに属するPC/サーバーすべてに対してWMIを用いてMACアドレスを収集するスクリプトも付属しています。リモートシャットダウンスクリプトとあわせてご活用ください。スクリプトセンターに、リモートコンピュータをシャットダウンするPowerShellスクリプトを投稿しました。PC/サーバーを一括してシャットダウンすることができます。対象とするコンピュータの一覧とシャットダウンに必要な資格情報を出力して保存しておけば、タスクスケジューラなどで決まった時間に自動的に実行することも可能です。3/11に東日本で発生した大地震とそれに伴う原発事故の影響で、関東地方および東北地方で計画停電が実施・予定されています。PC/サーバーのシャットダウン操作を行わずに電源供給が絶たれると、データの損失やハードウェアへのダメージが懸念されます。このスクリプトによってこれらの事態を回避する役に立てば幸いです。震災で亡くなられた方々のご冥福をお祈りするとともに、被害を受けた方々に心よりお見舞い申し上げます。@mutaguchiBooks書籍版。