mirror of
https://github.com/MAKS-IT-COM/uscheduler.git
synced 2026-02-14 06:37:18 +01:00
(feature): hyper-v backup dryRun mode
This commit is contained in:
parent
5839f55999
commit
728f657112
@ -1,7 +1,7 @@
|
|||||||
# Hyper-V Backup Script
|
# Hyper-V Backup Script
|
||||||
|
|
||||||
**Version:** 1.0.1
|
**Version:** 1.0.2
|
||||||
**Last Updated:** 2026-01-26
|
**Last Updated:** 2026-01-28
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@ -16,6 +16,7 @@ Production-ready automated backup solution for Hyper-V virtual machines with sch
|
|||||||
- ✅ **Checkpoint Management** - Automatic cleanup of backup checkpoints (keeps last 2 for rollback)
|
- ✅ **Checkpoint Management** - Automatic cleanup of backup checkpoints (keeps last 2 for rollback)
|
||||||
- ✅ **Space Validation** - Dynamic space checks for temp (per VM) and destination before copy
|
- ✅ **Space Validation** - Dynamic space checks for temp (per VM) and destination before copy
|
||||||
- ✅ **VM Exclusion** - Exclude specific VMs from backup
|
- ✅ **VM Exclusion** - Exclude specific VMs from backup
|
||||||
|
- ✅ **Dry Run Mode** - Test backup detection without actual export/copy operations
|
||||||
- ✅ **Detailed Logging** - Comprehensive logging with timestamps and severity levels
|
- ✅ **Detailed Logging** - Comprehensive logging with timestamps and severity levels
|
||||||
- ✅ **Lock Files** - Prevents concurrent execution
|
- ✅ **Lock Files** - Prevents concurrent execution
|
||||||
- ✅ **Error Handling** - Proper exit codes and error reporting
|
- ✅ **Error Handling** - Proper exit codes and error reporting
|
||||||
@ -26,7 +27,7 @@ Production-ready automated backup solution for Hyper-V virtual machines with sch
|
|||||||
- Windows Server with Hyper-V role installed
|
- Windows Server with Hyper-V role installed
|
||||||
- PowerShell 5.1 or later
|
- PowerShell 5.1 or later
|
||||||
- Administrator privileges
|
- Administrator privileges
|
||||||
- Hyper-V PowerShell module
|
- Hyper-V PowerShell module (auto-installed if missing)
|
||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
- `SchedulerTemplate.psm1` module (located in parent directory)
|
- `SchedulerTemplate.psm1` module (located in parent directory)
|
||||||
@ -65,7 +66,10 @@ HyperV-Backup/
|
|||||||
"credentialEnvVar": "YOUR_ENV_VAR_NAME",
|
"credentialEnvVar": "YOUR_ENV_VAR_NAME",
|
||||||
"tempExportRoot": "D:\\Temp\\HyperVExport",
|
"tempExportRoot": "D:\\Temp\\HyperVExport",
|
||||||
"retentionCount": 3,
|
"retentionCount": 3,
|
||||||
"excludeVMs": ["vm-to-exclude"]
|
"excludeVMs": ["vm-to-exclude"],
|
||||||
|
"options": {
|
||||||
|
"dryRun": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -89,6 +93,8 @@ HyperV-Backup/
|
|||||||
.\hyper-v-backup.bat
|
.\hyper-v-backup.bat
|
||||||
# or
|
# or
|
||||||
.\hyper-v-backup.ps1
|
.\hyper-v-backup.ps1
|
||||||
|
|
||||||
|
# Test with dry run first (set dryRun: true in scriptsettings.json)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration Reference
|
## Configuration Reference
|
||||||
@ -112,6 +118,12 @@ HyperV-Backup/
|
|||||||
| `retentionCount` | number | Yes | Number of backup generations to keep (1-365) |
|
| `retentionCount` | number | Yes | Number of backup generations to keep (1-365) |
|
||||||
| `excludeVMs` | array | No | VM names to exclude from backup |
|
| `excludeVMs` | array | No | VM names to exclude from backup |
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Property | Type | Default | Description |
|
||||||
|
|----------|------|---------|-------------|
|
||||||
|
| `dryRun` | bool | `false` | Simulate backup without exporting or copying VMs |
|
||||||
|
|
||||||
### Version Tracking
|
### Version Tracking
|
||||||
|
|
||||||
| Property | Type | Description |
|
| Property | Type | Description |
|
||||||
@ -346,6 +358,14 @@ Run with verbose output:
|
|||||||
- Performance improvement: Skip unnecessary space checks
|
- Performance improvement: Skip unnecessary space checks
|
||||||
- Refactored parameter splatting for Invoke-ScheduledExecution
|
- Refactored parameter splatting for Invoke-ScheduledExecution
|
||||||
|
|
||||||
|
### 1.0.2 (2026-01-28)
|
||||||
|
- Added auto-installation of Hyper-V PowerShell module if missing
|
||||||
|
- Removed `#Requires -Modules Hyper-V` directive in favor of runtime installation
|
||||||
|
- Module installation uses `Enable-WindowsOptionalFeature` for the Microsoft-Hyper-V-Management-PowerShell feature
|
||||||
|
- Handles restart requirement notification if Windows feature installation requires reboot
|
||||||
|
- Added dry run mode (`options.dryRun`) to simulate backup without actual export/copy operations
|
||||||
|
- Restructured settings to use `options` object for consistency with other scripts
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
For issues or questions:
|
For issues or questions:
|
||||||
|
|||||||
@ -3,8 +3,8 @@ setlocal EnableDelayedExpansion
|
|||||||
|
|
||||||
REM ============================================================================
|
REM ============================================================================
|
||||||
REM Hyper-V Backup Launcher
|
REM Hyper-V Backup Launcher
|
||||||
REM VERSION: 1.0.1
|
REM VERSION: 1.0.2
|
||||||
REM DATE: 2026-01-26
|
REM DATE: 2026-01-28
|
||||||
REM DESCRIPTION: Batch file launcher for hyper-v-backup.ps1 with admin check
|
REM DESCRIPTION: Batch file launcher for hyper-v-backup.ps1 with admin check
|
||||||
REM ============================================================================
|
REM ============================================================================
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,6 @@ param (
|
|||||||
)
|
)
|
||||||
|
|
||||||
#Requires -RunAsAdministrator
|
#Requires -RunAsAdministrator
|
||||||
#Requires -Modules Hyper-V
|
|
||||||
|
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
@ -13,18 +12,18 @@ param (
|
|||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
Production-ready Hyper-V backup solution with scheduling, checkpoints, and retention management.
|
Production-ready Hyper-V backup solution with scheduling, checkpoints, and retention management.
|
||||||
.VERSION
|
.VERSION
|
||||||
1.0.1
|
1.0.2
|
||||||
.DATE
|
.DATE
|
||||||
2026-01-26
|
2026-01-28
|
||||||
.NOTES
|
.NOTES
|
||||||
- Requires Administrator privileges
|
- Requires Administrator privileges
|
||||||
- Requires Hyper-V PowerShell module
|
- Requires Hyper-V PowerShell module (auto-installed if missing)
|
||||||
- Requires SchedulerTemplate.psm1 module
|
- Requires SchedulerTemplate.psm1 module
|
||||||
#>
|
#>
|
||||||
|
|
||||||
# Script Version
|
# Script Version
|
||||||
$ScriptVersion = "1.0.1"
|
$ScriptVersion = "1.0.2"
|
||||||
$ScriptDate = "2026-01-26"
|
$ScriptDate = "2026-01-28"
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Import-Module "$PSScriptRoot\..\SchedulerTemplate.psm1" -Force -ErrorAction Stop
|
Import-Module "$PSScriptRoot\..\SchedulerTemplate.psm1" -Force -ErrorAction Stop
|
||||||
@ -68,6 +67,10 @@ $CredentialEnvVar = $settings.credentialEnvVar
|
|||||||
$TempExportRoot = $settings.tempExportRoot
|
$TempExportRoot = $settings.tempExportRoot
|
||||||
$RetentionCount = $settings.retentionCount
|
$RetentionCount = $settings.retentionCount
|
||||||
$BlacklistedVMs = $settings.excludeVMs
|
$BlacklistedVMs = $settings.excludeVMs
|
||||||
|
$Options = $settings.options
|
||||||
|
|
||||||
|
# Get DryRun from settings
|
||||||
|
$DryRun = $Options.dryRun
|
||||||
|
|
||||||
# Schedule Configuration
|
# Schedule Configuration
|
||||||
$Config = @{
|
$Config = @{
|
||||||
@ -106,9 +109,47 @@ $script:BackupStats = @{
|
|||||||
|
|
||||||
# Helper Functions =========================================================
|
# Helper Functions =========================================================
|
||||||
|
|
||||||
|
function Test-HyperVModule {
|
||||||
|
param([switch]$Automated)
|
||||||
|
|
||||||
|
Write-Log "Checking Hyper-V PowerShell module..." -Level Info -Automated:$Automated
|
||||||
|
|
||||||
|
if (-not (Get-Module -ListAvailable -Name Hyper-V)) {
|
||||||
|
Write-Log "Hyper-V PowerShell module not found. Installing..." -Level Warning -Automated:$Automated
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Install Hyper-V PowerShell module (Windows Feature)
|
||||||
|
$result = Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Management-PowerShell -All -NoRestart -ErrorAction Stop
|
||||||
|
|
||||||
|
if ($result.RestartNeeded) {
|
||||||
|
Write-Log "Hyper-V PowerShell module installed but requires system restart" -Level Warning -Automated:$Automated
|
||||||
|
Write-Log "Please restart the system and run the script again" -Level Info -Automated:$Automated
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Hyper-V PowerShell module installed successfully" -Level Success -Automated:$Automated
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "Failed to install Hyper-V PowerShell module: $_" -Level Error -Automated:$Automated
|
||||||
|
Write-Log "Please install manually: Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Management-PowerShell -All" -Level Info -Automated:$Automated
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Import-Module Hyper-V -ErrorAction Stop
|
||||||
|
Write-Log "Hyper-V PowerShell module loaded" -Level Success -Automated:$Automated
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "Failed to import Hyper-V module: $_" -Level Error -Automated:$Automated
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function Test-Prerequisites {
|
function Test-Prerequisites {
|
||||||
param([switch]$Automated)
|
param([switch]$Automated)
|
||||||
|
|
||||||
Write-Log "Checking prerequisites..." -Level Info -Automated:$Automated
|
Write-Log "Checking prerequisites..." -Level Info -Automated:$Automated
|
||||||
|
|
||||||
# Check if running as Administrator
|
# Check if running as Administrator
|
||||||
@ -119,8 +160,7 @@ function Test-Prerequisites {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Check Hyper-V module
|
# Check Hyper-V module
|
||||||
if (-not (Get-Module -ListAvailable -Name Hyper-V)) {
|
if (-not (Test-HyperVModule -Automated:$Automated)) {
|
||||||
Write-Log "Hyper-V PowerShell module is not installed!" -Level Error -Automated:$Automated
|
|
||||||
return $false
|
return $false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,21 +372,30 @@ function Backup-VM {
|
|||||||
Write-Log "Temp drive ${tempDrive}: has $([math]::Round($freeSpace / 1GB, 2)) GB free" -Level Info -Automated:$Automated
|
Write-Log "Temp drive ${tempDrive}: has $([math]::Round($freeSpace / 1GB, 2)) GB free" -Level Info -Automated:$Automated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Dry run mode - skip actual backup operations
|
||||||
|
if ($DryRun) {
|
||||||
|
Write-Log "DRY RUN: Would export VM '$VMName' to temp location" -Level Warning -Automated:$Automated
|
||||||
|
Write-Log "DRY RUN: Would copy export to backup location: $vmBackupPath" -Level Warning -Automated:$Automated
|
||||||
|
Write-Log "=== DRY RUN: Backup simulated for VM: $VMName ===" -Level Warning -Automated:$Automated
|
||||||
|
$script:BackupStats.SkippedVMs++
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
# Export VM to temp location (Export-VM creates its own checkpoint internally)
|
# Export VM to temp location (Export-VM creates its own checkpoint internally)
|
||||||
$tempExportPath = Join-Path -Path $TempExportRoot -ChildPath "$VMName-$DateSuffix"
|
$tempExportPath = Join-Path -Path $TempExportRoot -ChildPath "$VMName-$DateSuffix"
|
||||||
Write-Log "Exporting VM '$VMName' to temp location: $tempExportPath" -Level Info -Automated:$Automated
|
Write-Log "Exporting VM '$VMName' to temp location: $tempExportPath" -Level Info -Automated:$Automated
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Export-VM -Name $VMName -Path $tempExportPath -ErrorAction Stop
|
Export-VM -Name $VMName -Path $tempExportPath -ErrorAction Stop
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
Write-Log "Failed to export VM '$VMName': $_" -Level Error -Automated:$Automated
|
Write-Log "Failed to export VM '$VMName': $_" -Level Error -Automated:$Automated
|
||||||
|
|
||||||
# Cleanup temp if export failed
|
# Cleanup temp if export failed
|
||||||
if (Test-Path $tempExportPath) {
|
if (Test-Path $tempExportPath) {
|
||||||
Remove-Item -Path $tempExportPath -Recurse -Force -ErrorAction SilentlyContinue
|
Remove-Item -Path $tempExportPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
$script:BackupStats.FailedVMs++
|
$script:BackupStats.FailedVMs++
|
||||||
$script:BackupStats.FailureMessages += "Export failed for $VMName"
|
$script:BackupStats.FailureMessages += "Export failed for $VMName"
|
||||||
return $false
|
return $false
|
||||||
@ -394,12 +443,12 @@ function Backup-VM {
|
|||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
Write-Log "Failed to copy VM '$VMName' to backup location: $_" -Level Error -Automated:$Automated
|
Write-Log "Failed to copy VM '$VMName' to backup location: $_" -Level Error -Automated:$Automated
|
||||||
|
|
||||||
# Cleanup partial backup
|
# Cleanup partial backup
|
||||||
if (Test-Path $vmBackupPath) {
|
if (Test-Path $vmBackupPath) {
|
||||||
Remove-Item -Path $vmBackupPath -Recurse -Force -ErrorAction SilentlyContinue
|
Remove-Item -Path $vmBackupPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
$script:BackupStats.FailedVMs++
|
$script:BackupStats.FailedVMs++
|
||||||
$script:BackupStats.FailureMessages += "Copy to NAS failed for $VMName"
|
$script:BackupStats.FailureMessages += "Copy to NAS failed for $VMName"
|
||||||
return $false
|
return $false
|
||||||
@ -409,7 +458,7 @@ function Backup-VM {
|
|||||||
|
|
||||||
# Cleanup temp export
|
# Cleanup temp export
|
||||||
Write-Log "Cleaning up temp export for VM '$VMName'..." -Level Info -Automated:$Automated
|
Write-Log "Cleaning up temp export for VM '$VMName'..." -Level Info -Automated:$Automated
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (Test-Path $tempExportPath) {
|
if (Test-Path $tempExportPath) {
|
||||||
Remove-Item -Path $tempExportPath -Recurse -Force -ErrorAction Stop
|
Remove-Item -Path $tempExportPath -Recurse -Force -ErrorAction Stop
|
||||||
@ -558,6 +607,9 @@ function Start-BusinessLogic {
|
|||||||
Write-Log "Hyper-V Backup Process Started" -Level Info -Automated:$Automated
|
Write-Log "Hyper-V Backup Process Started" -Level Info -Automated:$Automated
|
||||||
Write-Log "Script Version: $ScriptVersion ($ScriptDate)" -Level Info -Automated:$Automated
|
Write-Log "Script Version: $ScriptVersion ($ScriptDate)" -Level Info -Automated:$Automated
|
||||||
Write-Log "Host: $Hostname" -Level Info -Automated:$Automated
|
Write-Log "Host: $Hostname" -Level Info -Automated:$Automated
|
||||||
|
if ($DryRun) {
|
||||||
|
Write-Log "DRY RUN MODE - No changes will be made" -Level Warning -Automated:$Automated
|
||||||
|
}
|
||||||
Write-Log "========================================" -Level Info -Automated:$Automated
|
Write-Log "========================================" -Level Info -Automated:$Automated
|
||||||
|
|
||||||
# Check prerequisites
|
# Check prerequisites
|
||||||
@ -606,13 +658,18 @@ function Start-BusinessLogic {
|
|||||||
$dateSuffix = Get-Date -Format "yyyyMMddHHmmss"
|
$dateSuffix = Get-Date -Format "yyyyMMddHHmmss"
|
||||||
$backupFolder = Join-Path -Path $BackupPath -ChildPath $dateSuffix
|
$backupFolder = Join-Path -Path $BackupPath -ChildPath $dateSuffix
|
||||||
|
|
||||||
try {
|
if ($DryRun) {
|
||||||
New-Item -Path $backupFolder -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
Write-Log "DRY RUN: Would create backup folder: $backupFolder" -Level Warning -Automated:$Automated
|
||||||
Write-Log "Created backup folder: $backupFolder" -Level Success -Automated:$Automated
|
|
||||||
}
|
}
|
||||||
catch {
|
else {
|
||||||
Write-Log "Failed to create backup folder '$backupFolder': $_" -Level Error -Automated:$Automated
|
try {
|
||||||
exit 1
|
New-Item -Path $backupFolder -ItemType Directory -Force -ErrorAction Stop | Out-Null
|
||||||
|
Write-Log "Created backup folder: $backupFolder" -Level Success -Automated:$Automated
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "Failed to create backup folder '$backupFolder': $_" -Level Error -Automated:$Automated
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Process each VM
|
# Process each VM
|
||||||
@ -628,11 +685,21 @@ function Start-BusinessLogic {
|
|||||||
Backup-VM -VMName $vmName -BackupFolder $backupFolder -DateSuffix $dateSuffix -Automated:$Automated
|
Backup-VM -VMName $vmName -BackupFolder $backupFolder -DateSuffix $dateSuffix -Automated:$Automated
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cleanup old checkpoints
|
# Cleanup old checkpoints (skip in dry run mode)
|
||||||
Remove-OldCheckpoints -VMs $vms -Automated:$Automated
|
if ($DryRun) {
|
||||||
|
Write-Log "DRY RUN: Skipping checkpoint cleanup" -Level Warning -Automated:$Automated
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Remove-OldCheckpoints -VMs $vms -Automated:$Automated
|
||||||
|
}
|
||||||
|
|
||||||
# Cleanup old backups
|
# Cleanup old backups (skip in dry run mode)
|
||||||
Remove-OldBackups -BackupPath $BackupPath -RetentionCount $RetentionCount -Automated:$Automated
|
if ($DryRun) {
|
||||||
|
Write-Log "DRY RUN: Skipping old backup cleanup" -Level Warning -Automated:$Automated
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Remove-OldBackups -BackupPath $BackupPath -RetentionCount $RetentionCount -Automated:$Automated
|
||||||
|
}
|
||||||
|
|
||||||
# Print summary
|
# Print summary
|
||||||
Write-BackupSummary -Automated:$Automated
|
Write-BackupSummary -Automated:$Automated
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
"$schema": "https://json-schema.org/draft-07/schema",
|
"$schema": "https://json-schema.org/draft-07/schema",
|
||||||
"title": "Hyper-V Backup Script Settings",
|
"title": "Hyper-V Backup Script Settings",
|
||||||
"description": "Configuration file for hyper-v-backup.ps1 script",
|
"description": "Configuration file for hyper-v-backup.ps1 script",
|
||||||
"version": "1.0.1",
|
"version": "1.0.2",
|
||||||
"lastModified": "2026-01-26",
|
"lastModified": "2026-01-28",
|
||||||
"schedule": {
|
"schedule": {
|
||||||
"runMonth": [],
|
"runMonth": [],
|
||||||
"runWeekday": ["Monday"],
|
"runWeekday": ["Monday"],
|
||||||
@ -15,6 +15,9 @@
|
|||||||
"tempExportRoot": "D:\\Temp\\HyperVExport",
|
"tempExportRoot": "D:\\Temp\\HyperVExport",
|
||||||
"retentionCount": 3,
|
"retentionCount": 3,
|
||||||
"excludeVMs": ["nassrv0002"],
|
"excludeVMs": ["nassrv0002"],
|
||||||
|
"options": {
|
||||||
|
"dryRun": false
|
||||||
|
},
|
||||||
"_comments": {
|
"_comments": {
|
||||||
"version": "Configuration schema version",
|
"version": "Configuration schema version",
|
||||||
"lastModified": "Last modification date (YYYY-MM-DD)",
|
"lastModified": "Last modification date (YYYY-MM-DD)",
|
||||||
@ -28,6 +31,9 @@
|
|||||||
"credentialEnvVar": "Name of Machine-level environment variable containing Base64-encoded 'username:password'",
|
"credentialEnvVar": "Name of Machine-level environment variable containing Base64-encoded 'username:password'",
|
||||||
"tempExportRoot": "Local directory for temporary VM exports. Space is checked dynamically per VM (1.5x VM size).",
|
"tempExportRoot": "Local directory for temporary VM exports. Space is checked dynamically per VM (1.5x VM size).",
|
||||||
"retentionCount": "Number of backup generations to keep (1-365). Older backups are automatically deleted.",
|
"retentionCount": "Number of backup generations to keep (1-365). Older backups are automatically deleted.",
|
||||||
"excludeVMs": "Array of VM names to exclude from backup process"
|
"excludeVMs": "Array of VM names to exclude from backup process",
|
||||||
|
"options": {
|
||||||
|
"dryRun": "Simulate backup without actually exporting or copying VMs"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user