A Week of Upgrades [4/4]: Hyper-V Backup Script

[sc:windows-category ]The first three parts of this series were all about updating the software I run at home, this last part of the series will instead be about a backup script I modified for the VM servers.

So first of all, let me walk you though the manual method I have been using to make backups of the Hyper-V VM’s:

  1. Connect a USB drive to my workstation
  2. Mount a TrueCrypt volume on the USB drive and share it from the workstation
  3. Connect to the Hyper-V server through RDP
  4. Shutdown the VM (either though Hyper-V Manager or for the Linux boxes, through a console logon)
  5. Open an Explorer window and copy the VHD to a temporary directory on the server
  6. Restart the VM
  7. Copy the backup VHD across the network using Explorer
  8. Rinse, wash, repeat for each VM

This is not a fast process and has multiple steps that can take a lot of time to complete (mostly the copies) which makes is prone to taking longer than it should as I get working on something else and forget to check the progress.

After poking around the net a bit I found this article with a script that backs up a VM through PowerShell.  It’s not too bad but it does have a few things that needed changing for my own uses:

First off the script is in two parts, a calling script (backup.ps1) and the core script that actually does the backup (function.ps1).  I modified Backup.PS1 in two ways:

$script_dir = “D:\VMBackup”

$bdate = get-date -format “MMM dd yyyy”

$guest = “VM01”
$dest_server = “\\desktop\k\$guest\$bdate”
. “$script_dir\function.ps1”

$guest = “VM02”
$dest_server = “\\desktop\k\$guest\$bdate”
. “$script_dir\function.ps1”

The first change is to set $bdate using the get-date cmdlet so I don’t have to manually edit the script each time I want to run the script.

The second change is to move setting the $dest_server variables from function.ps1 to backup.ps1 so each VM can be stored in a different directory.

Function.ps1 had some more significant changes:

##
## Create a backup of the VM defined in the variable $guest
##

write-host “Backing up $guest…”
$temp_dir = “d:\VMBackup”
$VM_Service = get-wmiobject -namespace root\virtualization Msvm_VirtualSystemManagementService

$VM = gwmi -namespace root\virtualization -query “select * from msvm_computersystem where elementname=’$guest'”

$VMReturnState = $VM.EnabledState
$VMName = $VM.ElementName

if (($VM.EnabledState -eq 2) -or ($VM.EnabledState -eq 32768) -or ($VM.EnabledState -eq 32770))
{
write-host “Saving the state of $VMName”
$VM.RequestStateChange(32769)
}

while (!($VM.EnabledState -eq 32769) -and !($VM.EnabledState -eq 3))
{
Start-Sleep(1)
$VM = get-wmiobject -namespace root\virtualization -Query “Select * From Msvm_ComputerSystem Where ElementName=’$VMName'”
}

if ([IO.Directory]::Exists(“$temp_dir\$VMName”))
{
[IO.Directory]::Delete(“$temp_dir\$VMName”, $True)
}

write-host “Exporting the VM…”
$status = $VM_Service.ExportVirtualSystem($VM.__PATH, $True, “$temp_dir”)
if ($status.ReturnValue -eq 4096)
{
$job = [Wmi]$status.Job
while (!($job.PercentComplete -eq 100) -and ($job.ErrorCode -eq 0))
{
Start-Sleep(1)
$job = [Wmi]$status.Job

$i = $job.PercentComplete
write-progress -activity “Export in progress” -status “$i% Complete:” -percentcomplete $i
}
}

## Re-start the VM.
write-host “Restarting $guest…”
$VM.RequestStateChange($VMReturnState)

## Remove any old file that exist in the destination.
if ([IO.Directory]::Exists(“$dest_server\$VMName”))
{
write-host “Found existing backup at destination, deleting it…”
[IO.Directory]::Delete(“$dest_server\$VMName”, $True)
}

write-host “Removing backup VHD’s from the export…”
remove-item(“$temp_dir\$VMName\Virtual Hard Disks\* – Backup Disk.vhd”)

write-host “Copying backup to destination…”
Copy-Item “$temp_dir\$VMName” “$dest_server” -recurse

write-host “Deleting local copy…”
[IO.Directory]::Delete(“$temp_dir\$VMName”, $True)

write-host “Backup of $VMName complete!”

First off I replaced the echo commands with write-host, the original intent of this was to add the -no-new-line option in the export loop so instead of printing the percentages one per line I could add them together with some “…” between them.

Of course doing some additional poking around I found the write-progress cmdlet which gives a nice progress bar instead so I replaced it and reduced the sleep time from 5 seconds to 1 second.

In the original article they do mention moving the VM restart to right after the export and I agree this is a good idea, no sense waiting for the network copy to finish.

I added in a few extra status messages just to make sure things were progressing.  The next item I changed was the last if/else statement in the original script.  It was there as a way to handle existing backups, if one existed it renamed the old folder, did the copy across the network and then deleted the old folder.  However at no point did it do any error checking so it would never exit out, effectively making the rename a redundant item.  Instead I simply checked for the existence of an old backup and if so deleted it.  The rest was common after that so I removed the else.

I also renamed the old $dest variable to $temp_dir to make it clear it was only a temporary export location.

Another item I changed was the destination location.  My external USB drive directory structure is setup as \HOSTNAME\DATE\FILES, so I can have multiple backups of the same server.  The original code made that \HOSTNAME\DATE\HOSTNAME\FILES.  I removed the second redundant hostname entry from the script logic.

The final item I added was the “backup” VHD deletion from the export.  On the Windows servers BM’s I have at least two VHD attached, one for the OS and one for Windows Server Backup to use.  This second VHD is always called “SERVER – Backup Disk.vhd” and as I’m pulling a “clean” copy of the VHD I don’t really need the Windows Server Backup volume so I delete it before moving the export to the USB drive.

This script is pretty good and now my process for backing up the VM’s is much simpler:

  • Connect a USB drive to my workstation
  • Mount a TrueCrypt volume on the USB drive and share it from the workstation
  • Connect to the Hyper-V server through RDP
  • Execute d:\VMBackup\backup.ps1

This process is fully automated and I can let it run over night.  Likewise it only keeps the VM’s down for a short while during the export and then brings them back up as soon as possible.

The only issue I have with it is the copy of the export to the USB drive, copy-item provides NO feedback, it’s just a black hole until the copy is complete.  For large files like the VHD’s this really isn’t a very good thing.  In the manual method the copies provided feedback through the standard Windows Explorer copy dialog.

I believe I have a solution to this, however it is only for one file at a time and doesn’t recurse through the export directory so it will take a bit more work to build the recursion in to it.  As I’m now done all of the backups for this round, it will probably wait for a few more weeks until the next time I run the VM backups.

Avatar photo

Greg

Greg is the head cat at JumbleCat, with over 20 years of experience in the computer field, he has done everything from programming to hardware solutions. You can contact Greg via the contact form on the main menu above.

More Posts - Website

Avatar photo

Greg

Greg is the head cat at JumbleCat, with over 20 years of experience in the computer field, he has done everything from programming to hardware solutions. You can contact Greg via the contact form on the main menu above.