Powershell: запуск нескольких заданий в parralel и просмотр результатов потоковой передачи из фоновых заданий

Обзор

Ищете вызов Powershell script, который принимает аргумент, запускает каждое задание в фоновом режиме и показывает мне подробный вывод.

Проблема, с которой я сталкиваюсь

Выполняется script, но я хочу точно убедиться в этом потоком результатов фоновых заданий по мере их запуска.

код

###StartServerUpdates.ps1 Script###

#get list of servers to update from text file and store in array
$servers=get-content c:\serverstoupdate.txt

#run all jobs, using multi-threading, in background
ForEach($server in $servers){
  Start-Job -FilePath c:\cefcu_it\psscripts\PSPatch.ps1 -ArgumentList $server
}

#Wait for all jobs
Get-Job | Wait-Job

#Get all job results
Get-Job | Receive-Job

То, что я сейчас вижу:

Id              Name            State      HasMoreData     Location             Command                  
--              ----            -----      -----------     --------             -------                  
23              Job23           Running    True            localhost            #patch server ...        
25              Job25           Running    True            localhost            #patch server ...        

Что я хочу видеть:

Searching for approved updates ...

Update Found:  Security Update for Windows Server 2003 (KB2807986)
Update Found:  Windows Malicious Software Removal Tool - March 2013 (KB890830)

Download complete.  Installing updates ...

The system must be rebooted to complete installation.
cscript exited on "myServer" with error code 3.
Reboot required...
Waiting for server to reboot (35)

Searching for approved updates ...

There are no updates to install.
cscript exited on "myServer" with error code 2.
Servername "myServer" is fully patched after 2 loops

Я хочу видеть выход или хранить его где-нибудь, чтобы я мог вернуться, чтобы убедиться, что script запущен и посмотреть, какие серверы перезагружены и т.д.

Вывод:

В прошлом я запускал script, и он прошел обновление серверов по одному и дал мне результат, который я хотел, но когда я начал делать больше серверов, эта задача заняла слишком много времени, и именно поэтому я Я пытаюсь использовать фоновые задания с помощью "Start-Job".

Может кто-нибудь помочь мне понять это, пожалуйста?

Ответ 1

Вы можете взглянуть на модуль SplitPipeline. Это специально для таких задач. Рабочий демо-код:

# import the module (not necessary in PS V3)
Import-Module SplitPipeline

# some servers (from 1 to 10 for the test)
$servers = 1..10

# process servers by parallel pipelines and output results immediately
$servers | Split-Pipeline {process{"processing server $_"; sleep 1}} -Load 1, 1

Для вашей задачи замените "processing server $_"; sleep 1 (имитирует медленное задание) на вызов вашего script и используйте переменную $_ в качестве ввода, текущий сервер.

Если каждое задание не является интенсивным, то для повышения производительности увеличьте параметр Count (по умолчанию - количество процессоров).

Ответ 2

Не новый вопрос, но я чувствую, что ему не хватает ответа, включая Powershell, используя рабочие процессы и его параллельные возможности, начиная с версии PowerShell 3. Что меньше кода и, возможно, более понятно, чем запуск и ожидание заданий, что, конечно, хорошо работает, как хорошо.

У меня есть два файла: TheScript.ps1, который координирует серверы и BackgroundJob.ps1, который выполняет некоторую проверку. Они должны находиться в одном каталоге.

Write-Output в фоновом файле задания записывается в тот же поток, который вы видите при запуске TheScript.ps1.

TheScript.ps1:

workflow parallelCheckServer {
    param ($Servers)
    foreach -parallel($Server in $Servers)
    {
        Invoke-Expression -Command ".\BackgroundJob.ps1 -Server $Server"
    }
}

parallelCheckServer -Servers @("host1.com", "host2.com", "host3.com")

Write-Output "Done with all servers."

BackgroundJob.ps1 (например):

param (
    [Parameter(Mandatory=$true)] [string] $server
)

Write-Host "[$server]`t Processing server $server"
Start-Sleep -Seconds 5

Таким образом, при запуске TheScript.ps1 он будет писать "Сервер обработки" 3 раза, но он не будет ждать 15 секунд, а вместо этого 5, потому что они запускаются параллельно.

[host3.com]  Processing server host3.com
[host2.com]  Processing server host2.com
[host1.com]  Processing server host1.com
Done with all servers.

Ответ 3

В вашем цикле ForEach вы захотите захватить вывод, сгенерированный уже запущенными Заданиями.

Пример не протестирован

$sb = {
     "Starting Job on $($args[0])"
     #Do something
     "$($args[0]) => Do something completed successfully"
     "$($args[0]) => Now for something completely different"
     "Ending Job on $($args[0])"
}
Foreach($computer in $computers){
    Start-Job -ScriptBlock $sb -Args $computer | Out-Null
    Get-Job | Receive-Job
}

Теперь, если вы сделаете это, все ваши результаты будут смешанными. Возможно, вы захотите поставить штамп на свой подробный вывод, чтобы сообщить, из какого результата пришли.

или

Foreach($computer in $computers){
    Start-Job -ScriptBlock $sb -Args $computer | Out-Null
    Get-Job | ? {$_.State -eq 'Complete' -and $_.HasMoreData} | % {Receive-Job $_}
}
while((Get-Job -State Running).count){
    Get-Job | ? {$_.State -eq 'Complete' -and $_.HasMoreData} | % {Receive-Job $_}
    start-sleep -seconds 1
}

Он отобразит весь вывод сразу после завершения задания. Не путайте.

Ответ 4

Если вы хотите, чтобы выполнялось несколько заданий, вы, вероятно, захотите массировать выходные данные, чтобы помочь сохранить результат, с которым работает прямо на консоли.

$BGList = 'Black','Green','DarkBlue','DarkCyan','Red','DarkGreen'
$JobHash = @{};$ColorHash = @{};$i=0


ForEach($server in $servers)
{
  Start-Job -FilePath c:\cefcu_it\psscripts\PSPatch.ps1 -ArgumentList $server |
   foreach {
            $ColorHash[$_.ID] = $BGList[$i++]
            $JobHash[$_.ID] = $Server
           }
}
  While ((Get-Job).State -match 'Running')
   {
     foreach ($Job in  Get-Job | where {$_.HasMoreData})
       {
         [System.Console]::BackgroundColor = $ColorHash[$Job.ID]
         Write-Host $JobHash[$Job.ID] -ForegroundColor Black -BackgroundColor White
         Receive-Job $Job
       }
    Start-Sleep -Seconds 5
   } 
 [System.Console]::BackgroundColor = 'Black'   

Ответ 5

Вы можете получить результаты, выполнив что-то вроде этого после получения всех заданий:

$массив = @() Get-Job -Name * | где {$ массив + = $_. ChildJobs.output}

.ChildJobs.output будет иметь все, что было возвращено в каждом задании.