O PowerShell pode executar comandos em paralelo?

125

Eu tenho um script do PowerShell para fazer um processamento em lote em várias imagens e gostaria de fazer um processamento paralelo. O Powershell parece ter algumas opções de processamento em segundo plano, como trabalho inicial, trabalho de espera, etc., mas o único bom recurso que encontrei para executar trabalhos paralelos foi escrever o texto de um script e executá-los ( PowerShell Multithreading )

Idealmente, eu gostaria de algo semelhante ao foreach paralelo no .net 4.

Algo bastante parecido com:

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

Talvez eu estivesse melhor apenas caindo para c # ...

Alan Jackson
fonte
tl; dr: receive-job (wait-job ($a = start-job { "heyo!" })); remove-job $a ou $a = start-job { "heyo!" }; wait-job $a; receive-job $a; remove-job $aObserve também que, se você ligar receive-jobantes do término do trabalho, poderá obter nada.
22717 Andrew Andrew
Também(get-job $a).jobstateinfo.state;
Andrew

Respostas:

99

Você pode executar tarefas paralelas no Powershell 2 usando tarefas em segundo plano . Confira Iniciar trabalho e outros cmdlets do trabalho.

# 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
Steve Townsend
fonte
3
Tentei essa sugestão várias vezes, mas parece que minhas variáveis ​​não estão sendo expandidas corretamente. Para usar o mesmo exemplo, quando esta linha é executada: Test-Path "\\$_\c$\Something"eu esperaria que ela se expandisse $_para o item atual. No entanto, não. Em vez disso, ele retorna um valor vazio. Isso parece acontecer apenas dentro dos blocos de script. Se eu escrever esse valor imediatamente após o primeiro comentário, ele parece funcionar corretamente.
Rjg
1
@likwid - soa como uma pergunta separada para o site #
Steve Townsend
Como posso visualizar a saída do trabalho que está sendo executado em segundo plano?
SimpleGuy
@SimpleGuy - veja aqui para obter informações sobre captura de saída - stackoverflow.com/questions/15605095/… - não parece que você pode vê-lo com segurança até que o trabalho em segundo plano seja concluído.
Steve Townsend
@SteveTownsend Thanks! Na verdade, visualizar a saída não é tão bom na tela. Vem com atraso, então não é útil para mim. Em vez disso, iniciei um processo no novo terminal (shell); agora, cada processo está sendo executado em um terminal diferente, o que dá uma visão do progresso muito melhor e muito mais limpa.
precisa saber é o seguinte
98

A resposta de Steve Townsend está correta na teoria, mas não na prática, como apontou @likwid. Meu código revisado leva em consideração a barreira do contexto do trabalho - nada ultrapassa essa barreira por padrão! A $_variável automática pode, portanto, ser usada no loop, mas não pode ser usada diretamente no bloco de scripts, pois está dentro de um contexto separado criado pelo trabalho.

Para passar variáveis ​​do contexto pai para o contexto filho, use o -ArgumentListparâmetro on Start-Jobpara enviá-lo e use paramdentro do bloco de scripts para recebê-lo.

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 *

(Geralmente, gosto de fornecer uma referência à documentação do PowerShell como evidência de suporte, mas, infelizmente, minha pesquisa foi infrutífera. Se você souber onde a separação de contexto está documentada, poste um comentário aqui para que eu saiba!)

Michael Sorens
fonte
Obrigado por esta resposta. Tentei usar sua solução, mas não consegui fazê-la funcionar totalmente. Você pode dar uma olhada na minha pergunta aqui: stackoverflow.com/questions/28509659/…
David diz Reinstate Monica
Como alternativa, é muito fácil chamar um arquivo de script separado. Basta usarStart-Job -FilePath script.ps1 -ArgumentList $_
Chad Zawistowski
Uma abordagem alternativa é fazer uma passagem preliminar da geração de scripts, onde nada está sendo feito, exceto a expansão variável e, em seguida, chamar os scripts gerados em paralelo. Eu tenho uma pequena ferramenta que pode ser adaptada à geração de scripts, embora nunca tenha sido criada para oferecer suporte à geração de scripts. Você pode vê-lo aqui .
Walter Mitty
Isso funciona. Mas não consigo obter o fluxo de saída de feed ao vivo do ScriptBlock. A saída só é impressa quando o ScriptBlock retorna.
vothaison 14/04
8

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

Eu criei um invoke-async que permite executar vários blocos de script / cmdlets / funções ao mesmo tempo. isso é ótimo para trabalhos pequenos (varredura de sub-rede ou consulta wmi em centenas de máquinas), porque a sobrecarga para criar um espaço de execução versus o tempo de inicialização do trabalho inicial é bastante drástica. Pode ser usado assim.

com scriptblock,

$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

apenas cmdlet / function

$servers = Get-Content servers.txt 

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

Existem muitas respostas para isso hoje em dia:

  1. trabalhos (ou tarefas de encadeamento no PS 6/7 ou no módulo)
  2. processo inicial
  3. fluxos de trabalho
  4. API do PowerShell com outro espaço de execução
  5. invoke-command com vários computadores, que podem ser todos host local (tem que ser administrador)
  6. abas múltiplas da sessão (espaço de execução) no ISE, ou abas ISE remotas do powershell
  7. O PowerShell 7 tem foreach-object -paralleluma alternativa para o # 4

Aqui estão os fluxos de trabalho com literalmente um foreach -parallel:

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

work

3 done
1 done
2 done

Ou um fluxo de trabalho com um bloco paralelo:

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

Aqui está um exemplo de API com espaços de execução:

$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
js2010
fonte
7

Os trabalhos em segundo plano são caros de configurar e não são reutilizáveis. O MVP do PowerShell Oisin Grehan tem um bom exemplo de multiencadeamento do PowerShell.

(O site 25/10/2010 está inoperante, mas acessível através do Arquivo da Web).

Utilizamos o script Oisin adaptado para uso em uma rotina de carregamento de dados aqui:

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

Chad Miller
fonte
Fazer a ligação rot colocou em para esta resposta
Lucas
4

Para concluir as respostas anteriores, você também pode Wait-Jobaguardar a conclusão de todos os trabalhos:

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
Thomas
fonte
0

No Powershell 7, você pode usar o ForEach-Object -Parallel

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

Se você estiver usando o powershell de plataforma cruzada mais recente (que você deve btw) https://github.com/powershell/powershell#get-powershell , você poderá adicionar &scripts únicos para executar paralelos. (Use ;para executar sequencialmente)

No meu caso, eu precisava executar scripts de 2 npm em paralelo: npm run hotReload & npm run dev


Você também pode configurar o npm para usar powershellem seus scripts (por padrão, ele usa cmdno Windows).

Execute da pasta raiz do projeto: npm config set script-shell pwsh --userconfig ./.npmrc e use o comando de script npm único:npm run start

"start":"npm run hotReload & npm run dev"
JerryGoyal
fonte