Статьи

Удаленное выполнение PowerShell для каждой виртуальной машины Windows 8

метро-логотип-PowerShellЗапуск удаленного запуска PowerShell для каждой виртуальной машины Windows 8 на хосте Hyper-V может помочь вам поддерживать гостевую виртуальную машину за минимальное время.

После всей работы, проводимой в офисе вокруг PowerShell, я хотел пойти и решить еще одну проблему. Я продолжал сталкиваться с проблемой при использовании Hyper-V на моем локальном компьютере. Время от времени, чтобы сохранить их мягкость, мне нужно загружать каждую виртуальную машину и запускать обновления Windows. Моей первой мыслью было создание PowerShell, который бы автоматически обновлял их, и тогда я подумал … почему я не могу передать скрипт каждому из них.

  • ВЫПОЛНЕНО Удаленное выполнение PowerShell для каждой виртуальной машины
  • СОВЕРШЕНО Выполнить PowerShell против VM

Это потребует двух разных сценариев.

Удаленное выполнение против каждой виртуальной машины

Я использую Windows 8 с Hyper-V, поэтому все виртуальные машины являются локальными. Поэтому первым шагом является получение списка виртуальных машин и их цикл.

$VMs = Get-VM
foreach ($vm in $VMs)
{
    Write-Host  " $($vm.Name)" -ForegroundColor Yellow
}

Обратите внимание: Get-VM не выдает ошибку, если вы не работаете с повышенными привилегиями, она просто не возвращает машины. Теперь это меня раздражало на некоторое время.

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

If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
{   
    $arguments = "& '" + $myinvocation.mycommand.definition + "'"
    Start-Process powershell -Verb runAs -ArgumentList $arguments
    Break
}

Мы увидим, как это работает удаленно, но это позже.

Таким образом, для каждой виртуальной машины мы можем выполнить что-то. Однако я хочу запустить скрипт на моем хосте (мой главный компьютер) и заставить его выполнить часть этого скрипта на виртуальной машине. Для этого я выбрал отдельный файл ps1, который выполнял все локальные действия. На самом деле есть два способа сделать это: во-первых, «[удаленный регион]», который выполняет что-либо внутри этого блока кода на удаленной виртуальной машине, и во-вторых, «Invoke-Command -ComputerName [имя_компьютера]». Первое звучит так, как будто это усложнит мои сценарии, делая их слишком длинными, поэтому я выбрал второе. «Invoke-Command» имеет возможность передать другой ps1, который выполняет фактическое выполнение, что, в свою очередь, позволяет мне тестировать этот скрипт отдельно.

Итак, что я получу в итоге?

Try{
    Invoke-Command -ComputerName $compName -ScriptBlock {Set-ExecutionPolicy Unrestricted} -Credential $Cred
    Invoke-Command -ComputerName $compName -FilePath $RemoteScript -Credential $Cred
}

catch {
    "any other undefined errors"
    $error[0]
}

Вы заметите, что я установил политику выполнения «неограниченно» до выполнения кода. Поскольку я не подписываю скрипты PowerShell, мне нужно сделать это, чтобы они запускались. Хотя все гостевые виртуальные машины находятся в домене моего локального компьютера, тот, на котором запущен главный сценарий, — нет. Это создает ряд интересных проблем, которые означают, что вам нужно передать объект -Credential в метод invoke.

$Username = 'nakedalm\mrhinsh'
$Password = 'P2ssw0rd'
$pass = ConvertTo-SecureString -AsPlainText $Password -Force
$Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$pass

Затем этот объект позволит «Invoke-Command» правильно проходить аутентификацию на удаленном сервере. Однако при первом запуске этого сценария вы можете получить сообщение об ошибке, поскольку локальный компьютер не является доверенным. Если он был присоединен к домену, это не будет проблемой, но, поскольку он входит в рабочую группу, виртуальная машина, присоединенная к домену, отклонит вас.

Set-Item wsman:\localhost\client\trustedhosts *

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

Теперь мы можем перебрать виртуальные машины и выполнить удаленный скрипт для них. Тогда это звучит так, как будто мы почти закончили, однако, что происходит, когда виртуальные машины выключены или сохранены или приостановлены. Ну … ничего, поскольку вы не можете выполнить скрипт на компьютере, который не работает. Мне нужно было найти способ запустить машины, и, к счастью, PowerShell полностью управляет Hyper-V, поэтому для этого есть команда. Текущее состояние машины хранится в «$ vm.State», в котором есть несколько значений, для которых нам нужно делать разные вещи.

switch ($vm.State) 
{
     default {
          Write-Host  " Don't need to do anything with $($compName) as it is $($vm.State) "
     }
     "Paused"
     {
          Write-Host  "$($vm.Name) is paused so making it go "
          Resume-VM –Name *$compName* -Confirm:$false
     }
     "Saved"
     {
          Write-Host  "$($vm.Name) is paused so making it go "
          Start-VM –Name *$compName* -Confirm:$false
      }
     "Off"
     {
          Write-Host  "$($vm.Name) is stopped so making it go "
          Start-VM –Name *$compName* -Confirm:$false
     }
}

Здесь я использую оператор case, чтобы выбрать, что делать с каждой виртуальной машиной в зависимости от ее текущего состояния. Поэтому, если он «приостановлен», мне нужно «возобновить» его в рабочее состояние. Кроме того, я также сохраняю текущее состояние в переменной, так что я могу сбросить каждую ВМ в ее исходное состояние после запуска сценария. Это позволило бы мне загружать каждую виртуальную машину по очереди, а не все одновременно, и, таким образом, не перегружать мой локальный компьютер.

Так что остается сделать еще одну вещь. Когда вы изменяете состояние виртуальной машины с помощью «Start-VM», загрузка может занять некоторое время. Вы можете угадать и вставить задержку, но что, если виртуальной машине потребуется много времени для обновления групповой политики или, возможно, обновление Windows должно быть завершено.

do {
     Start-Sleep -milliseconds 100
     Write-Progress -activity "Waiting for VM to become responsive" -SecondsRemaining -1
} 
until ((Get-VMIntegrationService $vm | ?{$_.name -eq "Heartbeat"}).PrimaryStatusDescription -eq "OK")
Write-Progress -activity "Waiting for VM to become responsive" -SecondsRemaining -1 -Completed

Поэтому я отслеживаю «сердцебиение» виртуальной машины, чтобы определить, когда она станет доступной. Это вернет «ОК», как только виртуальная машина загрузится достаточно далеко, чтобы начать отвечать на Hyper-V. Это должно означать, что виртуальная машина теперь будет реагировать на команды. Поскольку это может занять некоторое время, я также хочу, чтобы какой-то индикатор прогресса был виден, чтобы любой, кто следит за сценарием, знал, что мы ждем. Существует встроенная команда «Write-Progress», которая позволяет нам сделать это, и она отрисовывается в соответствующей форме для носителя, на котором выполняется скрипт.

$Username = 'nakedalm\mrhinsh'
$Password = 'P2ssw0rd'
$nameRegex = "\[\d*\]\[(?.*)\](?.*)\[(?.*)\]"
$RemoteScript = D:\DataUsers\MrHinsh\Desktop\cmd\Service-VM.ps1
#------------------------------------------------------------------------
If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
{   
    $arguments = "& '" + $myinvocation.mycommand.definition + "'"
    Start-Process powershell -Verb runAs -ArgumentList $arguments
    Break
}
#------------------------------------------------------------------------
$pass = ConvertTo-SecureString -AsPlainText $Password -Force
$Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$pass
$confirm = $false
$VMs = Get-VM
Write-Output "Found $($VMs.Count) virtual computers"
foreach ($vm in $VMs)
{
    Write-Host  "Execute for $($vm.Name)" -ForegroundColor Yellow
    $matches = [Regex]::Matches($vm.Name,$nameRegex)
    if ($matches.count -gt 0)
    {
        $compName = $matches[0].groups["name"].value.Trim()
   
        $startState = $vm.State
        switch ($vm.State) 
        {
            default {
   
               Write-Host  " Don't need to do anything with $($compName) as it is $($vm.State) "
            }
            "Paused"
            {
                Write-Host  "$($vm.Name) is paused so making it go "
                Resume-VM –Name *$compName* -Confirm:$false
            }
            "Saved"
            {
                Write-Host  "$($vm.Name) is paused so making it go "
                Start-VM –Name *$compName* -Confirm:$false
            }
            "Off"
            {
                 Write-Host  "$($vm.Name) is stopped so making it go "
                 Start-VM –Name *$compName* -Confirm:$false
            }
        }

        Write-Host  " Waiting for '$($compName)' to respond "
        do {
                Start-Sleep -milliseconds 100
                Write-Progress -activity "Waiting for VM to become responsive" -SecondsRemaining -1
            } 
        until ((Get-VMIntegrationService $vm | ?{$_.name -eq "Heartbeat"}).PrimaryStatusDescription -eq "OK")
        Write-Progress -activity "Waiting for VM to become responsive" -SecondsRemaining -1 -Completed


        Write-Host  " Remote execution for '$($compName)' 🙂 "

        Try{
            Invoke-Command -ComputerName $compName -ScriptBlock {Set-ExecutionPolicy Unrestricted} -Credential $Cred
            Invoke-Command -ComputerName $compName -FilePath $RemoteScript -Credential $Cred
        }

        catch {
            "any other undefined errors"
            $error[0]
        }

         switch ($startState) 
        {
            "Off"
            {
                Stop-VM  –Name *$compName* -Force -Confirm:$false
            }
            "Paused"
            {
                Suspend-VM –Name *$compName* -Confirm:$false
            }
            "Saved"
            {
                Save-VM –Name *$compName* -Confirm:$false
            }
            default {
   
                Write-Host  " Leaving $($compName) running $startState "
            }
        }
        
    }
    else
    {
        Write-Host  "Unable to determin name of $($vm.Name) as it is in a crappy format. Needs to be '$nameRegex'" -ForegroundColor Red
    }
}

Имейте в виду, что это мой первый проход по этому сценарию и, что более страшно, мой первый настоящий сценарий PowerShell. Таким образом, я понятия не имею, правильно ли это делать, но это работает. Это позволяет вам выполнить другой сценарий PowerShell для каждой из моих виртуальных машин.

Выполнить против ВМ

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

Param(
  [string]$computerName = $env:COMPUTERNAME
)
If (!(Test-Path C:\Chocolatey\))
{
    Write-Host  " Install Chocolatey "
    iex ((new-object net.webclient).DownloadString("http://bit.ly/psChocInstall"))
}
Else 
{
    chocolatey update
}
cinst enablepsremoting
cinst PSWindowsUpdate -Version 1.4.6
Write-Output "--SERVICE"
Write-Output "-Allow ping for whatever"
netsh advfirewall firewall add rule name="All ICMP V4" dir=in action=allow protocol=icmpv4
Write-Output "-Enable File and Print Sharing for possible Lab Management use"
netsh advfirewall firewall set rule group=”File and Printer Sharing” new enable=Yes
Write-Output "-Check all Updates"
Import-Module PSWindowsUpdate
Get-WUInstall -MicrosoftUpdate -IgnoreUserInput -acceptall -autoreboot -verbose

Я, вероятно, сделаю еще несколько итераций этого сценария, но сейчас это:

  1. Уверен, что Chocolatey установлен
  2. Установленные пакеты Chocolatey, включая PSWindowsUpdate
  3. Проверяет некоторые изменения брандмауэра, которые мне нужны
  4. Загружает и устанавливает все обновления Microsoft

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

Вывод

Хотя я время от времени возился с PowerShell, это первый исполняемый скрипт, который я написал. Я все еще нахожусь в режиме копирования / вставки, но я точно вижу ценность обучения и использования PowerShell для всего, от установки приложений до настройки систем.

Вы можете делать с PowerShell все что угодно.