2011/12/02

はじめに

このたび、技術系アドベントカレンダーイベントの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パラメータが定義されているそのほかのコマンドレットを示します。

これらのコマンドレットはコマンドレット自体にジョブ実行機能がついているので、単独で実行するだけなら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

2011/09/08

PowerShellでクリップボードを読み書きする方法については5年ほど前にクリップボードアクセス(未完)II という記事を書きました。タイトル通り未完でして、このたび完結編を書こうと思いました。

System.Windows.Forms.Clipboardクラスを使う場合は先の記事にあるようにPowerShellをSTAで動作させる必要があります。ver.1の当時はその方法がありませんでしたが、ver.2ではpowershell.exeに-staパラメータが追加され、STAでの実行が可能となっています。

-staパラメータを使用しない場合は相変わらずこの方法は使えません。powershell.exe –staのプロセスを逐次起こして、クリップボードアクセス部分だけSTAの子プロセスでやらせることも不可能ではないですが、冗長ですしパフォーマンスの点で難があります。

ver.2ではAdd-Typeコマンドレットが追加され、任意のC#コードを動的に読み込むことができるようになりました。これを利用してクリップボードアクセスクラスを書くという手もあると思います。その中ではSTAのスレッドを起こしてクリップボードアクセスをすることになります。実際PowerShell Community Extensionsに含まれるOut-Clipboard/Get-Clipboardコマンドレットはそのような実装がされているようです。ですが動的にコードを読み込みコンパイルはやはりパフォーマンス上不利ですし、PSCXをインストールできない場合などもあるでしょう。

ところでSystem.Windows.Formsに含まれるTextBoxクラスはその名の通りテキストボックスコントロールなのですが、このコントロールにはCopy()メソッドとPaste()メソッドがあり、これらを使うとクリップボードに書き込み、クリップボードから読み込みが可能です。TextBoxクラスの良いところは、STAである必要がないところです。この方法を最初に提唱したのはこちらの方かな?

この方法を用いた関数もすでに公開されているのですが、PowerShellの作法に則った使い方ができるように少し改良を加えました。具体的には

Set-ClipBoard:  配列の指定、パイプラインからの入力を可能にした。

Get-ClipBoard: クリップボードの中身が複数行のテキストの場合、文字列配列を返すようにした。(Get-Contentと同様)

という変更を加えています。

function Set-ClipBoard
{
    param([parameter(Mandatory=$true,
            ValueFromPipeline=$true)][object[]]$InputObject)
    begin
    {
        Add-Type -AssemblyName System.Windows.Forms
        $tb = New-Object System.Windows.Forms.TextBox
        $tb.Multiline = $true
        $out=@()
    }
    process
    {
        $out+=$InputObject
    }
    end
    {
        $tb.Text = $out -join "`r`n"
        $tb.SelectAll()
        $tb.Copy()
    }
}

function Get-ClipBoard
{
    Add-Type -AssemblyName System.Windows.Forms
    $tb = New-Object System.Windows.Forms.TextBox
    $tb.Multiline = $true
    $tb.Paste()
    $tb.Text -replace "`r`n","`n" -replace "`r","`n"  -split "`n"
}

使い方例

# ファイルのフルパスをクリップボードに格納
Get-ChildItem|select -expand fullname|Set-ClipBoard

# CSV文字列がクリップボードに入っている場合以下のコマンドを実行すると、
# クリップボードの中身が対応するHTML tableタグに置換される
Get-ClipBoard|ConvertFrom-Csv|ConvertTo-Html -Fragment|Set-ClipBoard

やっぱりパイプで繋ぐのがPowerShellの醍醐味ですよね!

元記事:http://blogs.wankuma.com/mutaguchi/archive/2011/09/08/202547.aspx

2008/04/01

・スクリプトファイルの拡張子は*.ps3になります

・*.xbox360としても動作します。ただしたまにコンソールの背景が赤くなって落ちることがあります。その場合システムのリカバリが必要となります。

・新たにコマソドレットが追加されます

Sing-Song [-filePath] -lyrics [-singer]

-filePathパラメータにmp3やmidのオケを指定するとV0CAL0ID2エンジンが起動し-lyricsで指定した歌詞を歌います。

The-World -second

時を止めます。Start-Sleepコマソドレットがスレッドを停止するのに対し、OSすべてをフリーズさせます。

Get-MoeGazou [-age][-depth]

ネットからランダムに萌え画像を拾ってきます。-depthパラメータをあまり大きく設定すると準児ポ法に抵触する恐れがありますので注意が必要です。

・新たなPSスナッブイン「World」が追加されます。

PS 世界:\ > cd "日本\兵庫県\芦屋市"
PS 世界:\日本\兵庫県\芦屋市> dir
奥山
奥池町
奥池南町
六麓荘町
....

*4/1はエイプリルフールです。
http://blogs.wankuma.com/naka/archive/2008/03/31/130751.aspx

元記事:http://blogs.wankuma.com/mutaguchi/archive/2008/04/01/130857.aspx

2006/12/09

元記事はhttp://winscript.s41.xrea.com/mt/archives/2005/09/post_5.htmlです。

クリップボードを扱う.NET Frameworkのクラスに、Clipboardクラスがあります。そのSetDataObjectメソッドを使うとクリップボードにテキストデータを格納できる、はずです。が、

[void] [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") 
[System.Windows.Forms.Clipboard]::SetDataObject("あああ")

のようなスクリプトを実行すると、

Exception calling "SetDataObject" with "1" argument(s): "OLE が呼び出される前に、現在のスレッドが Single Thread Apartment (STA) モードに設定されていなければなりません。Main 関数に STAThreadAttribute が設定されていることを確認してください。".

というエラーが出てしまいます。MSHでMain関数にSTAThreadAttributeを指定するにはどうすれば良いのでしょうか? ちなみに

[System.Threading.Thread]::CurrentThread.ApartmentState = 
[System.Threading.ApartmentState]::STA

のようにしてもSTAにはなってくれませんでした。

クリップボードアクセスは、newpopsさんが提案されている、IEのCOMコンポーネントを使う方法で可能になります。

PowerShell Memo - 文字列をクリップボードにコピーする
http://d.hatena.ne.jp/newpops/20061104/p1

元記事:http://blogs.wankuma.com/mutaguchi/archive/2006/12/09/49650.aspx


Copyright © 2005-2018 Daisuke Mutaguchi All rights reserved
mailto: mutaguchi at roy.hi-ho.ne.jp
プライバシーポリシー

Books

Twitter