Thursday, January 22, 2009

Backing up Hyper-V using Powershell

I needed a simple and inexpensive way to backup my Hyper-V VMs. Like with most scripting tasks, I usually search online for examples and suggestions before attempting something myself. I soon discovered this:

http://mindre.net/post/Powershell-script-for-snapshot-and-exporting(backup)-Virtual-Machines.aspx

As with most scripts I needed to adjust it to my needs. Firstly, my host does not have enough space to keep a copy of all my VMs so I needed a script that would export one VM at a time to a network share. Of course, the export function in Hyper-V doesn’t support the use of network drives or UNC paths so I had to export locally first and then copy to the backup share on the file server. I also didn’t need to export all the VMs so I decided to add the VMs manually.

Before even starting I had to ensure that the scripts would work. For this you need two things. You need to install the “Windows PowerShell” feature in Windows Server 2008 and you need to adjust the script signing policy. For testing purposes you can use “Set-ExecutionPolicy Unrestricted” to disable the signing requirement.

I split the task into two scripts backup.ps1 and function.ps1. backup.ps1 simply sets the variable $guest to the name of the first VM and then calls function.ps1 to do the actual backing up. It then sets $guest to the second VM and calls function.ps1 again to back it up and so on.

Because I don’t have very much disk space I had the script delete the local files after copying them to the server. I only then started backing up the next VM.

The contents of the scripts can be found below.

The next steps are to schedule the scripts to run automatically and to create a log file and email it (to me for example).

 

backup.ps1

$script_dir = "d:\backup\scripts"

$guest = "db1"
. "$script_dir\function.ps1"

$guest = "name-of-VM-to-backup"
. "$script_dir\function.ps1"

 

function.ps1

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

$dest = "d:\backup"
$dest_server = "\\fileservername\Hyper-V-Backup"
$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))
    {
        $VM.RequestStateChange(32769)
        echo "Saving the state of $VMName"
    }

    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("$dest\TmpDir\$VMName"))
    {
        [IO.Directory]::Delete("$dest\TmpDir\$VMName", $True)
    }

    echo "Exporting the VM"
    $status = $VM_Service.ExportVirtualSystem($VM.__PATH, $True, "$dest\TmpDir")
    if ($status.ReturnValue -eq 4096)
    {
        $job = [Wmi]$status.Job   
        while (!($job.PercentComplete -eq 100) -and ($job.ErrorCode -eq 0))
        {
            Start-Sleep(5)
            $job = [Wmi]$status.Job   
            echo $job.PercentComplete
        }
    }

    ##    Store the files on in a temp directory before moving them to their location and then remove the old files.

    if ([IO.Directory]::Exists("$dest_server\$VMName"))
    {
        [IO.Directory]::Move("$dest_server\$VMName", "$dest_server\$VMName-OldTmpDir")

        Copy-Item "$dest\TmpDir\$VMName" "$dest_server\$VMName" -recurse

        [IO.Directory]::Delete("$dest_server\$VMName-OldTmpDir", $True)
        [IO.Directory]::Delete("$dest\TmpDir\$VMName", $True)
    }
    else
    {
        Copy-Item "$dest\TmpDir\$VMName" "$dest_server\$VMName" -recurse
        [IO.Directory]::Delete("$dest\TmpDir\$VMName", $True)
    }
    echo "Done with $VMName"
    $VM.RequestStateChange($VMReturnState)

 

UPDATE 19.08.2009

If you like you can change the last part of the script by moving the last command ($VM.RequestStateChange($VMReturnState))to directly after the export (i.e. before the last “if”). This way the VM will have a lot less down time since it won’t need to wait for the files to be copied over the network.

No comments:

Post a Comment