34

これをマルチスレッド、ジョブベース、非同期のいずれの必要性と呼ぶかはわかりませんが、基本的には、いくつかのパラメーターを受け取るPowershellスクリプト関数があり、さまざまなパラメーターを使用して数回呼び出し、これらを実行する必要があります並行して。

現在、私は次のような関数を呼び出しています。

Execute "param1" "param2" "param3" "param4"

各呼び出しが呼び出し元に戻るのを実行するのを待たずに、これを複数回呼び出すにはどうすればよいですか?

現在v2.0を実行していますが、必要に応じて更新できます

編集:これは私がこれまでに持っているものですが、機能しません:

$cmd = {
    param($vmxFilePath,$machineName,$username,$password,$scriptTpath,$scriptFile,$uacDismissScript,$snapshotName)
    Execute $vmxFilePath $machineName $username $password $scriptTpath $scriptFile $uacDismissScript $snapshotName
}

Start-Job -ScriptBlock $cmd -ArgumentList $vmxFilePath, $machineName, $username $password, $scriptTpath, $scriptFile, $uacDismissScript, $snapshotName

エラーが発生します:

'system.object[]'をパラメーター'initializationscript'で必要なタイプ'system.management.automation.scriptblock'に変換できません。指定されたメソッドはサポートされていません

EDIT2:スクリプトを変更しましたが、それでも上記のエラーが発生します。これが私のmodです:

$cmd = {
    param($vmxFilePath,$machineName,$username,$password,$scriptTpath,$scriptFile,$uacDismissScript,$snapshotName)
    Execute $vmxFilePath $machineName $username $password $scriptTpath $scriptFile $uacDismissScript $snapshotName
}

Start-Job -ScriptBlock $cmd -ArgumentList $vmxFilePath, $machineName, $username $password, $scriptTpath, $scriptFile, $uacDismissScript, $snapshotName
4

5 に答える 5

55

これには更新は必要ありません。スクリプトブロックを定義し、を使用Start-Jobしてスクリプトブロックを必要な回数だけ実行します。例:

$cmd = {
  param($a, $b)
  Write-Host $a $b
}

$foo = "foo"

1..5 | ForEach-Object {
  Start-Job -ScriptBlock $cmd -ArgumentList $_, $foo
}

スクリプトブロックは2つのパラメーター$aを取り、それらはオプション$bによって渡され-ArgumentListます。上記の例では、割り当ては$_$aおよび$foo$bです。$fooこれは、構成可能であるが静的なパラメーターの単なる例です。

Get-Job | Remove-Jobある時点で実行して、終了したジョブをキューから削除します(またはGet-Job | % { Receive-Job $_.Id; Remove-Job $_.Id }、出力を取得する場合)。

于 2012-10-07T11:32:16.127 に答える
29

テストを目的とした簡単な偽のスクリプトブロックを次に示します。

$Code = {
    param ($init)
    $start = Get-Date
    (1..30) | % { Start-Sleep -Seconds 1; $init +=1 }
    $stop = Get-Date
    Write-Output "Counted from $($init - 30) until $init in $($stop - $start)."
}

Start-Jobこのスクリプトブロックは、たとえば3つのパラメータ(10、15、35)を使用してに渡すことができます。

$jobs = @()
(10,15,35) | % { $jobs += Start-Job -ArgumentList $_ -ScriptBlock $Code }

Wait-Job -Job $jobs | Out-Null
Receive-Job -Job $jobs

これにより、3つのジョブが作成され、それらが$jobs変数に割り当てられ、それらが並行して実行され、これら3つのジョブが終了するのを待って、結果が取得されます。

Counted from 10 until 40 in 00:00:30.0147167.
Counted from 15 until 45 in 00:00:30.0057163.
Counted from 35 until 65 in 00:00:30.0067163.

これは実行に90秒もかからず、30秒しかかかりませんでした。

トリッキーな部分の1つは、に提供-Argumentlistし、 ScriptBlock内にブロックStart-Jobを含めることです。param()そうしないと、スクリプトブロックに値が表示されません。

于 2012-10-07T11:35:56.807 に答える
5

関数が長時間実行されていない場合は、ジョブを呼び出すよりも高速な代替手段を使用できます。最大スレッド数は25で、この関数を呼び出すのは10回だけなので、合計実行時間は5秒になると思います。'results ='ステートメントの周りにMeasure-Commandをラップして、統計を表示できます。

例:

$ScriptBlock = {
    Param ( [int]$RunNumber )
    Start-Sleep -Seconds 5   
    Return $RunNumber
}
$runNumbers = @(1..10)
$MaxThreads = 25
$runspacePool = [RunspaceFactory ]::CreateRunspacePool(1, $MaxThreads)
$runspacePool.Open()
$pipeLines = foreach($num in $runNumbers){
    $pipeline = [powershell]::Create()
    $pipeline.RunspacePool = $runspacePool
    $pipeline.AddScript($ScriptBlock)    | Out-Null   
    $pipeline.AddArgument($num)  | Out-Null
    $pipeline | Add-Member -MemberType NoteProperty -Name 'AsyncResult' -Value $pipeline.BeginInvoke() -PassThru 
}
#obtain results as they come.
$results =  foreach($pipeline in $pipeLines){
    $pipeline.EndInvoke($pipeline.AsyncResult )
}
#cleanup code.
$pipeLines | % { $_.Dispose()}
$pipeLines = $null
if ( $runspacePool ) { $runspacePool.Close()}
#your results
$results
于 2016-05-25T16:00:18.007 に答える
4

皆さんがあなたの問題を見逃してしまったことをお詫びします-私はそれが今では遅すぎることを知っています、しかし...

このエラーは、リストの$usernameと$passwordの間にコンマがないために発生します。

以前の回答をモデルにしたこのスニペットでテストできます。

$cmd = {
  param($a, $b, $c, $d)
}
$foo = "foo"
$bar = "bar"
start-job -scriptblock $cmd -ArgumentList "a", $foo, $bar, "gold" #added missing comma for this to work
于 2014-10-21T00:38:03.943 に答える
0

私はあなたのためにこれを行うために非常に用途の広い関数を作りました、そして他の答えとは異なり、あなたはそれを機能させるためにあなたのコードを再調整する必要はありません。
関数をパラメーターとして渡してAsync入力をパイプするだけで、パイプライン上の各アイテムはscriptblock非同期で並列に実行され、各アイテムが完了するとそれらを発行します。

特にあなたの質問のためにそれはこのように見えるでしょう

@(
    @{vmxFilePath='a';machineName='b';username='c';password='d';scriptTpath='e';scriptFile='f';uacDismissScript='g';snapshotName'h'},
    @{vmxFilePath='i';machineName='j';username='k';password='l';scriptTpath='m';scriptFile='n';uacDismissScript='o';snapshotName'p'}
    ...
) `
| Async `
    -Func { Process {
        Execute $_.vmxFilePath $_.machineName $_.username $_.password $_.scriptTpath $_.scriptFile $_.uacDismissScript $_.snapshotName
    } }

これに加えて、私の関数は[powershell](@binarySaltの回答)の自動構築だけでなく、 Jobs(@Joostで使用されますが、実行スペースよりもはるかに遅いため、これらを使用しないでください)およびTasks(すでに他の人のコードを使用している場合)もサポートしますそれらをスポーンします(-AsJobこの回答の下部で説明しているフラグを使用してください)。


したがって、これはこの質問への新規訪問者には役立ちません。マシン上で実行して実際の結果を確認できる、より実証可能な何かを実行しましょう。
この単純なコードを例にとると、Webサイトのテストデータを取得し、それらが稼働しているかどうかを確認します。

$in=TestData | ?{ $_.proto -eq 'tcp' }
$in `
| %{
    $WarningPreference='SilentlyContinue'
    $_ `
    | Add-Member `
        -PassThru `
        -MemberType NoteProperty `
        -Name result `
        -Value $(Test-NetConnection `
            -ComputerName $_.address `
            -Port $_.port `
            -InformationLevel Quiet
        )
} `
| Timer -name 'normal' `
| Format-Table

ここに画像の説明を入力してください

これがテストデータです。同じWebサイトのほんの一部が繰り返されています。
また、パフォーマンスを確認するためのタイミング機能もあります。

Function TestData {
    1..20 | %{
        [PsCustomObject]@{proto='tcp'  ; address='www.w3.org'            ; port=443},
        [PsCustomObject]@{proto='https'; address='www.w3.org'            ; port=443},
        [PsCustomObject]@{proto='icmp' ; address='www.w3.org'            ;         },
        [PsCustomObject]@{proto='tcp'  ; address='developer.mozilla.org' ; port=443},
        [PsCustomObject]@{proto='https'; address='developer.mozilla.org' ; port=443},
        [PsCustomObject]@{proto='icmp' ; address='developer.mozilla.org' ;         },
        [PsCustomObject]@{proto='tcp'  ; address='help.dottoro.com'      ; port=80 },
        [PsCustomObject]@{proto='http' ; address='help.dottoro.com'      ; port=80 },
        [PsCustomObject]@{proto='icmp' ; address='help.dottoro.com'      ;         }
    }
}

Function Timer {
    Param ($name)
    Begin {
        $timer=[system.diagnostics.stopwatch]::StartNew()
    }
    Process { $_ }
    End {
        @(
            $name,
            ' '
            [math]::Floor($timer.Elapsed.TotalMinutes),
            ':',
            ($timer.Elapsed.Seconds -replace '^(.)$','0$1')
        ) -join '' | Out-Host
    }
}

さて、15秒です。使用すると、これはどれだけ速くなりますAsyncか?
そして、それを機能させるためにどれだけ変更する必要がありますか?

$in=TestData | ?{ $_.proto -eq 'tcp' }
$in `
| Async `
    -Expected $in.Count `
    -Func { Process {
        $WarningPreference='SilentlyContinue'
        $_ `
        | Add-Member `
            -PassThru `
            -MemberType NoteProperty `
            -Name result `
            -Value $(Test-NetConnection `
                -ComputerName $_.address `
                -Port $_.port `
                -InformationLevel Quiet
            )
    } } `
| Timer -name 'async' `
| Format-Table

それは基本的に同じように見えます..
さて、速度は何ですか?

ここに画像の説明を入力してください

うわー、3分の2にカット!
それだけでなく、パイプラインにあるアイテムの数がわかっているので、プログレスバーとETAを提供するためにいくつかのスマートで書きました

ここに画像の説明を入力してください

私を信じないの?ビデオを持っている
か、自分でコードを実行してください:)

#Requires -Version 5.1

#asynchronously run a pool of tasks,
#and aggregate the results back into a synchronous output
#without waiting to pool all input before seeing the first result
Function Async { Param(
    #maximum permitted simultaneous background tasks
    [int]$BatchSize=[int]$env:NUMBER_OF_PROCESSORS * 3,
    #the task that accepts input on a pipe to execute in the background
    [scriptblock]$Func,
    #because your task is in a subshell you wont have access to your outer scope,
    #you may pass them in here
    [array]$ArgumentList=@(),
    [System.Collections.IDictionary]$Parameters=@{},
    #the title of the progress bar
    [string]$Name='Processing',
    #your -Func may return a [Job] instead of being backgrounded itself,
    #if so it must return @(job;input;args)
    #optionally job may be a [scriptblock] to be backgrounded, or a [Task]
    [switch]$AsJob,
    #if you know the number of tasks ahead of time,
    #providing it here will have the progress bar show an ETA
    [int]$Expected,
    #outputs of this stream will be @(job;input) where job is the result
    [switch]$PassThru,
    #the time it takes to give up on one job type if there are others waiting
    [int]$Retry=5
)   
    Begin {
        $ArgumentList=[Array]::AsReadOnly($ArgumentList)
        $Parameters=$Parameters.GetEnumerator() `
        | &{
            Begin { $params=[ordered]@{} }
            Process { $params.Add($_.Key, $_.Value) }
            End { $params.AsReadOnly() }
        }
        #the currently running background tasks
        $running=@{}
        $counts=[PSCustomObject]@{
            completed=0;
            jobs=0;
            tasks=0;
            results=0;
        }
        #a lazy attempt at uniquely IDing this instance for Write-Progress
        $asyncId=Get-Random
        #a timer for Write-Progress
        $timer=[system.diagnostics.stopwatch]::StartNew()

        $pool=[RunspaceFactory]::CreateRunspacePool(1, $BatchSize)
        $pool.Open()

        #called whenever we want to update the progress bar
        Function Progress { Param($Reason)
            #calculate ETA if applicable
            $eta=-1
            $total=[math]::Max(1, $counts.completed + $running.Count)
            if ($Expected) {
                $total=[math]::Max($total, $Expected)
                if ($counts.completed) {
                    $eta=`
                        ($total - $counts.completed) * `
                        $timer.Elapsed.TotalSeconds / `
                        $counts.completed
                }
            }

            $Reason=Switch -regex ($Reason) {
                '^done$'   { "Finishing up the final $($running.Count) jobs." }
                '^(do|next)$' { "
                    Running
                     $($running.Count)
                     jobs concurrently.
                     $(@('Adding','Waiting to add')[!($Reason -eq 'do')])
                     job #
                    $($counts.completed + $running.Count + 1)
                " -replace '\r?\n\t*','' }
                Default { "
                    Running $($running.Count) jobs concurrently.
                     Emitting
                     $($counts.completed)
                    $(@{1='st';2='nd';3='rd'}[$counts.completed % 10] -replace '^$','th')
                     result.
                " -replace '\r?\n\t*','' }
            }

            Write-Progress `
                -Id $asyncId `
                -Activity $Name `
                -SecondsRemaining $eta `
                -Status ("
                    $($counts.completed)
                     jobs completed in
                     $([math]::Floor($timer.Elapsed.TotalMinutes))
                    :
                    $($timer.Elapsed.Seconds -replace '^(.)$','0$1')
                " -replace '\r?\n\t*','') `
                -CurrentOperation $Reason `
                -PercentComplete (100 * $counts.completed / $total)
        }

        #called with the [Job]'s that have completed 
        Filter Done {
            ++$counts.completed
            $out=$running.Item($_.Id)
            $running.Remove($_.Id)

            Progress

            $out.job=`
            if ($_ -is [System.Management.Automation.Job]) {
                --$counts.jobs
                $_ | Receive-Job
            }
            elseif ($_.pwsh) {
                --$counts.results
                try {
                    $_.pwsh.EndInvoke($_)
                }
                catch {
                    #[System.Management.Automation.MethodInvocationException]
                    $_.Exception.InnerException
                }
                finally {
                    $_.pwsh.Dispose()
                }
            }
            elseif ($_.IsFaulted) {
                --$counts.tasks
                #[System.AggregateException]
                $_.Exception.InnerException
            }
            else {
                --$counts.tasks
                $_.Result
            }

            if ($PassThru) {
                $out
            }
            else {
                $out.job
            }
        }

        $isJob={
            $_ -is [System.Management.Automation.Job]
        }
        $isTask={
            $_ -is [System.Threading.Tasks.Task]
        }
        $isResult={
            $_ -is [IAsyncResult]
        }
        $isFinished={
            $_.IsCompleted -or `
            (
                $_.JobStateInfo.State -gt 1 -and
                $_.JobStateInfo.State -ne 6 -and
                $_.JobStateInfo.State -ne 8
            )
        }
        $handle={
            $_.AsyncWaitHandle
        }

        Function Jobs { Param($Filter)
            $running.Values | %{ $_.job } | ? $Filter
        }

        #called whenever we need to wait for at least one task to completed
        #outputs the completed tasks
        Function Wait { Param([switch]$Finishing)
            #if we are at the max background tasks this instant
            while ($running.Count -ge $BatchSize) {
                Progress -Reason @('done','next')[!$Finishing]

                $value=@('jobs', 'tasks', 'results') `
                | %{ $counts.($_) } `
                | measure -Maximum -Sum
                $wait=if ($value.Maximum -lt $value.Sum) {
                    $Retry
                }
                else {
                    -1
                }

                $value=Switch -exact ($value.Maximum) {
                    $counts.jobs {
                        (Wait-Job `
                            -Any `
                            -Job (Jobs -Filter $isJob) `
                            -Timeout $wait
                        ).Count -lt 1
                        break
                    }
                    Default {
                        [System.Threading.WaitHandle]::WaitAny(
                            (Jobs -Filter $handle | % $handle),
                            [math]::Max($wait * 1000, -1)
                        ) -eq [System.Threading.WaitHandle]::WaitTimeout
                        break
                    }
                }

                (Jobs -Filter $isFinished) | Done
            }
        }
    }

    #accepts inputs to spawn a new background task with
    Process {
        Wait
        Progress -Reason 'do'

        $run=[PSCustomObject]@{
            input=$_;
            job=$Func;
            args=$ArgumentList;
            params=$Parameters;
        }
        if ($AsJob) {
            $run.job=$NULL
            Invoke-Command `
                -ScriptBlock $Func `
                -ArgumentList @($run) `
            | Out-Null
        }

        if ($run.job | % $isJob) {
            ++$counts.jobs
        }
        elseif ($run.job | % $isTask) {
            ++$counts.tasks
        }
        #if we weren't given a [Job] we need to spawn it for them
        elseif ($run.job -is [ScriptBlock]) {
            $pwsh=[powershell]::Create().AddScript($run.job)
            $run.args | %{ $pwsh.AddArgument($_) } | Out-Null
            $pwsh.RunspacePool=$pool
            $run.job=$pwsh.AddParameters($run.params).BeginInvoke(
                [System.Management.Automation.PSDataCollection[PSObject]]::new(
                    [PSObject[]]($run.input)
                )
            )
            $run.job | Add-Member `
                -MemberType NoteProperty `
                -Name pwsh `
                -Value $pwsh `
                -PassThru `
            | Add-Member `
                -MemberType NoteProperty `
                -Name Id `
                -Value $run.job.AsyncWaitHandle.Handle.ToString()
            ++$counts.results
        }
        else {
            throw "$($run.job.GetType()) needs to be a ScriptBlock"
        }

        $running.Add($run.job.Id, $run) | Out-Null
    }

    End {
        #wait for the remaining running processes
        $BatchSize=1
        Wait -Finishing
        Write-Progress -Id $asyncId -Activity $Name -Completed
        $pool.Close()
        $pool.Dispose()
    }
}

したがって、上記の3つのことに気付いたかもしれませんが、私がほのめかしたのは( s、s 、sの-AsJob組み合わせを使用)、テストデータに未使用のプロトコルが記載されており、ビデオには3番目のテストが含まれていることです。JobTaskscriptblock

ここにあります。基本的なtcpテストを実行する代わりに、テストデータを使用してhttp /sチェックとicmppingも実行します(idk、httpsは失敗する可能性がありますが、マシンがダウンしているため、またはサービスのみが原因である場合は、絞り込みます。 )。

  • Test-Connection -AsJobを返し、Jobpingをチェックするコマンドレットです。
  • WebRequest.GetResponseAsync()これはWebリソースに接続し、Task
  • そして最後Test-NetConnectionに、以前と同じように、同期スクリプトブロックとしてプログラムしたもので実行されました。これは非同期で実行されます。
$in=TestData
$in `
| Async `
    -Expected $in.Count `
    -PassThru `
    -AsJob `
    <#this would be accessible as a named parameter if needed#>`
    -Parameters @{proxy=[System.Net.WebRequest]::GetSystemWebProxy()} `
    -Func { Param([parameter(Position=0)]$x)
        $x.job=Switch -regex ($x.input.proto) {
            '^icmp$' {
                Test-Connection `
                    -ComputerName $x.input.address `
                    -Count 1 `
                    -ThrottleLimit 1 `
                    -AsJob
            }

            '^tcp$' {
                $x.params=@{address=$x.input.address; port=$x.input.port}
                { Param($address, $port)
                    $WarningPreference='SilentlyContinue'
                    Test-NetConnection `
                        -ComputerName $address `
                        -Port $port `
                        -InformationLevel Quiet
                }
            }

            '^(http|https)$' {
                [Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12
                $request=[System.Net.HttpWebRequest]::Create((@(
                    $x.input.proto,
                    '://',
                    $x.input.address,
                    ':',
                    $x.input.port
                ) -join ''))
                $request.Proxy=$NULL
                $request.Method='Get'
                $request.GetResponseAsync()
            }
        }
    } `
| %{
    $result=$_
    $result.input `
    | Add-Member `
        -PassThru `
        -MemberType NoteProperty `
        -Name result `
        -Value $(Switch -regex (@($result.input.proto, $result.job.message)[$result.job -is [Exception]]) {
            #[Win32_PingStatus]
            '^icmp$' { $result.job.StatusCode -eq 0 }

            #[bool]
            '^tcp$' { $result.job }

            #[System.Net.HttpWebResponse]
            '^(http|https)$' {
                $result.job.Close()
                Switch ($result.job.StatusCode.value__) {
                    { $_ -ge 200 -and $_ -lt 400 } { $True }
                    Default {$False}
                }
            }

            #[Exception]
            Default { $False }
        })
} `
| Timer -name 'async asjob' `
| Format-Table

ご覧のとおり、これは元のコードの2倍以上の作業を行いましたが、それでも8秒で約半分の時間で終了しました。

ここに画像の説明を入力してください

于 2020-03-30T14:14:22.730 に答える