Hyper-V-Backup.ps1
<#PSScriptInfo .VERSION 3.5.1 .GUID c7fb05cc-1e20-4277-9986-523020060668 .AUTHOR Mike Galvin twitter.com/digressive .COMPANYNAME .COPYRIGHT (C) Mike Galvin. All rights reserved. .TAGS Hyper-V Virtual Machines Backup Export Permissions .LICENSEURI .PROJECTURI https://gal.vin/2017/09/18/vm-backup-for-hyper-v .ICONURI .EXTERNALMODULEDEPENDENCIES Windows 10/Windows Serevr2016/Windows 2012 R2 Hyper-V PowerShell Management Modules .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES #> <# .SYNOPSIS Creates a full backup of running Hyper-V Virtual Machines. .DESCRIPTION Creates a full backup of running Hyper-V Virtual Machines. This script will: Create a full backup of Virtual Machine(s), complete with configuration, snapshot, and VHD files. If the -noprems switch is used, the script will shutdown the VM and copy all the files to the backup location, then start the VM. You should use the -noperms switch if Hyper-V does not have the appropriate permissions to the backup location to do an export. If the -noprems switch is NOT used, the script will use the built-in export function. Important note: This script should be run on a Hyper-V host. The Hyper-V PowerShell management modules should be installed. .PARAMETER BackupTo The path the Virtual Machines should be backed up to. A folder will be created named after the Hyper-V host and each VM will have it's own folder inside. .PARAMETER l The path to output the log file to. The file name will be HyperV-Backup-YYYY-MM-dd-HH-mm-ss.log .PARAMETER NoPerms Instructs the script to shutdown the running VM(s) to do the file-copy based backup, instead of the Hyper-V export function. When multipul VMs are running, the first VM (alphabetically) will be shutdown, backed up, and then started, then the next and so on. .EXAMPLE Hyper-V-Backup.ps1 -backupto \\nas\vms -noperms -l E:\scripts -sendto me@contoso.com -from hyperv@contoso.com -smtp smtp.outlook.com -user user -pwd p@ssw0rd -usessl This will shutdown all running VMs and back up their files to \\nas\vms. The log file will be output to E:\scripts and sent via email. #> [CmdletBinding()] Param( [parameter(Mandatory=$true)] [alias("backupto")] $Backup, [alias("l")] $LogPath, [alias("sendto")] $MailTo, [alias("from")] $MailFrom, [alias("smtp")] $SmtpServer, [alias("user")] $SmtpUser, [alias("pwd")] $SmtpPwd, [switch]$UseSsl, [switch]$NoPerms) ## If logging is configured, start log If ($LogPath) { $LogFile = ("HyperV-Backup-{0:yyyy-MM-dd-HH-mm-ss}.log" -f (Get-Date)) $Log = "$LogPath\$LogFile" ## If the log file already exists, clear it $LogT = Test-Path -Path $Log If ($LogT) { Clear-Content -Path $Log } Add-Content -Path $Log -Value "****************************************" Add-Content -Path $Log -Value "$(Get-Date -format g) Log started" Add-Content -Path $Log -Value "" } ## Set variables for computer name and get all running VMs $Vs = $Env:ComputerName $Vms = Get-VM | Where-Object {$_.State -eq 'Running'} ## Logging If ($LogPath) { Add-Content -Path $Log -Value "$(Get-Date -format g) This virtual host is: $Vs" Add-Content -Path $Log -Value "$(Get-Date -format g) The following VMs will be backed up:" ForEach ($vm in $Vms) { Add-Content -Path $Log -Value "$($vm.name)" } } ## Test for backup folder existence ForEach ($vm in $Vms) { $VmExport = Test-Path "$Backup\$Vs\$($vm.name)" If ($VmExport -eq $true) { Remove-Item "$Backup\$Vs\$($vm.name)" -Recurse -Force If ($LogPath) { Add-Content -Path $Log -Value "$(Get-Date -format g) Removing previous backup of $($vm.name)" } Start-Sleep -s 5 } } ## If no perms switch is set do the following commands If ($NoPerms) { ## For each VM do the following ForEach ($vm in $Vms) { ## Create directories New-Item "$Backup\$Vs\$($vm.name)\Virtual Machines" -ItemType Directory -Force New-Item "$Backup\$Vs\$($vm.name)\VHD" -ItemType Directory -Force New-Item "$Backup\$Vs\$($vm.name)\Snapshots" -ItemType Directory -Force ## For logging, test for creation of backup folders, report if non existant. If ($LogPath) { $VmFolderTest = Test-Path "$Backup\$Vs\$($vm.name)\Virtual Machines" If ($VmFolderTest -eq $true) { Add-Content -Path $Log -Value "$(Get-Date -format g) Successfully created backup folder $Backup\$Vs\$($vm.name)\Virtual Machines" } Else { Add-Content -Path $Log -Value "$(Get-Date -format g) ERROR: There was a problem creating folder $Backup\$Vs\$($vm.name)\Virtual Machines" } $VmVHDTest = Test-Path "$Backup\$Vs\$($vm.name)\VHD" If ($VmVHDTest -eq $true) { Add-Content -Path $Log -Value "$(Get-Date -format g) Successfully created backup folder $Backup\$Vs\$($vm.name)\VHD" } Else { Add-Content -Path $Log -Value "$(Get-Date -format g) ERROR: There was a problem creating folder $Backup\$Vs\$($vm.name)\VHD" } $VmSnapTest = Test-Path "$Backup\$Vs\$($vm.name)\Snapshots" If ($VmSnapTest -eq $true) { Add-Content -Path $Log -Value "$(Get-Date -format g) Successfully created backup folder $Backup\$Vs\$($vm.name)\Snapshots" } Else { Add-Content -Path $Log -Value "$(Get-Date -format g) ERROR: There was a problem creating folder $Backup\$Vs\$($vm.name)\Snapshots" } } ## Stop the VM Stop-VM $vm ## For logging If ($LogPath) { Add-Content -Path $Log -Value "$(Get-Date -format g) Stopping VM: $($vm.name)" } Start-Sleep -s 5 ## Copy the config files and folders Copy-Item "$($vm.ConfigurationLocation)\Virtual Machines\$($vm.id)" "$Backup\$Vs\$($vm.name)\Virtual Machines\" -Recurse -Force Copy-Item "$($vm.ConfigurationLocation)\Virtual Machines\$($vm.id).*" "$Backup\$Vs\$($vm.name)\Virtual Machines\" -Recurse -Force ## For logging If ($LogPath) { $VmConfigTest = Test-Path "$Backup\$Vs\$($vm.name)\Virtual Machines\*" If ($VmConfigTest -eq $true) { Add-Content -Path $Log -Value "$(Get-Date -format g) Successfully copied $($vm.name) configuration to $Backup\$Vs\$($vm.name)\Virtual Machines" } Else { Add-Content -Path $Log -Value "$(Get-Date -format g) ERROR: There was a problem copying the configuration for $($vm.name)" } } ## Copy the VHD Copy-Item $vm.HardDrives.Path -Destination "$Backup\$Vs\$($vm.name)\VHD\" -Recurse -Force ## For logging If ($LogPath) { $VmVHDCopyTest = Test-Path "$Backup\$Vs\$($vm.name)\VHD\*" If ($VmVHDCopyTest -eq $true) { Add-Content -Path $Log -Value "$(Get-Date -format g) Successfully copied $($vm.name) VHDs to $Backup\$Vs\$($vm.name)\VHD" } Else { Add-Content -Path $Log -Value "$(Get-Date -format g) ERROR: There was a problem copying the VHDs for $($vm.name)" } } ## Get the snapshots $Snaps = Get-VMSnapshot $vm ## For each snapshot do the following ForEach ($snap in $Snaps) { ## Copy the snapshot config files and folders Copy-Item "$($vm.ConfigurationLocation)\Snapshots\$($snap.id)" "$Backup\$Vs\$($vm.name)\Snapshots\" -Recurse -Force Copy-Item "$($vm.ConfigurationLocation)\Snapshots\$($snap.id).*" "$Backup\$Vs\$($vm.name)\Snapshots\" -Recurse -Force ## For logging If ($LogPath) { $VmSnapCopyTest = Test-Path "$Backup\$Vs\$($vm.name)\Snapshots\*" If ($VmSnapCopyTest -eq $true) { Add-Content -Path $Log -Value "$(Get-Date -format g) Successfully copied checkpoint configuration for $($vm.name) to $Backup\$Vs\$($vm.name)\Snapshots" } Else { Add-Content -Path $Log -Value "$(Get-Date -format g) ERROR: There was a problem copying the checkpoint configuration for $($vm.name)" } } ## Copy the snapshot root VHD Copy-Item $snap.HardDrives.Path -Destination "$Backup\$Vs\$($vm.name)\VHD\" -Recurse -Force If ($LogPath) { Add-Content -Path $Log -Value "$(Get-Date -format g) Successfully copied checkpoint VHDs for $($vm.name) to $Backup\$Vs\$($vm.name)\VHD" } } ## Start the VM and wait for 60 seconds before proceeding Start-VM $vm ## For logging If ($LogPath) { Add-Content -Path $Log -Value "$(Get-Date -format g) Starting VM: $($vm.name)" } Start-Sleep -s 60 } } ## If no perms is not set export the VM like normal Else { $Vms | Export-VM -Path "$Backup\$vs" ## For logging If ($LogPath) { $VmExportTest = Test-Path "$Backup\$Vs\*" If ($VmExportTest -eq $true) { Add-Content -Path $Log -Value "$(Get-Date -format g) Successfully exported specified VMs to $Backup\$Vs" } Else { Add-Content -Path $Log -Value "$(Get-Date -format g) ERROR: There was a problem exporting the specified VMs to $Backup\$Vs" } } } ## If log was configured stop the log If ($LogPath) { Add-Content -Path $Log -Value "" Add-Content -Path $Log -Value "$(Get-Date -format g) Log finished" Add-Content -Path $Log -Value "****************************************" ## If email was configured, set the variables for the email subject and body If ($SmtpServer) { $MailSubject = "Hyper-V Backup Log" $MailBody = Get-Content -Path $Log | Out-String ## If an email password was configured, create a variable with the username and password If ($SmtpPwd) { $SmtpCreds = New-Object System.Management.Automation.PSCredential -ArgumentList $SmtpUser, $($SmtpPwd | ConvertTo-SecureString -AsPlainText -Force) ## If ssl was configured, send the email with ssl If ($UseSsl) { Send-MailMessage -To $MailTo -From $MailFrom -Subject $MailSubject -Body $MailBody -SmtpServer $SmtpServer -UseSsl -Credential $SmtpCreds } ## If ssl wasn't configured, send the email without ssl Else { Send-MailMessage -To $MailTo -From $MailFrom -Subject $MailSubject -Body $MailBody -SmtpServer $SmtpServer -Credential $SmtpCreds } } ## If an email username and password were not configured, send the email without authentication Else { Send-MailMessage -To $MailTo -From $MailFrom -Subject $MailSubject -Body $MailBody -SmtpServer $SmtpServer } } } ## End |