Powershellはコマンドを並行して実行できますか?


125

大量の画像に対していくつかのバッチ処理を実行するPowerShellスクリプトがあり、並列処理を実行したいと考えています。Powershellには、start-job、wait-jobなどのバックグラウンド処理オプションがあるようですが、並列処理を行うために見つけた唯一の優れたリソースは、スクリプトのテキストを書き、それらを実行することでした(PowerShellマルチスレッド

理想的には、.net 4のforeachに類似したものを希望します。

かなりのように見えないもの:

foreach-parallel -threads 4 ($file in (Get-ChildItem $dir))
{
   .. Do Work
}

多分私はc#にドロップダウンするほうがいいでしょう...


tl; dr: receive-job (wait-job ($a = start-job { "heyo!" })); remove-job $aまたはジョブが完了する前に$a = start-job { "heyo!" }; wait-job $a; receive-job $a; remove-job $a呼び出すとreceive-job、何も得られない場合があることにも注意してください。
アンドリュー、

また(get-job $a).jobstateinfo.state;
Andrew

回答:


99

バックグラウンドジョブを使用して、Powershell 2で並列ジョブを実行できます。Start-Jobおよびその他のジョブコマンドレットを確認してください。

# Loop through the server list
Get-Content "ServerList.txt" | %{

  # Define what each job does
  $ScriptBlock = {
    param($pipelinePassIn) 
    Test-Path "\\$pipelinePassIn\c`$\Something"
    Start-Sleep 60
  }

  # Execute the jobs in parallel
  Start-Job $ScriptBlock -ArgumentList $_
}

Get-Job

# Wait for it all to complete
While (Get-Job -State "Running")
{
  Start-Sleep 10
}

# Getting the information back from the jobs
Get-Job | Receive-Job

3
そのため、この提案を何度か試しましたが、変数が正しく展開されていないようです。同じ例を使用すると、この行が実行されると、現在のアイテムTest-Path "\\$_\c$\Something"に展開$_されることが期待されます。ただし、そうではありません。代わりに、空の値を返します。これは、スクリプトブロック内からのみ発生するようです。最初のコメントの直後にその値を書き出すと、正しく機能しているようです。
rjg 2011

1
@likwid-サイトに対する別の質問のように聞こえる
Steve Townsend

バックグラウンドで実行されているジョブの出力を表示するにはどうすればよいですか?
SimpleGuy 2017年

@SimpleGuy - -出力のキャプチャの詳細はこちらをご覧stackoverflow.com/questions/15605095/... -あなたがバックグラウンドジョブが完了するまで確実にこれを表示することができますように見えるしていません。
Steve Townsend 2017年

@SteveTownsendありがとうございます!実際に出力を表示することは、画面上ではそれほど良くありません。遅延が伴うので、私には役に立ちません。代わりに、新しいターミナル(シェル)でプロセスを開始したので、各プロセスは異なるターミナルで実行され、進行状況をより良く、よりクリーンに表示します。
SimpleGuy 2017年

98

Steve Townsendからの回答は理論的には正しいですが、@ likwidが指摘したように実際には正しくありません。私の改訂されたコードは、ジョブコンテキストの障壁を考慮に入れています。したがって、自動$_変数はループで使用できますが、ジョブによって作成された別のコンテキスト内にあるため、スクリプトブロック内で直接使用することはできません。

変数を親コンテキストから子コンテキストに渡すには、-ArgumentListパラメーターon Start-Jobを使用して送信しparam、スクリプトブロック内で使用して受信します。

cls
# Send in two root directory names, one that exists and one that does not.
# Should then get a "True" and a "False" result out the end.
"temp", "foo" | %{

  $ScriptBlock = {
    # accept the loop variable across the job-context barrier
    param($name) 
    # Show the loop variable has made it through!
    Write-Host "[processing '$name' inside the job]"
    # Execute a command
    Test-Path "\$name"
    # Just wait for a bit...
    Start-Sleep 5
  }

  # Show the loop variable here is correct
  Write-Host "processing $_..."

  # pass the loop variable across the job-context barrier
  Start-Job $ScriptBlock -ArgumentList $_
}

# Wait for all to complete
While (Get-Job -State "Running") { Start-Sleep 2 }

# Display output from all jobs
Get-Job | Receive-Job

# Cleanup
Remove-Job *

(私は通常、裏付けとなる証拠としてPowerShellドキュメントへの参照を提供したいのですが、悲しいことに、私の検索は無益でした。コンテキスト分離が文書化されている場所を知っている場合は、ここにコメントを投稿して知らせてください!)


この回答をありがとう。私はあなたのソリューションを使ってみましたが、完全に機能させることができませんでした。ここで私の質問を見てください。stackoverflow.com
デビッドはモニカを

または、別のスクリプトファイルを呼び出すのも簡単です。そのまま使用Start-Job -FilePath script.ps1 -ArgumentList $_
Chad Zawistowski 2016年

代替のアプローチは、変数の展開以外は何も行われていないスクリプト生成の予備パスを実行してから、生成されたスクリプトを並行して呼び出すことです。スクリプトの生成をサポートすることを意図したものではありませんが、スクリプトの生成に適応できる小さなツールがあります。こちらご覧いただけます
Walter Mitty

これは機能します。しかし、ScriptBlockからライブフィード出力ストリームを取得できません。出力は、ScriptBlockが戻るときにのみ出力されます。
vothaison

8

http://gallery.technet.microsoft.com/scriptcenter/Invoke-Async-Allows-you-to-83b0c9f0

複数のスクリプトブロック/コマンドレット/関数を同時に実行できる、invoke-asyncを作成しました。これは、小さなジョブ(サブネットスキャンまたは数百台のマシンに対するwmiクエリ)に最適です。これは、実行スペースを作成するオーバーヘッドとstart-jobの起動時間のオーバーヘッドがかなり大きいためです。そのまま使えます。

スクリプトブロックで、

$sb = [scriptblock] {param($system) gwmi win32_operatingsystem -ComputerName $system | select csname,caption} 

$servers = Get-Content servers.txt 

$rtn = Invoke-Async -Set $server -SetParam system  -ScriptBlock $sb

コマンドレット/関数のみ

$servers = Get-Content servers.txt 

$rtn = Invoke-Async -Set $servers -SetParam computername -Params @{count=1} -Cmdlet Test-Connection -ThreadCount 50

8

最近、これに対する答えはたくさんあります。

  1. ジョブ(またはPS 6/7またはモジュールのスレッドジョブ)
  2. 開始プロセス
  3. ワークフロー
  4. 別の実行空間を備えたpowershell api
  5. すべてlocalhostにすることができる複数のコンピューターでのinvoke-command(管理者である必要があります)
  6. ISEの複数のセッション(実行スペース)タブ、またはリモートのPowerShell PowerShell ISEタブ
  7. Powershell 7はforeach-object -parallel#4の代替として

文字通りforeach -parallelを使用したワークフローを次に示します。

workflow work {
  foreach -parallel ($i in 1..3) { 
    sleep 5 
    "$i done" 
  }
}

work

3 done
1 done
2 done

または、並列ブロックを使用したワークフロー:

function sleepfor($time) { sleep $time; "sleepfor $time done"}

workflow work {
  parallel {
    sleepfor 3
    sleepfor 2
    sleepfor 1
  }
  'hi'
}

work 

sleepfor 1 done
sleepfor 2 done
sleepfor 3 done
hi

ランスペースを使用したAPIの例を次に示します。

$a =  [PowerShell]::Create().AddScript{sleep 5;'a done'}
$b =  [PowerShell]::Create().AddScript{sleep 5;'b done'}
$c =  [PowerShell]::Create().AddScript{sleep 5;'c done'}
$r1,$r2,$r3 = ($a,$b,$c).begininvoke() # run in background
$a.EndInvoke($r1); $b.EndInvoke($r2); $c.EndInvoke($r3) # wait
($a,$b,$c).streams.error # check for errors
($a,$b,$c).dispose() # clean

a done
b done
c done

7

バックグラウンドジョブはセットアップにコストがかかり、再利用できません。PowerShell MVP Oisin Grehanは PowerShellマルチスレッドの良い例です。

(2010年10月25日のサイトはダウンしていますが、Webアーカイブからアクセスできます)。

私はここでデータ読み込みルーチンで使用するために適応されたOisinスクリプトを使用しました:

http://rsdd.codeplex.com/SourceControl/changeset/view/a6cd657ea2be#Invoke-RSDDThreaded.ps1


Link rotがこの回答に着手しました
Luke

4

以前の回答を完了するには、を使用Wait-Jobして、すべてのジョブが完了するのを待つこともできます。

For ($i=1; $i -le 3; $i++) {
    $ScriptBlock = {
        Param (
            [string] [Parameter(Mandatory=$true)] $increment
        )

        Write-Host $increment
    }

    Start-Job $ScriptBlock -ArgumentList $i
}

Get-Job | Wait-Job | Receive-Job

0

Powershell 7では、ForEach-Object -Parallelを使用できます

$Message = "Output:"
Get-ChildItem $dir | ForEach-Object -Parallel {
    "$using:Message $_"
} -ThrottleLimit 4

0

最新のクロスプラットフォームPowershell(使用する必要があります)https://github.com/powershell/powershell#get-powershellを使用している場合は、シングル&を追加して並列スクリプトを実行できます。(使用する;順次実行に)

私の場合、2つのnpmスクリプトを並行して実行する必要がありました。 npm run hotReload & npm run dev


powershellスクリプトに使用するようにnpmを設定することもできます(デフォルトではcmdWindows で使用されます)。

プロジェクトのルートフォルダーから実行し、npm config set script-shell pwsh --userconfig ./.npmrc 単一のnpmスクリプトコマンドを使用します。npm run start

"start":"npm run hotReload & npm run dev"
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.