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