Powershell:并行运行多个作业并查看后台作业的流结果

yyhrrdl8  于 2022-12-04  发布在  Shell
关注(0)|答案(6)|浏览(187)

概览

希望调用一个Powershell脚本,该脚本接受一个参数,在后台运行每个作业,并向我显示详细的输出。

我遇到的问题

脚本似乎在运行,但我想通过在后台作业运行时对它们的结果进行流式处理来确认这一点。

代码

###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

我希望能够看到输出或将其存储在某个地方,以便我可以回头查看以确保脚本运行并查看哪些服务器重新启动等。

结论:

在过去,我运行脚本,它经历了更新服务器一次一个,并给我我想要的输出,但当我开始做更多的服务器-这个任务太长了,这就是为什么我试图使用后台作业与“启动作业”。
有人能帮我解决这个问题吗?

czq61nw1

czq61nw11#

你可以看看模块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(模拟一个缓慢的作业),并使用变量$_作为输入,即当前服务器。
如果每个作业不是处理器密集型作业,则增加参数Count(默认值为处理器计数)以提高性能。

lokaqttq

lokaqttq2#

这不是一个新的问题,但我觉得它缺少一个答案,包括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."

背景作业.ps1(例如):

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

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

因此,当启动TheScript.ps1时,它将写入“Processing server”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.
7vhp5slm

7vhp5slm3#

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
}

它将显示所有的输出,只要一个工作完成。没有被混淆。

6ioyuze2

6ioyuze24#

如果您希望处理多个正在进行的作业,您可能需要处理输出,以帮助在控制台上保持输出与作业直接相关。

$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'
pgccezyw

pgccezyw5#

您可以在收到所有作业后执行以下操作来获得结果:
$array=@()获取作业名称 *| 其中{$array+=$_.子作业.输出}
.ChildJobs.输出将包含每个作业中返回任何内容

2mbi3lxu

2mbi3lxu6#

function OutputJoblogs {
    [CmdletBinding(DefaultParameterSetName='Name')]
    Param
    (
        [Parameter(Mandatory=$true, Position=0)]
        [System.Management.Automation.Job] $job,        
        [Parameter(Mandatory=$true, Position=1)]
        [string] $logFolder,        
        [Parameter(Mandatory=$true, Position=2)]
        [string] $logTimeStamp
    )

    #Output All logs
    while ($job.sate -eq "Running" -or $job.HasMoreData){
        start-sleep -Seconds 1
        foreach($remotejob in $job.ChildJobs){
            if($remotejob.HasMoreData){
                $output=(Receive-Job $remotejob)
                if($output -gt 0){
                    $remotejob.location +": "+ (($output) | Tee-Object -Append -file ("$logFolder\$logTimeStamp."+$remotejob.Location+".txt"))
                }
            }
        }
    }
    #Output Errors
    foreach($remotejob in $job.ChildJobs){
        if($remotejob.Error.Count -gt0){$remotejob.location +": "}
        foreach($myerr in $remotejob.Error){
            $myerr 2>&1 | Tee-Object -Append -file ("$logFolder\$logTimeStamp."+$remotejob.Location+".ERROR.txt")
        }
        if($remotejob.JobStateInfo.Reason.ErrorRecord.Count -gt 0){$remotejob.location +": "}
        foreach($myerr in $remotejob.JobStateInfo.Reason.ErrorRecord){
            $myerr 2>&1 | Tee-Object -Append -file ("$logFolder\$logTimeStamp."+$remotejob.Location+".ERROR.txt")
        }
    }
}

#example of usage
$logfileDate="$((Get-Date).ToString('yyyy-MM-dd-HH.mm.ss'))"
$job = Invoke-Command -ComputerName "servername1","servername2" -ScriptBlock { 
        for ($i=1; $i -le 5; $i++) {
            $i+"`n";
            if($i -gt 2){
                write-error "Bad thing happened"};
            if($i -eq 4){
                throw "Super Bad thing happened"
            };
            start-sleep -Seconds 1
        }  
    } -asjob

OutputJoblogs -Job $job -logFolder "$PSScriptRoot\logs" -logTimeStamp $logfileDate

相关问题