Бэкап Hyper-V через PowerShell » История » Версия 1
Redmine Admin, 25.03.2026 10:44
| 1 | 1 | Redmine Admin | <div class="hyperv-backup-guide"> |
|---|---|---|---|
| 2 | <h2>💾 Автоматический бэкап виртуальных машин Hyper-V</h2> |
||
| 3 | <p><strong>Описание:</strong> Скрипт PowerShell для еженедельного экспорта ВМ Hyper-V с ротацией архивов и автоматической настройкой через Планировщик заданий.</p> |
||
| 4 | |||
| 5 | <div class="overview-box"> |
||
| 6 | <h3>📋 Компоненты решения</h3> |
||
| 7 | <table class="info-table"> |
||
| 8 | <thead> |
||
| 9 | <tr> |
||
| 10 | <th>Компонент</th> |
||
| 11 | <th>Функция</th> |
||
| 12 | <th>Путь</th> |
||
| 13 | </tr> |
||
| 14 | </thead> |
||
| 15 | <tbody> |
||
| 16 | <tr> |
||
| 17 | <td><code>Backup-HyperV.ps1</code></td> |
||
| 18 | <td>Экспорт ВМ, ротация бэкапов, логирование</td> |
||
| 19 | <td><code>C:\Scripts\</code></td> |
||
| 20 | </tr> |
||
| 21 | <tr> |
||
| 22 | <td><code>Setup-BackupTask.ps1</code></td> |
||
| 23 | <td>Автоматическая регистрация задачи в Планировщике</td> |
||
| 24 | <td><code>C:\Scripts\</code></td> |
||
| 25 | </tr> |
||
| 26 | <tr> |
||
| 27 | <td>Планировщик заданий</td> |
||
| 28 | <td>Запуск каждую неделю в воскресенье в 03:00</td> |
||
| 29 | <td><code>taskschd.msc</code></td> |
||
| 30 | </tr> |
||
| 31 | </tbody> |
||
| 32 | </table> |
||
| 33 | </div> |
||
| 34 | |||
| 35 | <hr> |
||
| 36 | |||
| 37 | <h2>🔧 Часть 1: Основной скрипт бэкапа</h2> |
||
| 38 | |||
| 39 | <h3>1.1 Создайте файл</h3> |
||
| 40 | <pre class="powershell"><code># Путь для сохранения |
||
| 41 | C:\Scripts\Backup-HyperV.ps1</code></pre> |
||
| 42 | |||
| 43 | <h3>1.2 Содержимое <code>Backup-HyperV.ps1</code></h3> |
||
| 44 | <pre class="powershell"><code># ================= НАСТРОЙКИ ================= |
||
| 45 | $BackupRootPath = "D:\HyperVBackups" # Путь, где будут храниться бэкапы |
||
| 46 | $LogFilePath = "C:\Scripts\BackupLog.txt" # Путь к файлу лога |
||
| 47 | $RetentionDays = 180 # Сколько дней хранить бэкапы |
||
| 48 | # ============================================= |
||
| 49 | |||
| 50 | # Функция для записи в лог |
||
| 51 | function Write-Log { |
||
| 52 | param ([string]$Message) |
||
| 53 | $TimeStamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" |
||
| 54 | $LogEntry = "[$TimeStamp] $Message" |
||
| 55 | Add-Content -Path $LogFilePath -Value $LogEntry |
||
| 56 | Write-Host $LogEntry |
||
| 57 | } |
||
| 58 | |||
| 59 | # Проверка прав администратора |
||
| 60 | if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { |
||
| 61 | Write-Log "ОШИБКА: Скрипт должен быть запущен от имени администратора!" |
||
| 62 | exit 1 |
||
| 63 | } |
||
| 64 | |||
| 65 | # Проверка существования модуля Hyper-V |
||
| 66 | if (-not (Get-Module -ListAvailable -Name Hyper-V)) { |
||
| 67 | Write-Log "ОШИБКА: Модуль Hyper-V не найден. Убедитесь, что роль установлена." |
||
| 68 | exit 1 |
||
| 69 | } |
||
| 70 | |||
| 71 | # Проверка пути бэкапа |
||
| 72 | if (-not (Test-Path -Path $BackupRootPath)) { |
||
| 73 | try { |
||
| 74 | New-Item -ItemType Directory -Path $BackupRootPath -Force | Out-Null |
||
| 75 | Write-Log "Папка бэкапов создана: $BackupRootPath" |
||
| 76 | } catch { |
||
| 77 | Write-Log "ОШИБКА: Не удалось создать папку бэкапов. $_" |
||
| 78 | exit 1 |
||
| 79 | } |
||
| 80 | } |
||
| 81 | |||
| 82 | $DateStamp = Get-Date -Format "yyyy-MM-dd" |
||
| 83 | Write-Log "=== Начало задачи бэкапа ===" |
||
| 84 | Write-Log "Дата бэкапа: $DateStamp" |
||
| 85 | |||
| 86 | # Получение списка всех ВМ |
||
| 87 | $VMs = Get-VM |
||
| 88 | |||
| 89 | if ($VMs.Count -eq 0) { |
||
| 90 | Write-Log "Виртуальные машины не найдены." |
||
| 91 | } else { |
||
| 92 | foreach ($VM in $VMs) { |
||
| 93 | $VMName = $VM.Name |
||
| 94 | $ExportPath = "$BackupRootPath\$($VMName)_$DateStamp" |
||
| 95 | |||
| 96 | try { |
||
| 97 | Write-Log "Начало экспорта ВМ: $VMName" |
||
| 98 | Export-VM -Name $VMName -Path $ExportPath -ErrorAction Stop |
||
| 99 | Write-Log "Успешно: ВМ $VMName экспортирована в $ExportPath" |
||
| 100 | } |
||
| 101 | catch { |
||
| 102 | Write-Log "ОШИБКА экспорта ВМ $VMName : $_" |
||
| 103 | } |
||
| 104 | } |
||
| 105 | } |
||
| 106 | |||
| 107 | Write-Log "=== Начало очистки старых бэкапов (старше $RetentionDays дней) ===" |
||
| 108 | |||
| 109 | $BackupFolders = Get-ChildItem -Path $BackupRootPath -Directory |
||
| 110 | $DeletedCount = 0 |
||
| 111 | |||
| 112 | foreach ($Folder in $BackupFolders) { |
||
| 113 | if ($Folder.CreationTime -lt (Get-Date).AddDays(-$RetentionDays)) { |
||
| 114 | try { |
||
| 115 | Write-Log "Удаление старой папки: $($Folder.Name) (Дата: $($Folder.CreationTime))" |
||
| 116 | Remove-Item -Path $Folder.FullName -Recurse -Force |
||
| 117 | $DeletedCount++ |
||
| 118 | } |
||
| 119 | catch { |
||
| 120 | Write-Log "ОШИБКА удаления папки $($Folder.Name): $_" |
||
| 121 | } |
||
| 122 | } |
||
| 123 | } |
||
| 124 | |||
| 125 | Write-Log "Удалено папок: $DeletedCount" |
||
| 126 | Write-Log "=== Задача бэкапа завершена ===" |
||
| 127 | Write-Log "-------------------------------------------"</code></pre> |
||
| 128 | |||
| 129 | <h3>1.3 Разрешите выполнение скриптов</h3> |
||
| 130 | <pre class="powershell"><code># Разрешите выполнение скриптов (однократно) |
||
| 131 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser |
||
| 132 | |||
| 133 | # Тестовый запуск (от имени Администратора!) |
||
| 134 | C:\Scripts\Backup-HyperV.ps1</code></pre> |
||
| 135 | |||
| 136 | <div class="success-box"> |
||
| 137 | ✅ <strong>Проверка:</strong> Убедитесь, что папки появились в <code>D:\HyperVBackups\</code> и записи добавлены в <code>C:\Scripts\BackupLog.txt</code> |
||
| 138 | </div> |
||
| 139 | |||
| 140 | <hr> |
||
| 141 | |||
| 142 | <h2>⏰ Часть 2: Настройка Планировщика заданий</h2> |
||
| 143 | <p>Есть два способа: <strong>ручной</strong> (через GUI) или <strong>автоматический</strong> (через скрипт).</p> |
||
| 144 | |||
| 145 | <h3>📌 Вариант А: Ручная настройка (через GUI)</h3> |
||
| 146 | |||
| 147 | <ol> |
||
| 148 | <li>Откройте Планировщик заданий: <code>taskschd.msc</code></li> |
||
| 149 | <li>В меню справа выберите <strong>Создать задачу...</strong> (не "Создать простую задачу")</li> |
||
| 150 | </ol> |
||
| 151 | |||
| 152 | <table class="config-table"> |
||
| 153 | <thead> |
||
| 154 | <tr> |
||
| 155 | <th>Вкладка</th> |
||
| 156 | <th>Параметр</th> |
||
| 157 | <th>Значение</th> |
||
| 158 | </tr> |
||
| 159 | </thead> |
||
| 160 | <tbody> |
||
| 161 | <tr> |
||
| 162 | <td rowspan="4"><strong>Общие</strong></td> |
||
| 163 | <td>Имя задачи</td> |
||
| 164 | <td><code>Hyper-V Weekly Backup</code></td> |
||
| 165 | </tr> |
||
| 166 | <tr> |
||
| 167 | <td>Выполнять для</td> |
||
| 168 | <td>✅ Всех пользователей</td> |
||
| 169 | </tr> |
||
| 170 | <tr> |
||
| 171 | <td>Права</td> |
||
| 172 | <td>✅ Выполнять с наивысшими правами</td> |
||
| 173 | </tr> |
||
| 174 | <tr> |
||
| 175 | <td>ОС</td> |
||
| 176 | <td>Windows 10/Server 2016 и выше</td> |
||
| 177 | </tr> |
||
| 178 | <tr> |
||
| 179 | <td rowspan="4"><strong>Триггеры</strong></td> |
||
| 180 | <td>Начало задачи</td> |
||
| 181 | <td>По расписанию</td> |
||
| 182 | </tr> |
||
| 183 | <tr> |
||
| 184 | <td>Частота</td> |
||
| 185 | <td>Еженедельно</td> |
||
| 186 | </tr> |
||
| 187 | <tr> |
||
| 188 | <td>Дни недели</td> |
||
| 189 | <td>Воскресенье</td> |
||
| 190 | </tr> |
||
| 191 | <tr> |
||
| 192 | <td>Время</td> |
||
| 193 | <td>03:00:00</td> |
||
| 194 | </tr> |
||
| 195 | <tr> |
||
| 196 | <td rowspan="2"><strong>Действия</strong></td> |
||
| 197 | <td>Программа</td> |
||
| 198 | <td><code>powershell.exe</code></td> |
||
| 199 | </tr> |
||
| 200 | <tr> |
||
| 201 | <td>Аргументы</td> |
||
| 202 | <td><code>-ExecutionPolicy Bypass -WindowStyle Hidden -File "C:\Scripts\Backup-HyperV.ps1"</code></td> |
||
| 203 | </tr> |
||
| 204 | <tr> |
||
| 205 | <td rowspan="2"><strong>Условия</strong></td> |
||
| 206 | <td>Питание</td> |
||
| 207 | <td>❌ Снять "Запускать только от электросети"</td> |
||
| 208 | </tr> |
||
| 209 | <tr> |
||
| 210 | <td>Сеть</td> |
||
| 211 | <td>❌ Снять "Запускать только при входе пользователя"</td> |
||
| 212 | </tr> |
||
| 213 | </tbody> |
||
| 214 | </table> |
||
| 215 | |||
| 216 | <h3>🤖 Вариант Б: Автоматическая настройка (через скрипт)</h3> |
||
| 217 | |||
| 218 | <h4>Шаг 1: Создайте файл настройки</h4> |
||
| 219 | <pre class="powershell"><code># Путь для сохранения |
||
| 220 | C:\Scripts\Setup-BackupTask.ps1</code></pre> |
||
| 221 | |||
| 222 | <h4>Шаг 2: Вставьте код</h4> |
||
| 223 | <pre class="powershell"><code># ============================================================================== |
||
| 224 | # Скрипт создания задачи Планировщика для бэкапа Hyper-V |
||
| 225 | # Запускать СТРОГО от имени Администратора |
||
| 226 | # ============================================================================== |
||
| 227 | |||
| 228 | # --- НАСТРОЙКИ --- |
||
| 229 | $TaskName = "Hyper-V Weekly Backup" |
||
| 230 | $TaskPath = "\" |
||
| 231 | $ScriptPath = "C:\Scripts\Backup-HyperV.ps1" |
||
| 232 | $LogPath = "C:\Scripts\TaskCreation.log" |
||
| 233 | $RunAsUser = "SYSTEM" |
||
| 234 | # ----------------- |
||
| 235 | |||
| 236 | function Write-Log { |
||
| 237 | param ([string]$Message) |
||
| 238 | $TimeStamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" |
||
| 239 | $LogEntry = "[$TimeStamp] $Message" |
||
| 240 | Add-Content -Path $LogPath -Value $LogEntry |
||
| 241 | Write-Host $LogEntry -ForegroundColor Cyan |
||
| 242 | } |
||
| 243 | |||
| 244 | Write-Log "=== Начало настройки задачи планировщика ===" |
||
| 245 | |||
| 246 | # 1. Проверка прав администратора |
||
| 247 | if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { |
||
| 248 | Write-Log "ОШИБКА: Скрипт должен быть запущен от имени администратора!" |
||
| 249 | exit 1 |
||
| 250 | } |
||
| 251 | |||
| 252 | # 2. Проверка существования скрипта бэкапа |
||
| 253 | if (-not (Test-Path -Path $ScriptPath)) { |
||
| 254 | Write-Log "ОШИБКА: Скрипт бэкапа не найден по пути: $ScriptPath" |
||
| 255 | exit 1 |
||
| 256 | } |
||
| 257 | |||
| 258 | # 3. Проверка модуля Планировщика |
||
| 259 | try { |
||
| 260 | $ScheduleService = New-Object -ComObject "Schedule.Service" |
||
| 261 | $ScheduleService.Connect() |
||
| 262 | Write-Log "Подключение к службе Планировщика заданий успешно." |
||
| 263 | } catch { |
||
| 264 | Write-Log "ОШИБКА: Не удалось подключиться к службе Планировщика. $_" |
||
| 265 | exit 1 |
||
| 266 | } |
||
| 267 | |||
| 268 | # 4. Удаление старой задачи (если существует) |
||
| 269 | $RootFolder = $ScheduleService.GetFolder($TaskPath) |
||
| 270 | try { |
||
| 271 | $OldTask = $RootFolder.GetTask($TaskName) |
||
| 272 | if ($OldTask) { |
||
| 273 | Write-Log "Обнаружена существующая задача '$TaskName'. Удаление..." |
||
| 274 | $RootFolder.DeleteTask($TaskName, 0) |
||
| 275 | Start-Sleep -Seconds 1 |
||
| 276 | } |
||
| 277 | } catch { } |
||
| 278 | |||
| 279 | # 5. Создание новой задачи |
||
| 280 | Write-Log "Создание новой задачи '$TaskName'..." |
||
| 281 | $TaskDefinition = $ScheduleService.NewTask(0) |
||
| 282 | |||
| 283 | # --- Principals --- |
||
| 284 | $TaskDefinition.Principal.UserId = $RunAsUser |
||
| 285 | $TaskDefinition.Principal.RunLevel = 1 |
||
| 286 | $TaskDefinition.Principal.LogonType = 3 |
||
| 287 | Write-Log "Настроено: Запуск от $RunAsUser с наивысшими правами." |
||
| 288 | |||
| 289 | # --- Triggers --- |
||
| 290 | $Trigger = $TaskDefinition.Triggers.Create(1) |
||
| 291 | $Trigger.StartBoundary = (Get-Date).Date.AddHours(3).ToString("yyyy-MM-ddTHH:mm:ss") |
||
| 292 | $Trigger.DaysOfWeek = 64 |
||
| 293 | $Trigger.WeeksInterval = 1 |
||
| 294 | $Trigger.Enabled = $true |
||
| 295 | Write-Log "Настроено: Запуск каждое Воскресенье в 03:00." |
||
| 296 | |||
| 297 | # --- Actions --- |
||
| 298 | $Action = $TaskDefinition.Actions.Create(0) |
||
| 299 | $Action.Path = "powershell.exe" |
||
| 300 | $Action.Arguments = "-ExecutionPolicy Bypass -WindowStyle Hidden -File `"$ScriptPath`"" |
||
| 301 | $Action.WorkingDirectory = "C:\Scripts" |
||
| 302 | Write-Log "Настроено: Запуск PowerShell скрипта $ScriptPath" |
||
| 303 | |||
| 304 | # --- Settings --- |
||
| 305 | $TaskDefinition.Settings.AllowHardTerminate = $true |
||
| 306 | $TaskDefinition.Settings.StartWhenAvailable = $true |
||
| 307 | $TaskDefinition.Settings.StopIfGoingOnBatteries = $false |
||
| 308 | $TaskDefinition.Settings.IdleSettings.StopOnIdleEnd = $false |
||
| 309 | $TaskDefinition.Settings.ExecutionTimeLimit = "PT12H" |
||
| 310 | Write-Log "Настроено: Лимит времени 12ч, работа от ИБП разрешена." |
||
| 311 | |||
| 312 | # --- Регистрация --- |
||
| 313 | try { |
||
| 314 | $RootFolder.RegisterTaskDefinition($TaskName, $TaskDefinition, 6, $RunAsUser, $null, 3) |
||
| 315 | Write-Log "УСПЕШНО: Задача '$TaskName' создана и зарегистрирована." |
||
| 316 | } catch { |
||
| 317 | Write-Log "ОШИБКА при регистрации задачи: $_" |
||
| 318 | exit 1 |
||
| 319 | } |
||
| 320 | |||
| 321 | # 6. Тестовый запуск |
||
| 322 | Write-Log "Проверка статуса задачи..." |
||
| 323 | try { |
||
| 324 | $CreatedTask = $RootFolder.GetTask($TaskName) |
||
| 325 | Write-Log "Статус задачи: $($CreatedTask.State)" |
||
| 326 | |||
| 327 | Write-Host "`nХотите запустить тестовое выполнение задачи? (Y/N)" -ForegroundColor Yellow |
||
| 328 | $response = Read-Host |
||
| 329 | |||
| 330 | if ($response -eq 'Y' -or $response -eq 'y') { |
||
| 331 | Write-Log "Запуск задачи на выполнение..." |
||
| 332 | $CreatedTask.Run("") |
||
| 333 | Write-Log "Задача запущена. Проверьте логи бэкапа через несколько минут." |
||
| 334 | } |
||
| 335 | } catch { |
||
| 336 | Write-Log "Не удалось получить доступ к созданной задаче: $_" |
||
| 337 | } |
||
| 338 | |||
| 339 | Write-Log "=== Настройка завершена ==="</code></pre> |
||
| 340 | |||
| 341 | <h4>Шаг 3: Запустите скрипт настройки</h4> |
||
| 342 | <pre class="powershell"><code># Откройте PowerShell от имени администратора |
||
| 343 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser |
||
| 344 | C:\Scripts\Setup-BackupTask.ps1</code></pre> |
||
| 345 | |||
| 346 | <hr> |
||
| 347 | |||
| 348 | <h2>⚠️ Часть 3: Важные замечания и рекомендации</h2> |
||
| 349 | |||
| 350 | <div class="warning-box"> |
||
| 351 | <h3>💿 Дисковое пространство</h3> |
||
| 352 | <p>Бэкап через <code>Export-VM</code> создает полные копии дисков (VHDX).</p> |
||
| 353 | <p><strong>Формула расчёта:</strong> <code>Размер_ВМ × Количество_ВМ × (180 дней / 7 дней)</code></p> |
||
| 354 | <p><strong>Пример:</strong> 5 ВМ по 100 ГБ × 26 недель = <strong>13 ТБ</strong> за 180 дней</p> |
||
| 355 | <p>✅ Убедитесь, что на диске <code>D:</code> достаточно места. При ограничении рассмотрите:</p> |
||
| 356 | <ul> |
||
| 357 | <li>Сокращение срока хранения (<code>$RetentionDays</code>)</li> |
||
| 358 | <li>Использование дедупликации</li> |
||
| 359 | <li>Выгрузку старых бэкапов в облако</li> |
||
| 360 | </ul> |
||
| 361 | </div> |
||
| 362 | |||
| 363 | <div class="info-box"> |
||
| 364 | <h3>⚡ Производительность</h3> |
||
| 365 | <ul> |
||
| 366 | <li><code>Export-VM</code> создает временный чекпоинт на работающей ВМ для целостности данных</li> |
||
| 367 | <li>Это может немного замедлить ВМ во время бэкапа</li> |
||
| 368 | <li>Время бэкапа зависит от скорости диска и объема данных</li> |
||
| 369 | <li>Задания не накладываются (запуск раз в неделю)</li> |
||
| 370 | </ul> |
||
| 371 | </div> |
||
| 372 | |||
| 373 | <div class="tip-box"> |
||
| 374 | <h3>🧪 Тестирование</h3> |
||
| 375 | <ul> |
||
| 376 | <li>Не ждите воскресенья — запустите скрипт вручную один раз</li> |
||
| 377 | <li>Проверьте, что ВМ экспортируются и лог пишется</li> |
||
| 378 | <li>Для проверки удаления: временно установите <code>$RetentionDays = 0</code>, затем верните <code>180</code></li> |
||
| 379 | </ul> |
||
| 380 | </div> |
||
| 381 | |||
| 382 | <div class="success-box"> |
||
| 383 | <h3>🔄 Восстановление</h3> |
||
| 384 | <pre class="powershell"><code># Через PowerShell |
||
| 385 | Import-VM -Path "D:\HyperVBackups\VMName_2023-10-29\VMName\Virtual Machines\GUID.xml" |
||
| 386 | |||
| 387 | # Или через диспетчер Hyper-V → Импорт виртуальной машины</code></pre> |
||
| 388 | </div> |
||
| 389 | |||
| 390 | <div class="info-box"> |
||
| 391 | <h3>📧 Уведомления</h3> |
||
| 392 | <p>В данном скрипте уведомления только в лог-файл. Для email-уведомлений добавьте в скрипт:</p> |
||
| 393 | <pre class="powershell"><code>Send-MailMessage -From "backup@domain.com" -To "admin@domain.com" ` |
||
| 394 | -Subject "Hyper-V Backup Status" -Body $LogContent -SmtpServer "smtp.domain.com"</code></pre> |
||
| 395 | </div> |
||
| 396 | |||
| 397 | <hr> |
||
| 398 | |||
| 399 | <h2>✅ Часть 4: Проверка результата</h2> |
||
| 400 | |||
| 401 | <ol> |
||
| 402 | <li>Откройте Планировщик заданий: <code>taskschd.msc</code></li> |
||
| 403 | <li>Найдите задачу <strong>Hyper-V Weekly Backup</strong></li> |
||
| 404 | <li>ПКМ → <strong>Свойства</strong></li> |
||
| 405 | <li>Проверьте вкладку <strong>Общие</strong>: ✅ "Выполнять с наивысшими правами"</li> |
||
| 406 | <li>Включите вкладку <strong>Журнал</strong> (если отключена) для истории запусков</li> |
||
| 407 | </ol> |
||
| 408 | |||
| 409 | <h3>📊 Полезные команды для проверки</h3> |
||
| 410 | <pre class="powershell"><code># Просмотр логов бэкапа |
||
| 411 | Get-Content C:\Scripts\BackupLog.txt -Tail 50 |
||
| 412 | |||
| 413 | # Просмотр логов создания задачи |
||
| 414 | Get-Content C:\Scripts\TaskCreation.log -Tail 50 |
||
| 415 | |||
| 416 | # Проверка статуса задачи |
||
| 417 | Get-ScheduledTask -TaskName "Hyper-V Weekly Backup" |
||
| 418 | |||
| 419 | # Запуск задачи вручную |
||
| 420 | Start-ScheduledTask -TaskName "Hyper-V Weekly Backup" |
||
| 421 | |||
| 422 | # Просмотр истории выполнения |
||
| 423 | Get-ScheduledTaskInfo -TaskName "Hyper-V Weekly Backup" |
||
| 424 | |||
| 425 | # Проверка папок бэкапа |
||
| 426 | Get-ChildItem D:\HyperVBackups -Directory | Sort-Object CreationTime -Descending | Select-Object -First 10</code></pre> |
||
| 427 | |||
| 428 | <h3>📁 Структура после настройки</h3> |
||
| 429 | <pre><code>C:\Scripts\ |
||
| 430 | ├── Backup-HyperV.ps1 # Основной скрипт бэкапа |
||
| 431 | ├── Setup-BackupTask.ps1 # Скрипт настройки задачи |
||
| 432 | ├── BackupLog.txt # Лог выполнения бэкапов |
||
| 433 | └── TaskCreation.log # Лог создания задачи |
||
| 434 | |||
| 435 | D:\HyperVBackups\ |
||
| 436 | ├── VM1_2026-06-15\ |
||
| 437 | │ └── VM1\ |
||
| 438 | │ ├── Virtual Machines\ |
||
| 439 | │ └── ... |
||
| 440 | ├── VM2_2026-06-15\ |
||
| 441 | └── ...</code></pre> |
||
| 442 | |||
| 443 | <div class="warning-box"> |
||
| 444 | <h3>🔐 Требования безопасности</h3> |
||
| 445 | <ul> |
||
| 446 | <li>Оба скрипта требуют запуска <strong>от имени Администратора</strong></li> |
||
| 447 | <li>Задача выполняется от <strong>SYSTEM</strong> (наиболее надежно для серверов)</li> |
||
| 448 | <li>Тип входа <strong>S4U</strong> — задача работает без входа пользователя</li> |
||
| 449 | <li>Храните скрипты в защищенной папке с ограниченным доступом</li> |
||
| 450 | </ul> |
||
| 451 | </div> |
||
| 452 | </div> |
||
| 453 | |||
| 454 | <style> |
||
| 455 | .hyperv-backup-guide { |
||
| 456 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; |
||
| 457 | line-height: 1.6; |
||
| 458 | color: #2c3e50; |
||
| 459 | max-width: 100%; |
||
| 460 | font-size: 14px; |
||
| 461 | } |
||
| 462 | .hyperv-backup-guide h2 { |
||
| 463 | color: #1a5276; |
||
| 464 | border-bottom: 3px solid #3498db; |
||
| 465 | padding-bottom: 0.6em; |
||
| 466 | margin: 1.5em 0 1em; |
||
| 467 | font-size: 1.4em; |
||
| 468 | } |
||
| 469 | .hyperv-backup-guide h3 { |
||
| 470 | color: #2874a6; |
||
| 471 | margin: 1.3em 0 0.7em; |
||
| 472 | background: #ebf5fb; |
||
| 473 | padding: 0.6em 0.9em; |
||
| 474 | border-left: 4px solid #3498db; |
||
| 475 | border-radius: 0 4px 4px 0; |
||
| 476 | font-size: 1.15em; |
||
| 477 | } |
||
| 478 | .hyperv-backup-guide h4 { |
||
| 479 | color: #34495e; |
||
| 480 | margin: 1em 0 0.5em; |
||
| 481 | font-size: 1.05em; |
||
| 482 | } |
||
| 483 | .hyperv-backup-guide p { margin: 0.6em 0; } |
||
| 484 | .hyperv-backup-guide hr { |
||
| 485 | border: none; |
||
| 486 | border-top: 2px solid #ecf0f1; |
||
| 487 | margin: 1.5em 0; |
||
| 488 | } |
||
| 489 | .hyperv-backup-guide pre { |
||
| 490 | background: #1e1e1e; |
||
| 491 | color: #d4d4d4; |
||
| 492 | padding: 1em; |
||
| 493 | border-radius: 5px; |
||
| 494 | overflow-x: auto; |
||
| 495 | font-size: 0.85em; |
||
| 496 | margin: 0.7em 0; |
||
| 497 | border: 1px solid #3a3a3a; |
||
| 498 | } |
||
| 499 | .hyperv-backup-guide code { |
||
| 500 | font-family: "Consolas", "Monaco", "Courier New", monospace; |
||
| 501 | background: #f4f6f7; |
||
| 502 | padding: 0.2em 0.4em; |
||
| 503 | border-radius: 3px; |
||
| 504 | font-size: 0.9em; |
||
| 505 | color: #e74c3c; |
||
| 506 | } |
||
| 507 | .hyperv-backup-guide pre code { |
||
| 508 | background: none; |
||
| 509 | padding: 0; |
||
| 510 | color: #d4d4d4; |
||
| 511 | } |
||
| 512 | .hyperv-backup-guide pre.powershell::before { |
||
| 513 | content: "🪟 PowerShell"; |
||
| 514 | display: block; |
||
| 515 | background: #5c2d91; |
||
| 516 | color: #fff; |
||
| 517 | padding: 0.25em 0.6em; |
||
| 518 | margin: -1em -1em 0.8em -1em; |
||
| 519 | font-size: 0.75em; |
||
| 520 | border-radius: 4px 4px 0 0; |
||
| 521 | font-weight: 600; |
||
| 522 | } |
||
| 523 | .hyperv-backup-guide table { |
||
| 524 | width: 100%; |
||
| 525 | border-collapse: collapse; |
||
| 526 | margin: 0.8em 0; |
||
| 527 | font-size: 0.9em; |
||
| 528 | } |
||
| 529 | .hyperv-backup-guide th, |
||
| 530 | .hyperv-backup-guide td { |
||
| 531 | border: 1px solid #bdc3c7; |
||
| 532 | padding: 0.6em 0.8em; |
||
| 533 | text-align: left; |
||
| 534 | } |
||
| 535 | .hyperv-backup-guide th { |
||
| 536 | background: #3498db; |
||
| 537 | color: #fff; |
||
| 538 | font-weight: 600; |
||
| 539 | } |
||
| 540 | .hyperv-backup-guide tr:nth-child(even) { |
||
| 541 | background: #f8f9fa; |
||
| 542 | } |
||
| 543 | .hyperv-backup-guide ul, .hyperv-backup-guide ol { |
||
| 544 | padding-left: 1.6em; |
||
| 545 | margin: 0.5em 0; |
||
| 546 | } |
||
| 547 | .hyperv-backup-guide li { margin: 0.35em 0; } |
||
| 548 | .hyperv-backup-guide .overview-box, |
||
| 549 | .hyperv-backup-guide .info-box, |
||
| 550 | .hyperv-backup-guide .warning-box, |
||
| 551 | .hyperv-backup-guide .tip-box, |
||
| 552 | .hyperv-backup-guide .success-box { |
||
| 553 | padding: 1em 1.2em; |
||
| 554 | margin: 1em 0; |
||
| 555 | border-radius: 0 5px 5px 0; |
||
| 556 | border-left: 4px solid; |
||
| 557 | } |
||
| 558 | .hyperv-backup-guide .overview-box { |
||
| 559 | background: #e8f4fd; |
||
| 560 | border-color: #3498db; |
||
| 561 | } |
||
| 562 | .hyperv-backup-guide .info-box { |
||
| 563 | background: #f0f7ff; |
||
| 564 | border-color: #17a2b8; |
||
| 565 | } |
||
| 566 | .hyperv-backup-guide .warning-box { |
||
| 567 | background: #fff3cd; |
||
| 568 | border-color: #f39c12; |
||
| 569 | } |
||
| 570 | .hyperv-backup-guide .tip-box { |
||
| 571 | background: #e8f8f5; |
||
| 572 | border-color: #27ae60; |
||
| 573 | } |
||
| 574 | .hyperv-backup-guide .success-box { |
||
| 575 | background: #e6f7ee; |
||
| 576 | border-color: #27ae60; |
||
| 577 | } |
||
| 578 | .hyperv-backup-guide .config-table td:nth-child(1) { |
||
| 579 | font-weight: 600; |
||
| 580 | background: #f8f9fa; |
||
| 581 | } |
||
| 582 | @media (max-width: 768px) { |
||
| 583 | .hyperv-backup-guide { font-size: 13px; } |
||
| 584 | .hyperv-backup-guide pre { font-size: 0.8em; } |
||
| 585 | .hyperv-backup-guide table { font-size: 0.85em; } |
||
| 586 | } |
||
| 587 | </style> |