PowerShell 기능을 병렬로 여러 번 실행하는 방법은 무엇입니까?
이것을 멀티스레딩, 작업 기반 또는 비동기 중 어느 것이 필요한지는 잘 모르겠지만 기본적으로 여러 매개 변수를 사용하는 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[]'를 'system.management' 유형으로 변환할 수 없습니다.매개 변수 'initializationscript'에 필요한 automation.scriptblock'. 지정된 메서드는 지원되지 않습니다.
EDIT2: 스크립트를 수정했지만 위에 언급된 오류가 여전히 발생합니다.내 모드는 다음과 같습니다.
$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
업데이트가 필요하지 않습니다.하고 사용합니다.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
된 작업을 대에서완또작제료거업할수다있니습을된는기열또▁the(▁at▁from다▁remove수있니습or▁(는▁to▁the▁queue▁point▁some할).Get-Job | % { Receive-Job $_.Id; Remove-Job $_.Id }
출력을 검색하려는 경우).
테스트를 위한 빠른 가짜 스크립트 블록은 다음과 같습니다.
$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개의 를 하여 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
이 세합니다.
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초밖에 걸리지 않았습니다.
까다로운 부분 중 하나는 제공하는 것입니다.-Argumentlist
Start-Job
를포니다합을 합니다.param()
스크립트 블록 내부의 블록입니다.그렇지 않으면 스크립트 블록에 값이 표시되지 않습니다.
장시간 실행 중인 작업이 아닌 경우 작업을 호출하는 것보다 빠른 대체 작업을 사용할 수 있습니다.최대 스레드는 25이고 이 기능을 10번만 호출하기 때문에 총 런타임은 5초가 될 것으로 예상됩니다.Measure-Command를 'message=' 문 주위에 감아서 통계를 볼 수 있습니다.
예:
$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
모두가 당신의 이슈를 놓쳐서 유감입니다. 지금은 너무 늦었지만...
이 오류는 목록에 $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
이를 위해 매우 다양한 기능을 만들었습니다. 다른 답변과 달리 코드를 다시 조정할 필요가 없습니다.
을 매개변로함전니다합달수를에 .Async
각한 내용을하면 파프라인실행됩니다항각이목의이▁your다▁run▁and▁on니실됩행▁in를 실행합니다.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
} }
또한 내 함수는 (@binarySalt's 응답)의 자동 구성뿐만 아니라 (@Joost에서 사용되지만 런스페이스보다 훨씬 느리기 때문에 이들을 사용하지 마십시오.) 그리고 이미 생성된 다른 사용자의 코드를 사용하는 경우:-AsJob
플래그, 이 답변의 하단에서 설명합니다).
따라서 이 질문을 처음 접하는 사람들에게는 유용하지 않습니다. 컴퓨터에서 실행하여 실제 결과를 확인할 수 있는 좀 더 입증 가능한 것을 수행해 보겠습니다.
이 간단한 코드를 예로 들면, 웹 사이트에 대한 테스트 데이터를 가져와서 웹 사이트가 작동하는지 확인합니다.
$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
여기 테스트 데이터가 있습니다. 같은 웹사이트 몇 개만 반복해서 볼 수 있습니다.
그리고 그것이 얼마나 성능이 좋은지를 확인하는 타이밍 기능도 있습니다.
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분의! 으, 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()
}
}
그래서 위의 세 가지를 알아차렸을 수도 있습니다, 저는 암시했습니다.-AsJob
((으)의 혼합 Job
s,Task
모래땅scriptblock
된 미사용 , 에는 세 되어 있습니다 , 테트데이터언있비프는세있에다번테었니습가스트째오디으며스이콜로토에사용미급된.
여기 있어요.기본 tcp 테스트를 수행하는 대신 테스트 데이터를 사용하여 http/s 검사 및 icmpping도 수행합니다(idk, https가 실패할 수도 있지만, 기계가 다운되었거나 서비스만 중단된 경우 범위를 좁히고자 합니다).
Test-Connection -AsJob
다음을 반환하는 cmdlet입니다.Job
그리고 ping을 확인합니다.WebRequest.GetResponseAsync()
웹 리소스에 연결, 반환Task
- 그리고 마지막으로, 이전과 동일하게, 우리가 동기식 스크립트 블록으로 프로그래밍한 것을 실행했습니다. 이것은 우리를 위해 비동기식으로 실행될 것입니다.
$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
보신 것처럼, 이것은 원래 코드의 두 배 이상의 작업을 수행했지만 여전히 8초로 절반 정도의 시간으로 완료되었습니다.
언급URL : https://stackoverflow.com/questions/12766174/how-to-execute-a-powershell-function-several-times-in-parallel
'source' 카테고리의 다른 글
SQLSTATE[HY000]:일반 오류:테이블을 만들 수 없습니다. (0) | 2023.08.28 |
---|---|
쿼리는 총점에 따라 사용자의 순위를 잘못 설정합니다. (0) | 2023.08.28 |
Matplotlib 범례가 작동하지 않습니다. (0) | 2023.08.28 |
로그 파일이 아닌 변수로 출력을 캡처하는 방법은 무엇입니까? (0) | 2023.08.28 |
powershell로 문자열을 분할하여 첫 번째 및 마지막 요소 가져오기 (0) | 2023.08.28 |