Я работаю над проектом по перезагрузке большого количества компьютеров. Одно из важных требований - организовать перезагрузку так, чтобы все машины не перезагружались одновременно (слишком быстро, и это вызовет проблемы с 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 в этой среде был слишком нестабильным для использования.