使用Start-Job同时运行多个脚本块(而不是循环)

时间:2012-03-07 12:00:06

标签: powershell foreach jobs

大家好!

我一直在寻找一种方法来提高我的脚本效率,我得出结论(在StackOverflow上的好人帮助下),Start-Job是可行的方法。

我有以下foreach-loop,我想在$ servers中的所有服务器上同时运行。我在理解如何实际收集从Receive-Job返回的信息并添加到$ serverlist时遇到问题。

PS:我知道我远没有把它钉死,但我真的很感激一些帮助,因为我对Start-Job和Receive-Job的运作方式感到非常困惑。

# List 4 servers (for testing)
$servers = Get-QADComputer -sizelimit 4 -WarningAction SilentlyContinue -OSName *server*,*hyper*

# Create list
$serverlistlist = @()

# Loop servers
foreach($server in $servers) {

    # Fetch IP
    $ipaddress = [System.Net.Dns]::GetHostAddresses($Server.name)| select-object IPAddressToString -expandproperty IPAddressToString

    # Gather OSName through WMI
    $OSName = (Get-WmiObject Win32_OperatingSystem -ComputerName $server.name ).caption

    # Ping the server
    if (Test-Connection -ComputerName $server.name -count 1 -Quiet ) {
        $reachable = "Yes"
    }

    # Save info about server
    $serverInfo = New-Object -TypeName PSObject -Property @{
        SystemName = ($server.name).ToLower()
        IPAddress = $IPAddress
        OSName = $OSName
    }
    $serverlist += $serverinfo | Select-Object SystemName,IPAddress,OSName
}

备注

  • 我将$ serverlist输出到脚本末尾的csv文件
  • 我在完整脚本中列出了aprox 500服务器

2 个答案:

答案 0 :(得分:10)

由于您的循环只需要使用字符串,因此很容易将其转换为并发脚本。

以下是使循环使用后台作业加速处理的示例。

代码将遍历数组并启动后台作业以运行脚本块$sb中的代码。 $maxJobs变量控制一次运行的作业数,$chunkSize变量控制每个后台作业将处理的服务器数量。

在脚本块中添加剩余的处理,添加要返回到PsObject的其他属性。

$sb = {
    $serverInfos = @()
    $args | % {
        $IPAddress = [Net.Dns]::GetHostAddresses($_) | select -expand IPAddressToString
        # More processing here... 
        $serverInfos += New-Object -TypeName PsObject -Property @{ IPAddress = $IPAddress }
    }
    return $serverInfos
}

[string[]] $servers = Get-QADComputer -sizelimit 500 -WarningAction SilentlyContinue -OSName *server*,*hyper* | Select -Expand Name

$maxJobs = 10 # Max concurrent running jobs.
$chunkSize = 5 # Number of servers to process in a job.
$jobs = @()

# Process server list.
for ($i = 0 ; $i -le $servers.Count ; $i+=($chunkSize)) {
    if ($servers.Count - $i -le $chunkSize) 
        { $c = $servers.Count - $i } else { $c = $chunkSize }
    $c-- # Array is 0 indexed.

    # Spin up job.
    $jobs += Start-Job -ScriptBlock $sb -ArgumentList ( $servers[($i)..($i+$c)] ) 
    $running = @($jobs | ? {$_.State -eq 'Running'})

    # Throttle jobs.
    while ($running.Count -ge $maxJobs) {
        $finished = Wait-Job -Job $jobs -Any
        $running = @($jobs | ? {$_.State -eq 'Running'})
    }
}

# Wait for remaining.
Wait-Job -Job $jobs > $null

$jobs | Receive-Job | Select IPAddress

以下是每个作业处理单个服务器的版本:

$servers = Get-QADComputer -WarningAction SilentlyContinue -OSName *server*,*hyper*

# Create list
$serverlist = @()

$sb = {
    param ([string] $ServerName)
    try {
        # Fetch IP
        $ipaddress = [System.Net.Dns]::GetHostAddresses($ServerName)| select-object IPAddressToString -expandproperty IPAddressToString

        # Gather OSName through WMI
        $OSName = (Get-WmiObject Win32_OperatingSystem -ComputerName $ServerName ).caption

        # Ping the server
        if (Test-Connection -ComputerName $ServerName -count 1 -Quiet ) {
            $reachable = "Yes"
        }

        # Save info about server
        $serverInfo = New-Object -TypeName PSObject -Property @{
            SystemName = ($ServerName).ToLower()
            IPAddress = $IPAddress
            OSName = $OSName
        }
        return $serverInfo
    } catch {
        throw 'Failed to process server named {0}. The error was "{1}".' -f $ServerName, $_
    }
}

# Loop servers
$max = 5
$jobs = @()
foreach($server in $servers) {
    $jobs += Start-Job -ScriptBlock $sb -ArgumentList $server.Name
    $running = @($jobs | ? {$_.State -eq 'Running'})

    # Throttle jobs.
    while ($running.Count -ge $max) {
        $finished = Wait-Job -Job $jobs -Any
        $running = @($jobs | ? {$_.State -eq 'Running'})
    }
}

# Wait for remaining.
Wait-Job -Job $jobs > $null

# Check for failed jobs.
$failed = @($jobs | ? {$_.State -eq 'Failed'})
if ($failed.Count -gt 0) {
    $failed | % {
        $_.ChildJobs[0].JobStateInfo.Reason.Message
    }
}

# Collect job data.
$jobs | % {
    $serverlist += $_ | Receive-Job | Select-Object SystemName,IPAddress,OSName
}

答案 1 :(得分:3)

你需要了解的关于Start-Job的一点是,它启动了一个新的Powershell实例,作为一个单独的进程运行。 Receive-job为您提供了一种机制,可以将该会话的输出拉回到本地会话中,以便在主脚本中使用它。听起来很有吸引力,同时运行所有这些将意味着在您的计算机上启动500个Powershell实例,所有这些都会立即运行。这可能会产生一些意想不到的后果。

如果有帮助的话,这是分割工作的一种方法:

将一组计算机名称拆分为$ n个数组,并使用每个数组作为脚本块的参数列表启动一个新作业:

  $computers = gc c:\somedir\complist.txt
  $n = 6
  $complists = @{}
  $count = 0
  $computers |% {$complists[$count % $n] += @($_);$count++}

  0..($n-1) |% {
  start-job -scriptblock {gwmi win32_operatingsystem -computername $args} - argumentlist $complists[$_]
  }