PowerShell: Full Backup of Hyper-V Virtual Machines (Update 3.7)

In a previous post I wrote about my Hyper-V backup script, powered by PowerShell. This post will serve as a change log and documentation page, as my previous post was more about how the script is written as a reference.

My Hyper-V Backup script can be downloaded from the Microsoft TechNet Gallery and the PowerShell Gallery.

Features and Requirements

  • The script is designed to be run on a Hyper-V host.
  • The device must also have Hyper-V management tools installed.
  • Can be used to backup VMs to a device which the Hyper-V host machine does not have the access to run an Export operation.

The script has been tested on Windows 10, Windows Server 2016 (Datacenter and Core installations) and Windows Server 2012 R2 (Datacenter and Core Installations).

There are two ways the script can be run. When the -noperms switch is enabled, the script will:

  1. Get a list of all running Virtual Machines on the host.
  2. Gracefully shutdown the first VM.
  3. Copy all configuration, VHD, and snapshot files to the specified backup location.
  4. Start up the Virtual Machine, and move on to the next.
  5. Optionally create a log file and email it to an address of your choice.

When run without the -noperms switch, the script will:

  1. Get a list of all running Virtual Machines on the host.
  2. Go through each VM and run an Export operation, whilst all VMs are kept online.
  3. Optionally create a log file and email it to an address of your choice.

Detailed Explanation Of The Need For The -noperms Switch

Hyper-V’s export operation require that the computer account in Active Directory have access to the location where the exports are being saved. When a NAS device (for example a QNAP device) is intended to be used as an export location within a domain, Hyper-V will not be able to perform the operation as the computer account will not have access to the share on the NAS. Unfortunately, to copy all the files necessary for a complete backup the VM must be in an offline state for the operation to be completed.

Generating an encrypted password file

As of version 3.7 I’ve changed how the script handles configuring a password for the log notification e-mail. Specifically the password must now be in an encrypted text file. The advantage of this is that the password will no longer be in plain text, which is a security risk. The downside is that you will now need to generate a password file. The command to do so is pretty simple, but it must be generated on the computer the script will be running on, and as the user used to run the script.

To generate the password file, run the following command in PowerShell. When running the command you will be prompted for a username and password. The username doesn’t matter and can be anything, but the password must be the password you want to use to authenticate to your SMTP server.

$creds = Get-Credential
$creds.Password | ConvertFrom-SecureString | Set-Content c:\scripts\ps-script-pwd.txt

After running the commands, you should have a text file contained the encrypted password. Enter the path and filename for the -pwd switch to configure authenticated e-mail notification.

Configuration

The configuration can now be done via command line switches, instead of having to edit the script itself. Here’s a list of all the switches and example configurations.

Command Line Switch Mandatory Description Example
-backupto Yes Location of where to store the backups. Each VM will be stored in it’s own folder under a folder named after the Hyper-V host. Can be local or UNC. \\nas\Backups OR E:\Backups
-l No Location to store the optional log file. The name of the log file is generated automatically. E:\scripts
-sendto No The email address to send the log file to. me@contoso.com
-from No* The email address that the log file should be sent from.

*This switch isn’t mandatory but is required if you wish to email the log file.

HyperV@contoso.com
-smtp No* SMTP server address to use for the email functionality.

*This switch isn’t mandatory but is required if you wish to email the log file.

mail01.contoso.com

OR

smtp.live.com

OR

smtp.office365.com

-user No* The username of the account to use for SMTP authentication.

*This switch isn’t mandatory but may be required depending on the configuration of the SMTP server.

example@contoso.com
-pwd No* The location of the file containing the encrypted password of the account to use for SMTP authentication.

*This switch isn’t mandatory but may be required depending on your SMTP server.

c:\scripts\ps-script-pwd.txt
-usessl No* Add this option if you wish to use SSL with the configured SMTP server.

Tip: If you wish to send email to outlook.com or office365.com you will need this.

*This switch isn’t mandatory but may be required depending on the configuration of the SMTP server.

N/A
-noperms No Set if the backup location is a location where the Hyper-V host will not have the permissions to perform a export operation, such as a NAS appliance

When set the VMs will be shutdown to perform the backup, when not set a regular Hyper-V export, with the VMs kept online, will be done.

N/A

Change Log

16/10/2017 3.7

  • Changed SMTP authentication to require an encrypted password file.
  • Added instructions on how to generate an encrypted password file.

07/10/2017 3.6

  • Added necessary information to add the script to the PowerShell Gallery.

18/09/2017 3.5

  • Improved the log output to be easier to read.

22/07/2017 3.4

  • Improved commenting on the code for documentation purposes.
  • Added authentication and SSL options for e-mail notification.

20/05/2017 3.3

  • Added configuration via command line switches.
  • Added option to perform regular online export if destination allows it.

24/04/2017 Minor Update

  • Cleaned up the formatting and commented sections of the script.

21/04/2017 Minor Update

  • Added the ability to email the log file when the script completes.

PowerShell Code


<#PSScriptInfo .VERSION 3.7 .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 Server 2016/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. 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 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 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 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 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)
        {
            $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

If you’d like to get in touch with me, please leave a comment or tweet me.

-Mike

Follow Mike on Twitter: @Digressive

17 thoughts on “PowerShell: Full Backup of Hyper-V Virtual Machines (Update 3.7)

  1. Pingback: PowerShell: Hyper-V Backup With Checkpoints/Snapshots | Stick To The Script!

  2. Hi. I’m trying to use your script, but every VM that it tried to backup it says
    Export failed for virtual machine with error ‘The specified server cannot perform the
    requested operation.’ (0x8007003A).

    Like

      • Ok, thanks. If it works with the -noperms option then it’s most likly because of the permissions that the computer running Hyper-V has to the destination that you have configured.

        For me, I added the -noperms switch because my Hyper-V server is a member of a domain and I wanted to export the VMs to a NAS device which was not running a Windows OS, and so could not grant the permissions needed for the proper export function to work.

        Like

      • That makes sense, I’m in the same boat trying to backup to a NAS.

        But the script is awesome, thank you for sharing!

        Like

  3. I want to select specific VM’s so I assume I need to change the;
    $vs = $env:computername
    $vms = Get-VM | Where-Object {$_.State -eq ‘Running’}
    to somehow select the specific names instead of just running?
    I am not a programmer or understand scripting well, but it seems that it should be fairly easy since most of the VM’s names are similar to XXX16Vxx. Or just explicitly name them when running the script.

    Like

  4. Can I assume from the script the previously exported VM is deleted before the next export for that particular VM name?
    Also, I am going to try to modify the script in order to export certain VM’s to one host and other VM’s to another for DR purposes. I am not very good at scripting, but it seems that it should be fairly easy to modify one script or just have 2 scripts run (one script exports VM’s 1 & 2 to HOST1 and one script VM 3 to HOST2).

    Like

  5. Pingback: A Quick and Dirty Hyper-V Backup Script | Stick To The Script!

  6. Hello, i have an issue, when i execute the scritp and put the backup directory g:\ the shell said couldn’t creat a directory because didn’t founde the file in line 225 character 12
    + $Vms | Export-VM -Path “$Backup\$vs”
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : ObjectNotFound: (Microsoft.HyperV.PowerShell.VMTask:VM
    Task) [Export-VM], VirtualizationOperationFailedException
    + FullyQualifiedErrorId : ObjectNotFound,Microsoft.HyperV.PowerShell.Commands.Ex
    portVMCommand

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s