Hyper-V-Backup.ps1
<#PSScriptInfo .VERSION 3.8 .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 Cluster CSV Backup Export Permissions .LICENSEURI .PROJECTURI https://gal.vin/2017/09/18/vm-backup-for-hyper-v .ICONURI .EXTERNALMODULEDEPENDENCIES Windows 10/Windows Server 2016/Windows 2012 R2 Hyper-V PowerShell Management Modules .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES Hyper-V PowerShell Management Tools .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 -noperms 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 specified backup location to do an export. If the -noprems switch is NOT used, the script will use the built-in export function, and the VMs will continue to run. Important note: This script should be run on a Hyper-V host. The Hyper-V PowerShell management modules should be installed. Please note: to send a log file using ssl and an SMTP password you must generate an encrypted password file. The password file is unique to both the user and machine. The command is as follows: $creds = Get-Credential $creds.Password | ConvertFrom-SecureString | Set-Content c:\foo\ps-script-pwd.txt .PARAMETER BackupTo The path the Virtual Machines should be backed up to. A folder will be created and 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 Hyper-V-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 multiple VMs are running, the first VM (alphabetically) will be shutdown, backed up, and then started, then the next and so on. .PARAMETER SendTo The e-mail address the log should be sent to. .PARAMETER From The from address the log should be sent from. .PARAMETER Smtp The DNS or IP address of the SMTP server. .PARAMETER User The user account to connect to the SMTP server. .PARAMETER Pwd The txt file containing the encrypted password for the user account. .PARAMETER UseSsl Connect to the SMTP server using SSL. .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 C:\foo\pwd.txt -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 = ("Hyper-V-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) { $SmtpPwdEncrypt = Get-Content $SmtpPwd | ConvertTo-SecureString $SmtpCreds = New-Object System.Management.Automation.PSCredential -ArgumentList ($SmtpUser, $SmtpPwdEncrypt) ## 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 |