Экспорт виртуальных машин Hyper-V. Скрипт v2.0

11.02.2012 - 18:26

Рассмотрим скрипт по экспортированию ВМ на сервере Hyper-V.
Задача стоит в автоматизации процесса с максимальной логикой не в ущерб производительности. В результате, мною был написан скрипт, который я подавал раннем посте. Ситуация менялась с каждым днём, система уже работала в продуктиве, поэтому нужно было сократить риск что-то поломать до минимального. В результате, я доработал скрипт, концепция которого не изменилась:

  1. Находим ВМ
  2. Выключаем ВМ
  3. Экспортируем ВМ
  4. Включаем ВМ

Добавились только "рюшечки" и "вензеля".
Основное требование - сохранение 2-х копий ВМ, текущей и предыдущей. При формировании новой копии, предыдущая удаляется, а текущая переименовывается в предыдущую. Все реализовано на примере каталогов Today и Yesterday.

Основные возможности скрипта:

  1. Проверяет наличие нужной ВМ среди списка ВМ. Все действия записываются в log-файл.
  2. Если ВМ найдена - Удаляет каталог с прошлой записью, текущий каталог переименовывает в прошлый, создаёт каталог для текущей записи.
  3. Проверяет состояние ВМ. Если ВМ запущена - Останавливает ВМ. Экспортирует ВМ в назначенный каталог. Запускает.
  4. Если ВМ остановлена - экспортирует ВМ в назначенный каталог.
  5. Если ВМ в другом состоянии - пишет в лог.
  6. Если ВМ не найдена - пишет в лог.
  7. Позле экспорта - снимает всю информацию про VHD, тестирует VHD. Пишет в лог.
  8. Отправляет e-mail адресатам о проведённых операциях с установкой важности письма при неудаче.

Есть парочка особенностей:

  1. При отправке письма, лог, который планируется отправить вложением, не может находиться по UNC пути. Другими словами, если требуется отправить лог во вложении, нужно его разместить на локальном диске либо сетевой каталог подключить отдельным диском.
  2. Скрипт дополнительными функциями собирает информацию про VHD диск. Для успешной отработки функций аудита, диск не должен располагаться по UNC пути, опять придётся подключать сетевой диск.
  3. Для контроля выполнения экспорта машин по графику, было предпринято внедрить параметр "Имя ВМ".

Для успешной работы скрипта, нужно установить данный модуль. Я думаю, сложностей не возникнет.
Скрипт:

param ($VM) # Входящий параметр Виртуальной Машины (ВМ)

if (!(Get-Module -Name hyperv))
    {
        # Включить модуль
        import-module hyperv
    }
if($VM -eq $null)
    {
        Write-Host "Введите имя виртуальной машины."
    }
   
# ==== Начальные параметры
$ComputerName = Get-Content env:COMPUTERNAME
# Подключаем диск R: для получения информации про VHD
Invoke-Expression -Command "net use R: \\Server /y"
# Обнуляем счётчики
$count = $false # Триггер найденной ВМ
$export_count = $false # Счётчик успешного экспорта ВМ
# Определяем имя\адрес лог-файла
$log = "\\Server\backup\Logs\$VM\" + (Get-Date -Format "yyy-MM") +".log"
# Определяем путь к папке с РК
$BackupDir = "
\\Server\backup\"+$VM
# Путь к папке с РК для снятия информации с VHD
$InfoDir = "R:\backup\"+$VM
# Данные для email
$Sender = "
BackupOperator@contoso.com"
$SMTP = "
smtp.contoso.com"
$Recipients = @("
backup_monitoring@contoso.com") # группа рассылки с адресатами, которые мониторят резервное копирование
$AttachePath = "
R:\backup\Logs\$VM\" + (Get-Date -Format "yyy-MM") +".log"

# Функция оптравки сообщения, на момент написания я не знал про функцию Send-MailMessage и написал свою функцию. Переделывать не хочу, "Работает - не трогай!"
function SendEmail($Subject,$Body,$Priority)
    {
        $Message = New-Object System.Net.Mail.MailMessage
        $Server = New-Object System.Net.Mail.SMTPClient
        $Attache = New-Object System.Net.Mail.Attachment($AttachePath)
        # ==== Message
        $Recipients | ForEach-Object {$Message.To.Add($_)}
        $Message.From = $Sender
        $Message.Subject = $Subject
        $Message.Body = $Body
        $Message.Attachments.Add($Attache)
        $Message.Priority = [System.Net.Mail.MailPriority]::$Priority
        # ==== Sending
        $Server.Host = $SMTP
        $Server.Send($Message)
    }

# =========================================
# Начинаем запись лога
$log_string = $log_string + (Get-Date) + " `r`n Начало работы скрипта экспорта ВМ "+$VM + " сервер: " + $ComputerName + "`r`n Получение списка Виртуальных машин... `r`n"

# Получаем список ВМ
$VMArray = Get-VM | Select-Object -Property VMElementName
foreach($element in $VMArray)
{
    # Ищем заданную ВМ
    if($VM -eq $element.VMElementName)
    {
        $log_string = $log_string + "Виртуальная машина найдена. Выполняю заданные действия... `r`n"
        $count = $true            
# ========== Операции с папками ===============

        # Удаляем папку Yesterday
        Remove-Item -Path ($BackupDir + "\Yesterday") -Recurse -ErrorVariable err
        if($err)
        {
            # Если ошибка удаления - останавливаем выполнение.
            $log_string = $log_string + $err + "`r`n"
            $MailBody = $Mailbody + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Удаление каталога Yesterday не удалось, экспорт $VM прерван. Ошибка: " + $err
        }
        else
        {
            $log_string = $log_string + "Удаление каталога Yesterday проведено успешно. `r`n"
            # Переименовываем папку Today
            Rename-Item -Path ($BackupDir + "\Today") -NewName ($BackupDir + "\Yesterday") -Force -ErrorVariable err
            if($err)
            {
                # Если ошибка переименовывания - останавливаем выполнение.
                $log_string = $log_string + $err + "`r`n"
                $MailBody = $Mailbody + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Переименование каталога Yesterday не удалось, экспорт $VM прерван. Ошибка: " + $err
            }
            else
            {
                $log_string = $log_string + "Переименование папок проведено успешно. `r`n"
                # Создаём папку Today
                New-Item -Path ($BackupDir + "\Today") -ItemType Directory -Force -ErrorVariable err
                if($err)
                {
                    # Если ошибка создания - останавливаем выполнение.
                    $log_string = $log_string + $err + "`r`n"
                    $MailBody = $Mailbody + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + "Создание каталога Yesterday не удалось, экспорт $VM прерван. Ошибка: " + $err
                }
                else
                {
                    $log_string = $log_string + "Создание папки Today проведено успешно. `r`n"
#========== Конец работы с папками          

#========== Работа с ВМ
                    # Если состояние машины - "Работает", то
                    if((Get-VMSummary $VM).enabledstate -eq "Running")
                    {
                        # Запустить выключение
                        $log_string = $log_string + ((Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Виртуальная машина запущена. Выполняю остановку. `r`n")
                        invoke-vmshutdown -VM $VM -Reason "Export VM." -Force -ErrorVariable err
                        if($err)
                        {
                            $log_string = $log_string + $err + "`r`n"
                            $MailBody = $Mailbody + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Остановка ВМ не произведена, экспорт $VM прерван. Ошибка: " + $err
                        }
                        else
                        {
                            # Экспорт ВМ без подтверждения со всеми файлами (xml, vhd и т.д.) и подождать, пока не выполнится
                            $log_string = $log_string + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Виртуальная машина остановлена. Выполняю экспорт.`r`n"
                            $MailBody = $MailBody + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Виртуальная машина остановлена.`r`n"
                            $MailBody = $MailBody + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Выполняю экспорт виртуальной машины.`r`n"
                            export-VM -VM $VM -path ($backupDir + "\Today") -force -copystate -wait -ErrorVariable err
                            if($err)
                            {
                                $log_string = $log_string + $err + "`r`n"
                                $MailBody = $Mailbody + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Экспорт ВМ завершён с ошибкой, экспорт $VM прерван. Ошибка: " + $err
                            }
                            else
                            {
                                $log_string = $log_string + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Виртуальная машина успешно экспортирована. Выполняю запуск.`r`n"
                                $MailBody = $MailBody + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Виртуальная машина успешно экспортирована.`r`n"
                                $export_count = $true
                            }
                            # Запуск ВМ потверждением включения на протяжении 300с перед выполнением следующей команды                            
                            Start-VM -vm $VM -HeartBeatTimeOut 300 -ErrorVariable err
                            if($err)
                            {
                                $log_string = $log_string + $err + "`r`n"
                                $MailBody = $Mailbody + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Запуск ВМ не произведен. Ошибка: " + $err
                            }
                            else
                            {
                                $log_string = $log_string + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Виртуальная машина запущена.`r`n"
                                $MailBody = $MailBody + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Виртуальная машина запущена.`r`n"
                            }
                        }
                    }  
                    elseif((Get-VMSummary $VM).enabledstate -eq "Stopped")
                    {
                        # Экспорт ВМ без подтверждения со всеми файлами (xml, vhd и т.д.) и подождать, пока не выполнится
                        $log_string = $log_string + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Виртуальная машина находится в режиме Остановлена. Выполняю экспорт.`r`n"
                        $MailBody = $MailBody + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Виртуальная машина находится в режиме Остановлена.`r`n"
                        export-VM -VM $VM -path ($backupDir + "\Today") -force -copystate -wait -ErrorVariable err
                        if($err)
                        {
                            $log_string = $log_string + $err + "`r`n"
                            $MailBody = $Mailbody + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Экспорт ВМ не произведен, экспорт $VM прерван. Ошибка: " + $err
                        }
                        else
                        {
                            $log_string = $log_string + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Виртуальная машина успешно экспортирована.`r`n"
                            $MailBody = $MailBody + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Виртуальная машина успешно экспортирована.`r`n"
                            $export_count = $true
                        }
                    }
                    else
                    {
                        $vm_state = (Get-VMSummary -VM $VM).EnabledState
                        $log_string = $log_string + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Экспорт виртуальной машины не произведён, состояние $VM = $vm_state.`r`n"
                        $MailBody = $MailBody + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Экспорт виртуальной машины не произведён, состояние $VM = $vm_state.`r`n"
                    }
                }
            }
        }
    }
}
if($count) # Если ВМ найдена
{
    if(!($export_count)) # Если ВМ не экспортирована
    {
        $log_string = $log_string + "Экспорт виртуальной машины $VM выполнен с ошибками. Процесс прерван. `r`n"
        $MailBody = $MailBody + "Экспорт виртуальной машины $VM выполнен с ошибками. Процесс прерван.`r`n"
    }
    else # Если ВМ экспортирована
    {
        $log_string = $log_string + "Провожу поиск *.VHD для $VM... `r`n"
        # Проводим поиск *.vhd в заданном каталоге
        if(Get-Item -Path ($InfoDir + "\Today\" + $VM + "\Virtual Hard Disks\*") -Include *.*vhd | Select-Object -Property Name)
        {
            #$VHDInfo = "
VHD Info:`r`n"
            foreach($element in (Get-Item -Path ($InfoDir + "
\Today\" + $VM + "\Virtual Hard Disks\*") -Include *.*vhd | Select-Object -Property Name))
            {
                # Для каждого найденного элемента получаем информацию и пишем в лог и строку для письма
                $log_string = $log_string + "Информация для " + $element.Name + ":`r`n"
                $SomeVHDInfo = Get-VHDInfo -VHDPaths ($InfoDir + "\Today\" + $VM + "\Virtual Hard Disks\" + $element.Name)
                $log_string = $log_string + "Путь: " + $SomeVHDInfo.Path + "`r`n"
                $log_string = $log_string + "Объём файла: " + ([System.Math]::Round(($SomeVHDInfo.FileSize/1Gb),2)) + "Gb`r`n"
                $log_string = $log_string + "Привязка к VM: " + $SomeVHDInfo.InSavedState + "`r`n"
                $log_string = $log_string + "Смонтирован: " + $SomeVHDInfo.InUse + "`r`n"
                $log_string = $log_string + "Максимальный объём файла: " + ([System.Math]::Round(($SomeVHDInfo.MaxInternalSize/1Gb),2)) + "Gb`r`n"
                $log_string = $log_string + "Тип образа: " + $SomeVHDInfo.TypeName + "`r`n"
                # Проводим валидацию VHD ВМ
                $TestVHD = "VHD: " + $element.Name + ": " + (Test-VHD -VHDPaths ($InfoDir + "\Today\" + $VM + "\Virtual Hard Disks\" + $element.Name))
                $log_string = $log_string + $TestVHD + "`r`n"
                $MailBody = $Mailbody + $TestVHD + "`r`n"
            }
        }
        else # Вдруг VHD нет в этом каталоге О_О
        {
            $log_string = $log_string + "*.VHD для $VM не найдены.`r`n"
            $MailBody = $MailBody + "*.VHD для $VM не найдены.`r`n"
        }
    }
}
else # ВМ не найдена на сервере
{
    $log_string = $log_string + "Виртуальная машина $VM не найдена. Процесс завершён.`r`n"
    $MailBody = $MailBody + "Виртуальная машина $VM не найдена. Процесс завершён.`r`n"
}

$log_string = $log_string + (Get-Date -Format "(yyyy-MM-dd) hh:mm:ss") + " Работа над $VM окончена.`r`n"
$log_string = $log_string + "======================== `r`n"

$MailSubject = "$VM export report."
# Отправляем письмо:
SendEmail -Subject $MailSubject -Body $MailBody  -Priority $MailPriority

#net use R: /delete /y Данную функцию запускать не обязательно.

Вот, собственно, весь скрипт. Для работы с кластером - создаем для каждой ВМ задание на каждой Ноде кластера с минутным отличием в запуске, потому как скрипт с любой ноды обращается к одному и тому же лог-файлу и в одно и то же время может быть коллизия по доступу. Если на одной из нод кластера ВМ не будет, скрипт выдаст сообщение, что ВМ не найдена и завершит работу.
Скрипт внедрён уже на нескольких предприятиях с незначительным подпиливанием под конкретного заказчика. Все довольны, чего и Вам желаю.

Ваша оценка: Нет Средняя: 3.6 (7 votes)

Комментарии:


при установке модуля:

Import-Module : The specified module 'hyperv' was not loaded because no valid module file was found in
tory.
At line:4 char:22
+ import-module <<<< hyperv
+ CategoryInfo : ResourceUnavailable: (hyperv:String) [Import-Module], FileNotFoundExcepti
+ FullyQualifiedErrorId : Modules_ModuleNotFound,Microsoft.PowerShell.Commands.ImportModuleCommand



Доброго дня, извини за тупой вопрос: этот скрипт выполняется в powerShell ?