ジョブを使用せずにPowerShellスクリプトを並行して実行するにはどうすればよいですか?


29

複数のコンピューターに対して、または複数の異なる引数を使用して実行する必要があるスクリプトがある場合、新しいPSJobをStart-Job生成するオーバーヘッドを発生させることなく、どのように並行して実行できますか?

例として、次のように、すべてのドメインメンバーの時刻を再同期します

$computers = Get-ADComputer -filter * |Select-Object -ExpandProperty dnsHostName
$creds = Get-Credential domain\user
foreach($computer in $computers)
{
    $session = New-PSSession -ComputerName $computer -Credential $creds
    Invoke-Command -Session $session -ScriptBlock { w32tm /resync /nowait /rediscover }
}

ただし、各PSSessionが接続してコマンドを呼び出すのを待ちたくありません。ジョブなしでこれをどのように並行して行うことができますか?

回答:


51

更新 -この回答では、PowerShellランスペースのプロセスとメカニズム、およびそれらがマルチスレッドの非シーケンシャルワークロードに役立つ方法について説明していますが、PowerShellの愛好家であるWarren 'Cookie Monster' F氏は、同じ概念を1つのツールに統合しました呼び出されます -以下に説明することを行い、それから彼はログ用のオプションのスイッチとインポートされたモジュールを含む準備されたセッション状態でそれを拡張しました、本当にクールなもの- あなた自身の光沢のあるソリューションを構築する前にそれチェックすることを強くお勧めしますInvoke-Parallel


並列実行スペースの実行:

逃げられない待ち時間の削減

元の特定のケースでは、呼び出された実行可能ファイルには/nowait、ジョブ(この場合は時刻の再同期)が単独で終了している間、呼び出しスレッドのブロックを防ぐオプションがあります。

これにより、発行者の観点からは全体の実行時間が大幅に短縮されますが、各マシンへの接続は引き続き順番に行われます。タイムアウト待機の累積により、数千のクライアントに順番に接続すると、何らかの理由でアクセスできないマシンの数に応じて長い時間がかかる場合があります。

単一またはいくつかの連続したタイムアウトが発生した場合に後続のすべての接続をキューに入れる必要を回避するために、コマンドを接続して起動するジョブを個別のPowerShell実行スペースにディスパッチし、並行して実行できます。

ランスペースとは何ですか?

A の実行空間は、仮想コンテナであなたのPowerShellのコードが実行され、そして表し/ PowerShellの声明/コマンドの視点から環境を保持しています。

大まかに言うと、1 Runspace = 1実行スレッドなので、PowerShellスクリプトを「マルチスレッド化」するのに必要なのは、並列に実行できるRunspaceのコレクションだけです。

元の問題と同様に、複数の実行スペースをコマンドを呼び出すジョブは、次のように分類できます。

  1. RunspacePoolの作成
  2. PowerShellスクリプトまたは同等の実行可能コードをRunspacePoolに割り当てる
  3. 非同期でコードを呼び出します(つまり、コードが戻るのを待つ必要はありません)

RunspacePoolテンプレート

PowerShellには、[RunspaceFactory]Runspaceコンポーネントの作成を支援するというタイプアクセラレータがあります。それを機能させましょう

1. RunspacePoolとOpen()それを作成します:

$RunspacePool = [runspacefactory]::CreateRunspacePool(1,8)
$RunspacePool.Open()

2つの引数はに渡されCreateRunspacePool()1そして8最小と私たち有効与え、任意の時点で実行を許可実行空間の最大数である最大並列度 8を。

2. PowerShellのインスタンスを作成し、実行可能コードを添付してRunspacePoolに割り当てます。

PowerShellのインスタンスは、powershell.exeプロセス(実際にはホストアプリケーション)と同じではなく、実行するPowerShellコードを表す内部ランタイムオブジェクトです。我々は使用することができます[powershell]PowerShellの中に新しいPowerShellのインスタンスを作成するために、型アクセラレータを:

$Code = {
    param($Credentials,$ComputerName)
    $session = New-PSSession -ComputerName $ComputerName -Credential $Credentials
    Invoke-Command -Session $session -ScriptBlock {w32tm /resync /nowait /rediscover}
}
$PSinstance = [powershell]::Create().AddScript($Code).AddArgument($creds).AddArgument("computer1.domain.tld")
$PSinstance.RunspacePool = $RunspacePool

3. APMを使用してPowerShellインスタンスを非同期的に呼び出します。

.NET開発用語で非同期プログラミングモデルとして知られているものを使用して、コマンドの呼び出しをBeginメソッドに分割し、コードを実行するための「青信号」とEnd結果を収集するためのメソッドに分けることができます。この場合、フィードバックにはまったく関心がないため(w32tmとにかく出力を待つことはありません)、最初のメソッドを呼び出すだけで期限を設定できます

$PSinstance.BeginInvoke()

RunspacePoolにまとめる

上記の手法を使用して、新しい接続を作成し、並列実行フローでリモートコマンドを呼び出すという順次反復をラップできます。

$ComputerNames = Get-ADComputer -filter * -Properties dnsHostName |select -Expand dnsHostName

$Code = {
    param($Credentials,$ComputerName)
    $session = New-PSSession -ComputerName $ComputerName -Credential $Credentials
    Invoke-Command -Session $session -ScriptBlock {w32tm /resync /nowait /rediscover}
}

$creds = Get-Credential domain\user

$rsPool = [runspacefactory]::CreateRunspacePool(1,8)
$rsPool.Open()

foreach($ComputerName in $ComputerNames)
{
    $PSinstance = [powershell]::Create().AddScript($Code).AddArgument($creds).AddArgument($ComputerName)
    $PSinstance.RunspacePool = $rsPool
    $PSinstance.BeginInvoke()
}

CPUに8つの実行スペースすべてを一度に実行する能力があると仮定すると、実行時間が大幅に短縮されることがわかりますが、使用される「高度な」メソッドによりスクリプトが読みやすくなります。


最適な視差の度合いを判断する:

100個のランスペースを同時に実行できるRunspacePoolを簡単に作成できます。

[runspacefactory]::CreateRunspacePool(1,100)

しかし、結局のところ、すべては、ローカルCPUが処理できる実行単位の数に帰着します。つまり、コードが実行されている限り、論理プロセッサがコードの実行をディスパッチするよりも多くの実行スペースを許可しても意味がありません。

WMIのおかげで、このしきい値は簡単に決定できます。

$NumberOfLogicalProcessor = (Get-WmiObject Win32_Processor).NumberOfLogicalProcessors
[runspacefactory]::CreateRunspacePool(1,$NumberOfLogicalProcessors)

一方、実行中のコード自体がネットワーク遅延などの外部要因により多くの待機時間を要する場合、論理プロセッサよりも多くの同時実行スペースを実行することでメリットが得られるため、おそらくテストする必要があります。範囲の可能な最大の発見する実行空間損益分岐

foreach($n in ($NumberOfLogicalProcessors..($NumberOfLogicalProcessors*3)))
{
    Write-Host "$n: " -NoNewLine
    (Measure-Command {
        $Computers = Get-ADComputer -filter * -Properties dnsHostName |select -Expand dnsHostName -First 100
        ...
        [runspacefactory]::CreateRunspacePool(1,$n)
        ...
    }).TotalSeconds
}

4
リモートコンピューターでPowerShellコマンドを実行している場合など、ジョブがネットワークで待機している場合、CPUのボトルネックに達する前に、論理プロセッサーの数を簡単に超えることができます。
マイケルハンプトン

まあ、それは本当です。それを少し変更し、テストの例を提供しました
Mathias R. Jessen 14

すべてのジョブが最後に完了したことを確認する方法は?(すべてのスクリプトブロックが終了した後に何かが必要になる場合があります)
sjzls 14年

@NickWすばらしい質問です。私は仕事と「収穫」を追跡する上でのフォローアップをやる電位出力後の今日、お楽しみに
マティアスR.ジェッセン

1
@ MathiasR.Jessen非常によく書かれた答え!更新を楽しみにしています。
信号15 14

5

この説明に加えて、不足しているのは、ランスペースから作成されたデータを保存するコレクターと、ランスペースのステータスをチェックする変数です。つまり、完了したかどうかです。

#Add an collector object that will store the data
$Object = New-Object 'System.Management.Automation.PSDataCollection[psobject]'

#Create a variable to check the status
$Handle = $PSinstance.BeginInvoke($Object,$Object)

#So if you want to check the status simply type:
$Handle

#If you want to see the data collected, type:
$Object

3

PoshRSJobをご覧ください。ネイティブの* -Job関数と同じ/類似の機能を提供しますが、標準のPowershellジョブよりもはるかに高速で応答性の高いRunspaceを使用します。


1

@ mathias-r-jessenにはすばらしい答えがありますが、追加したい詳細があります。

最大スレッド

理論的には、スレッドはシステムプロセッサの数によって制限される必要があります。ただし、AsyncTcpScanのテストに、より大きな値を選択することにより、はるかに優れたパフォーマンスを達成しましたMaxThreads。したがって、そのモジュールに-MaxThreads入力パラメーターがある理由。スレッドの割り当てが多すぎるとパフォーマンスが低下することに注意してください。

データを返す

データを取得するのScriptBlockは難しいです。OPコードを更新し、AsyncTcpScanに使用されていたコードに統合しました。

警告:次のコードをテストできませんでした。Active Directoryコマンドレットでの作業経験に基づいて、OPスクリプトにいくつかの変更を加えました。

# Script to run in each thread.
[System.Management.Automation.ScriptBlock]$ScriptBlock = {

    $result = New-Object PSObject -Property @{ 'Computer' = $args[0];
                                               'Success'  = $false; }

    try {
            $session = New-PSSession -ComputerName $args[0] -Credential $args[1]
            Invoke-Command -Session $session -ScriptBlock { w32tm /resync /nowait /rediscover }
            Disconnect-PSSession -Session $session
            $result.Success = $true
    } catch {

    }

    return $result

} # End Scriptblock

function Invoke-AsyncJob
{
    [CmdletBinding()]
    param(
        [parameter(Mandatory=$true)]
        [System.Management.Automation.PSCredential]
        # Credential object to login to remote systems
        $Credentials
    )

    Import-Module ActiveDirectory

    $Results = @()

    $AllJobs = New-Object System.Collections.ArrayList

    $AllDomainComputers = Get-ADComputer -Filter * -Properties dnsHostName

    $HostRunspacePool = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool(2,10,$Host)

    $HostRunspacePool.Open()

    foreach($DomainComputer in $AllDomainComputers)
    {
        $asyncJob = [System.Management.Automation.PowerShell]::Create().AddScript($ScriptBlock).AddParameters($($($DomainComputer.dnsName),$Credentials))

        $asyncJob.RunspacePool = $HostRunspacePool

        $asyncJobObj = @{ JobHandle   = $asyncJob;
                          AsyncHandle = $asyncJob.BeginInvoke()    }

        $AllJobs.Add($asyncJobObj) | Out-Null
    }

    $ProcessingJobs = $true

    Do {

        $CompletedJobs = $AllJobs | Where-Object { $_.AsyncHandle.IsCompleted }

        if($null -ne $CompletedJobs)
        {
            foreach($job in $CompletedJobs)
            {
                $result = $job.JobHandle.EndInvoke($job.AsyncHandle)

                if($null -ne $result)
                {
                    $Results += $result
                }

                $job.JobHandle.Dispose()

                $AllJobs.Remove($job)
            } 

        } else {

            if($AllJobs.Count -eq 0)
            {
                $ProcessingJobs = $false

            } else {

                Start-Sleep -Milliseconds 500
            }
        }

    } While ($ProcessingJobs)

    $HostRunspacePool.Close()
    $HostRunspacePool.Dispose()

    return $Results

} # End function Invoke-AsyncJob
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.