Запуск нескольких блоков скриптов одновременно с помощью Start-Job (вместо зацикливания) - PullRequest
4 голосов
/ 07 марта 2012

Привет всем!

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

У меня есть следующий цикл foreach, который я хотел бы запустить одновременно на всех серверах в $ server.У меня проблемы с пониманием того, как я на самом деле собираю информацию, возвращаемую из 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
}

Примечания

  • Iвыводит $ serverlist в csv-файл в конце скрипта
  • Я перечисляю aprox 500 серверов в моем полном скрипте

Ответы [ 2 ]

11 голосов
/ 08 марта 2012

Поскольку вашему циклу нужно работать только со строкой, его легко превратить в параллельный скрипт.

Ниже приведен пример того, как заставить ваш цикл использовать фоновые задания для ускорения обработки.

Код будет циклически проходить по массиву и раскручивать фоновые задания для запуска кода в блоке скрипта $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
}
3 голосов
/ 07 марта 2012

Что нужно знать о 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[$_]
  }
...