Проект

Общее

Профиль

Действия

💾 Автоматический бэкап виртуальных машин Hyper-V

Описание: Скрипт PowerShell для еженедельного экспорта ВМ Hyper-V с ротацией архивов и автоматической настройкой через Планировщик заданий.

📋 Компоненты решения

Компонент Функция Путь
Backup-HyperV.ps1 Экспорт ВМ, ротация бэкапов, логирование C:\Scripts\
Setup-BackupTask.ps1 Автоматическая регистрация задачи в Планировщике C:\Scripts\
Планировщик заданий Запуск каждую неделю в воскресенье в 03:00 taskschd.msc

🔧 Часть 1: Основной скрипт бэкапа

1.1 Создайте файл

# Путь для сохранения
C:\Scripts\Backup-HyperV.ps1

1.2 Содержимое Backup-HyperV.ps1

# ================= НАСТРОЙКИ =================
$BackupRootPath = "D:\HyperVBackups"       # Путь, где будут храниться бэкапы
$LogFilePath    = "C:\Scripts\BackupLog.txt" # Путь к файлу лога
$RetentionDays  = 180                      # Сколько дней хранить бэкапы
# =============================================

Функция для записи в лог

function Write-Log {
param ([string]$Message)
$TimeStamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$LogEntry = "[$TimeStamp] $Message"
Add-Content -Path $LogFilePath -Value $LogEntry
Write-Host $LogEntry
}

Проверка прав администратора

if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
Write-Log "ОШИБКА: Скрипт должен быть запущен от имени администратора!"
exit 1
}

Проверка существования модуля Hyper-V

if (-not (Get-Module -ListAvailable -Name Hyper-V)) {
Write-Log "ОШИБКА: Модуль Hyper-V не найден. Убедитесь, что роль установлена."
exit 1
}

Проверка пути бэкапа

if (-not (Test-Path -Path $BackupRootPath)) {
try {
New-Item -ItemType Directory -Path $BackupRootPath -Force | Out-Null
Write-Log "Папка бэкапов создана: $BackupRootPath"
} catch {
Write-Log "ОШИБКА: Не удалось создать папку бэкапов. $_"
exit 1
}
}

$DateStamp = Get-Date -Format "yyyy-MM-dd"
Write-Log "=== Начало задачи бэкапа ==="
Write-Log "Дата бэкапа: $DateStamp"

Получение списка всех ВМ

$VMs = Get-VM

if ($VMs.Count -eq 0) {
Write-Log "Виртуальные машины не найдены."
} else {
foreach ($VM in $VMs) {
$VMName = $VM.Name
$ExportPath = "$BackupRootPath$($VMName)_$DateStamp"

    try {
        Write-Log "Начало экспорта ВМ: $VMName"
        Export-VM -Name $VMName -Path $ExportPath -ErrorAction Stop
        Write-Log "Успешно: ВМ $VMName экспортирована в $ExportPath"
    }
    catch {
        Write-Log "ОШИБКА экспорта ВМ $VMName : $_"
    }
}

}

Write-Log "=== Начало очистки старых бэкапов (старше $RetentionDays дней) ==="

$BackupFolders = Get-ChildItem -Path $BackupRootPath -Directory
$DeletedCount = 0

foreach ($Folder in $BackupFolders) {
if ($Folder.CreationTime -lt (Get-Date).AddDays(-$RetentionDays)) {
try {
Write-Log "Удаление старой папки: $($Folder.Name) (Дата: $($Folder.CreationTime))"
Remove-Item -Path $Folder.FullName -Recurse -Force
$DeletedCount++
}
catch {
Write-Log "ОШИБКА удаления папки $($Folder.Name): $_"
}
}
}

Write-Log "Удалено папок: $DeletedCount"
Write-Log "=== Задача бэкапа завершена ==="
Write-Log "-------------------------------------------"

1.3 Разрешите выполнение скриптов

# Разрешите выполнение скриптов (однократно)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

Тестовый запуск (от имени Администратора!)

C:\Scripts\Backup-HyperV.ps1

Проверка: Убедитесь, что папки появились в D:\HyperVBackups\ и записи добавлены в C:\Scripts\BackupLog.txt

⏰ Часть 2: Настройка Планировщика заданий

Есть два способа: ручной (через GUI) или автоматический (через скрипт).

📌 Вариант А: Ручная настройка (через GUI)

  1. Откройте Планировщик заданий: taskschd.msc
  2. В меню справа выберите Создать задачу... (не "Создать простую задачу")
Вкладка Параметр Значение
Общие Имя задачи Hyper-V Weekly Backup
Выполнять для ✅ Всех пользователей
Права ✅ Выполнять с наивысшими правами
ОС Windows 10/Server 2016 и выше
Триггеры Начало задачи По расписанию
Частота Еженедельно
Дни недели Воскресенье
Время 03:00:00
Действия Программа powershell.exe
Аргументы -ExecutionPolicy Bypass -WindowStyle Hidden -File "C:\Scripts\Backup-HyperV.ps1"
Условия Питание ❌ Снять "Запускать только от электросети"
Сеть ❌ Снять "Запускать только при входе пользователя"

🤖 Вариант Б: Автоматическая настройка (через скрипт)

Шаг 1: Создайте файл настройки

# Путь для сохранения
C:\Scripts\Setup-BackupTask.ps1

Шаг 2: Вставьте код

# ==============================================================================
# Скрипт создания задачи Планировщика для бэкапа Hyper-V
# Запускать СТРОГО от имени Администратора
# ==============================================================================

--- НАСТРОЙКИ ---

$TaskName = "Hyper-V Weekly Backup"
$TaskPath = ""
$ScriptPath = "C:\Scripts\Backup-HyperV.ps1"
$LogPath = "C:\Scripts\TaskCreation.log"
$RunAsUser = "SYSTEM"

-----------------

function Write-Log {
param ([string]$Message)
$TimeStamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$LogEntry = "[$TimeStamp] $Message"
Add-Content -Path $LogPath -Value $LogEntry
Write-Host $LogEntry -ForegroundColor Cyan
}

Write-Log "=== Начало настройки задачи планировщика ==="

1. Проверка прав администратора

if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
Write-Log "ОШИБКА: Скрипт должен быть запущен от имени администратора!"
exit 1
}

2. Проверка существования скрипта бэкапа

if (-not (Test-Path -Path $ScriptPath)) {
Write-Log "ОШИБКА: Скрипт бэкапа не найден по пути: $ScriptPath"
exit 1
}

3. Проверка модуля Планировщика

try {
$ScheduleService = New-Object -ComObject "Schedule.Service"
$ScheduleService.Connect()
Write-Log "Подключение к службе Планировщика заданий успешно."
} catch {
Write-Log "ОШИБКА: Не удалось подключиться к службе Планировщика. $_"
exit 1
}

4. Удаление старой задачи (если существует)

$RootFolder = $ScheduleService.GetFolder($TaskPath)
try {
$OldTask = $RootFolder.GetTask($TaskName)
if ($OldTask) {
Write-Log "Обнаружена существующая задача '$TaskName'. Удаление..."
$RootFolder.DeleteTask($TaskName, 0)
Start-Sleep -Seconds 1
}
} catch { }

5. Создание новой задачи

Write-Log "Создание новой задачи '$TaskName'..."
$TaskDefinition = $ScheduleService.NewTask(0)

--- Principals ---

$TaskDefinition.Principal.UserId = $RunAsUser
$TaskDefinition.Principal.RunLevel = 1
$TaskDefinition.Principal.LogonType = 3
Write-Log "Настроено: Запуск от $RunAsUser с наивысшими правами."

--- Triggers ---

$Trigger = $TaskDefinition.Triggers.Create(1)
$Trigger.StartBoundary = (Get-Date).Date.AddHours(3).ToString("yyyy-MM-ddTHH:mm:ss")
$Trigger.DaysOfWeek = 64
$Trigger.WeeksInterval = 1
$Trigger.Enabled = $true
Write-Log "Настроено: Запуск каждое Воскресенье в 03:00."

--- Actions ---

$Action = $TaskDefinition.Actions.Create(0)
$Action.Path = "powershell.exe"
$Action.Arguments = "-ExecutionPolicy Bypass -WindowStyle Hidden -File "$ScriptPath""
$Action.WorkingDirectory = "C:\Scripts"
Write-Log "Настроено: Запуск PowerShell скрипта $ScriptPath"

--- Settings ---

$TaskDefinition.Settings.AllowHardTerminate = $true
$TaskDefinition.Settings.StartWhenAvailable = $true
$TaskDefinition.Settings.StopIfGoingOnBatteries = $false
$TaskDefinition.Settings.IdleSettings.StopOnIdleEnd = $false
$TaskDefinition.Settings.ExecutionTimeLimit = "PT12H"
Write-Log "Настроено: Лимит времени 12ч, работа от ИБП разрешена."

--- Регистрация ---

try {
$RootFolder.RegisterTaskDefinition($TaskName, $TaskDefinition, 6, $RunAsUser, $null, 3)
Write-Log "УСПЕШНО: Задача '$TaskName' создана и зарегистрирована."
} catch {
Write-Log "ОШИБКА при регистрации задачи: $_"
exit 1
}

6. Тестовый запуск

Write-Log "Проверка статуса задачи..."
try {
$CreatedTask = $RootFolder.GetTask($TaskName)
Write-Log "Статус задачи: $($CreatedTask.State)"

Write-Host "`nХотите запустить тестовое выполнение задачи? (Y/N)" -ForegroundColor Yellow
$response = Read-Host

if ($response -eq 'Y' -or $response -eq 'y') {
    Write-Log "Запуск задачи на выполнение..."
    $CreatedTask.Run("")
    Write-Log "Задача запущена. Проверьте логи бэкапа через несколько минут."
}

} catch {
Write-Log "Не удалось получить доступ к созданной задаче: $_"
}

Write-Log "=== Настройка завершена ==="

Шаг 3: Запустите скрипт настройки

# Откройте PowerShell от имени администратора
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
C:\Scripts\Setup-BackupTask.ps1

⚠️ Часть 3: Важные замечания и рекомендации

💿 Дисковое пространство

Бэкап через Export-VM создает полные копии дисков (VHDX).

Формула расчёта: Размер_ВМ × Количество_ВМ × (180 дней / 7 дней)

Пример: 5 ВМ по 100 ГБ × 26 недель = 13 ТБ за 180 дней

✅ Убедитесь, что на диске D: достаточно места. При ограничении рассмотрите:

  • Сокращение срока хранения ($RetentionDays)
  • Использование дедупликации
  • Выгрузку старых бэкапов в облако

⚡ Производительность

  • Export-VM создает временный чекпоинт на работающей ВМ для целостности данных
  • Это может немного замедлить ВМ во время бэкапа
  • Время бэкапа зависит от скорости диска и объема данных
  • Задания не накладываются (запуск раз в неделю)

🧪 Тестирование

  • Не ждите воскресенья — запустите скрипт вручную один раз
  • Проверьте, что ВМ экспортируются и лог пишется
  • Для проверки удаления: временно установите $RetentionDays = 0, затем верните 180

🔄 Восстановление

# Через PowerShell
Import-VM -Path "D:\HyperVBackups\VMName_2023-10-29\VMName\Virtual Machines\GUID.xml"

Или через диспетчер Hyper-V → Импорт виртуальной машины

📧 Уведомления

В данном скрипте уведомления только в лог-файл. Для email-уведомлений добавьте в скрипт:

Send-MailMessage -From "backup@domain.com" -To "admin@domain.com" `
    -Subject "Hyper-V Backup Status" -Body $LogContent -SmtpServer "smtp.domain.com"

✅ Часть 4: Проверка результата

  1. Откройте Планировщик заданий: taskschd.msc
  2. Найдите задачу Hyper-V Weekly Backup
  3. ПКМ → Свойства
  4. Проверьте вкладку Общие: ✅ "Выполнять с наивысшими правами"
  5. Включите вкладку Журнал (если отключена) для истории запусков

📊 Полезные команды для проверки

# Просмотр логов бэкапа
Get-Content C:\Scripts\BackupLog.txt -Tail 50

Просмотр логов создания задачи

Get-Content C:\Scripts\TaskCreation.log -Tail 50

Проверка статуса задачи

Get-ScheduledTask -TaskName "Hyper-V Weekly Backup"

Запуск задачи вручную

Start-ScheduledTask -TaskName "Hyper-V Weekly Backup"

Просмотр истории выполнения

Get-ScheduledTaskInfo -TaskName "Hyper-V Weekly Backup"

Проверка папок бэкапа

Get-ChildItem D:\HyperVBackups -Directory | Sort-Object CreationTime -Descending | Select-Object -First 10

📁 Структура после настройки

C:\Scripts\
├── Backup-HyperV.ps1          # Основной скрипт бэкапа
├── Setup-BackupTask.ps1       # Скрипт настройки задачи
├── BackupLog.txt              # Лог выполнения бэкапов
└── TaskCreation.log           # Лог создания задачи

D:\HyperVBackups
├── VM1_2026-06-15
│ └── VM1
│ ├── Virtual Machines
│ └── ...
├── VM2_2026-06-15
└── ...

🔐 Требования безопасности

  • Оба скрипта требуют запуска от имени Администратора
  • Задача выполняется от SYSTEM (наиболее надежно для серверов)
  • Тип входа S4U — задача работает без входа пользователя
  • Храните скрипты в защищенной папке с ограниченным доступом
<style> .hyperv-backup-guide { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; line-height: 1.6; color: #2c3e50; max-width: 100%; font-size: 14px; } .hyperv-backup-guide h2 { color: #1a5276; border-bottom: 3px solid #3498db; padding-bottom: 0.6em; margin: 1.5em 0 1em; font-size: 1.4em; } .hyperv-backup-guide h3 { color: #2874a6; margin: 1.3em 0 0.7em; background: #ebf5fb; padding: 0.6em 0.9em; border-left: 4px solid #3498db; border-radius: 0 4px 4px 0; font-size: 1.15em; } .hyperv-backup-guide h4 { color: #34495e; margin: 1em 0 0.5em; font-size: 1.05em; } .hyperv-backup-guide p { margin: 0.6em 0; } .hyperv-backup-guide hr { border: none; border-top: 2px solid #ecf0f1; margin: 1.5em 0; } .hyperv-backup-guide pre { background: #1e1e1e; color: #d4d4d4; padding: 1em; border-radius: 5px; overflow-x: auto; font-size: 0.85em; margin: 0.7em 0; border: 1px solid #3a3a3a; } .hyperv-backup-guide code { font-family: "Consolas", "Monaco", "Courier New", monospace; background: #f4f6f7; padding: 0.2em 0.4em; border-radius: 3px; font-size: 0.9em; color: #e74c3c; } .hyperv-backup-guide pre code { background: none; padding: 0; color: #d4d4d4; } .hyperv-backup-guide pre.powershell::before { content: "🪟 PowerShell"; display: block; background: #5c2d91; color: #fff; padding: 0.25em 0.6em; margin: -1em -1em 0.8em -1em; font-size: 0.75em; border-radius: 4px 4px 0 0; font-weight: 600; } .hyperv-backup-guide table { width: 100%; border-collapse: collapse; margin: 0.8em 0; font-size: 0.9em; } .hyperv-backup-guide th, .hyperv-backup-guide td { border: 1px solid #bdc3c7; padding: 0.6em 0.8em; text-align: left; } .hyperv-backup-guide th { background: #3498db; color: #fff; font-weight: 600; } .hyperv-backup-guide tr:nth-child(even) { background: #f8f9fa; } .hyperv-backup-guide ul, .hyperv-backup-guide ol { padding-left: 1.6em; margin: 0.5em 0; } .hyperv-backup-guide li { margin: 0.35em 0; } .hyperv-backup-guide .overview-box, .hyperv-backup-guide .info-box, .hyperv-backup-guide .warning-box, .hyperv-backup-guide .tip-box, .hyperv-backup-guide .success-box { padding: 1em 1.2em; margin: 1em 0; border-radius: 0 5px 5px 0; border-left: 4px solid; } .hyperv-backup-guide .overview-box { background: #e8f4fd; border-color: #3498db; } .hyperv-backup-guide .info-box { background: #f0f7ff; border-color: #17a2b8; } .hyperv-backup-guide .warning-box { background: #fff3cd; border-color: #f39c12; } .hyperv-backup-guide .tip-box { background: #e8f8f5; border-color: #27ae60; } .hyperv-backup-guide .success-box { background: #e6f7ee; border-color: #27ae60; } .hyperv-backup-guide .config-table td:nth-child(1) { font-weight: 600; background: #f8f9fa; } @media (max-width: 768px) { .hyperv-backup-guide { font-size: 13px; } .hyperv-backup-guide pre { font-size: 0.8em; } .hyperv-backup-guide table { font-size: 0.85em; } } </style>

Обновлено Redmine Admin 5 месяца назад · 1 изменени(я, ий)