Назад | Перейти на главную страницу

Очередь перезагрузок в Powershell

Я работаю над проектом по перезагрузке большого количества компьютеров. Одно из важных требований - организовать перезагрузку так, чтобы все машины не перезагружались одновременно (слишком быстро, и это вызовет проблемы с SAN).

Я попытался сделать это в рабочем процессе, ограничив до 50 параллельных действий и добавив 15-секундную задержку (200 перезагрузок в минуту).

workflow Bounce-Computer {
param(
[string[]]$Computers
)
foreach -parallel -throttlelimit 50 ($computer in $Computers) {
    Restart-Computer -PSComputerName $computer -Force
    Start-Sleep -Seconds 15
    }
}

Но я столкнулся с проблемой, когда рабочий процесс зависал, если WMI был сломан на целевом компьютере.

Помимо исправления WMI на всех целевых машинах (их несколько тысяч), как я могу делать что-то подобное контролируемым образом? Работа?

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

Workflow Invoke-MassRestart{
    Param
    (
        [parameter(mandatory=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [string[]]
        $ComputerName,

        [int]
        $Throttle = 5,

        [int]
        $Delay = 5
    )

    Foreach -parallel -ThrottleLimit $Throttle ($Computer in $ComputerName){
        Sequence {
            InlineScript{
                [void](Restart-Computer -PSComputerName $using:Computer)
            }
            Start-Sleep -Seconds $Delay
        }
    }
}

Дайте мне знать, если вы хотите, чтобы я кое-что объяснил. Я хотел бы назвать свой кастинг Restart-Computer к [void]. Это в основном говорит системе, что нужно подать команду и двигаться дальше. Я думаю, вас облажали, когда он ждал 50 операций, чтобы сообщить о каком-то статусе. Также обратите внимание на уникальную область видимости, необходимую внутри InlineScript{} блок. Я также решил сделать так, чтобы он мог принимать входные данные конвейера от Get-ADComputer.

Не меняя сценарий слишком сильно, вы можете позволить ему перехватить ошибку тайм-аута WMI (если она выдает обнаруживаемую ошибку во время зависания), а затем перезагрузить следующий компьютер. Оператор catch может выводить проблемный компьютер и ошибку, чтобы вы могли просмотреть их позже.

Workflow Bounce-Computer {
 param([string[]]$Computers)
 foreach -parallel -throttlelimit 50 ($computer in $Computers) {
    try{
        Restart-Computer -PSComputerName $computer -Force -ErrorAction stop
        Start-Sleep -Seconds 15
    } 
    catch [AppropriateWMIExceptionError]
    { 
       echo 'WMI timeout error' #Or another action to note the problem computer
    }
}

Вместо этого я бы использовал движок заданий и сделал бы что-то вроде ниже. Это не полностью протестировано, но должно работать, возможно, где-то что-то упустил.

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

Код ниже:

function restart-myComputer (
[Parameter(ParameterSetName="restart", Mandatory=".")]
[Parameter(ParameterSetName="bg", Mandatory=".")]
[string]$computer,
[Parameter(ParameterSetName="restart")]
[int]$wait,
[Parameter(ParameterSetName="bg")]
[switch]$bg)
{ 
  if ($bg) {
    Start-Job -name $computer -ScriptBlock ([scriptblock]::create("Restart-Computer -PSComputerName $computer -Force -ErrorAction stop")) > $null
  } else {
    Restart-Computer -PSComputerName $computer -Force -ErrorAction stop
    Start-Sleep -Seconds $wait
  }
}

function remove-myJobs (
[Parameter(ParameterSetName="remove")]
[string[]]$names,
[Parameter(ParameterSetName="remove")]
[int]$maxRunTime = 30
{
  $d = get-date
  [string[]]$stillRunningJobArr = @()
  foreach ($name in $names) {
    $job = get-job -name $name 
    $diffTime = $d - $job.PSBeginTime
    if($diffTime.TotalSeconds -gt $maxRunTime) {
      remove-job -name $name -Force
      write-host "Removed job $name that ran longer then $maxRunTime seconds"
    } else {
      write-host "Job $name has still not been running for more then $maxRunTime"
      $stillRunningJobArr += $name
    }
  }
  return $stillRunningJobArr
}

function remove-allMyJobs (
[Parameter(ParameterSetName="remove")]
[int]$maxRunTime = 30)
{
  $d = get-date
  foreach ($job in get-job) {
    $diffTime = $d - $job.PSBeginTime
    $name = $job.name

    if($diffTime.TotalSeconds -gt $maxRunTime) {
      remove-job -name $name -Force
      write-host "Removed job $name that ran longer then $maxRunTime seconds"
    } else {
      write-host "Job $name has still not been running for more then $maxRunTime"
    }
  }
}

function restart-myComputers (
[Parameter(ParameterSetName="restart", Mandatory=".")]
[Parameter(ParameterSetName="bg", Mandatory=".")]
[string[]]$computers,
[Parameter(ParameterSetName="bg", Mandatory=".")]
[int]$maxConcurrentJobs,
[Parameter(ParameterSetName="bg", Mandatory=".")]
[Parameter(ParameterSetName="restart")]
[int]$wait,
[Parameter(ParameterSetName="bg")]
[switch]$bg)
{
  [string[]]$restartedComputerArr = @()
  if ($bg) {
    foreach ($computer in $computers) {
      if((get-job -state 'Running').Count -gt $maxConcurrentJobs) {
        $restartedComputerArr = stop-myJobs -$restartedComputerArr -maxRunTime $maxRunTime
        sleep $wait # Wait to get as many jobs to complete as possible.
      }
      Restart-myComputer -computer $computer -bg
      $restartedComputerArr += $computer
    }
  } else {
    Restart-myComputer -computer $computer -wait $wait
  }
  remove-allMyJobs
}

В итоге я переписал сценарий, чтобы использовать VIC. WMI в этой среде был слишком нестабильным для использования.