Gofolo Blog

The coding hell of Dennis V and the daily IT of Niklas Jumlin

April 21st, 2010 by Niklas

As far as I know, after some googling I couldn’t find any program that could take backups of Virtual Machines running on Hyper-V Core when the .VHD files were located on a Cluster Shared Storage.

If you read my previous post you can probably understand you aren’t able to install any advanced programs in the Core-version either because many required runtime files and libraries aren’t available.

Therefore I decided to write a PowerShell script that would export the machines to a network storage server.

Updated 2010-05-12 14:40 UTC +1 (Various fixes/Added option to exclude some virtual machines)

This script can and should be scheduled on every Hyper-V node in your cluster. It can ofcourse be used for backing up VMs in a non-clustered environment. The syntax to execute the script is as follow:

Powershell “& ‘D:\powershell\CreateVmBackups.ps1′”

##  Copyright 2010 Niklas Jumlin
##  VM Handling code based off Tore Lervik.
##
##	Create a backup of all the vm's
##
 
##  Name this Hyper-V
$name = hostname
 
##  Where do we wanna store the backups?
$dest = "\\nas1\Backups\Hyper-V Backup"
 
##  Specify where to save the log
$log = "$dest\CreateVmBackups-$name.log"
 
##  Excluded Virtual Machines, comma separated: "MACHINE1","MACHINE2"
$excludevm = "Exchangesrv","Win7Reference","WinXP-Pro"
 
##  Email settings, seperate each recipient with comma and no spaces
$recipient = "user1@domain.com,user2@domain.com"
$from= "administrator@domain.com"
$server = "172.16.30.7"
 
##  Dont bother these
$type = "Daily"
$currentday = get-date -uformat "%d"
$currentmonth = get-date -uformat "%m"
$date = get-date -uformat "%Y-%m-%d-%A"
$VM_Service = get-wmiobject -namespace root\virtualization Msvm_VirtualSystemManagementService
$ListofVMs = get-wmiobject -namespace root\virtualization Msvm_ComputerSystem -filter  "ElementName <> Name"   
 
##  Write start time to logfile
$dttm = get-date -uformat "%Y-%m-%d %H:%M:%S"
Add-Content $log "$dttm :: Backup started"
 
##  Start loop
foreach ($VM in [array] $ListOfVMs)
{
 
	$VMReturnState = $VM.EnabledState
	$VMName = $VM.ElementName
 
  ##  This part will exclude Virtual Machines that are specified within the $excludevm array
  if ($excludevm -contains $VMname)
  {
     echo "$dttm :: Excluding $VMName from Backup"
     Add-Content $log "$dttm :: Excluding $VMName from Backup"
  }
  else
  {
  ##  If the virtual machines isn't specified to be excluded, then continue
 
	if (($VM.EnabledState -eq 2) -or ($VM.EnabledState -eq 32768) -or ($VM.EnabledState -eq 32770))
	{
		$VM.RequestStateChange(32769)
		echo "Saving the state of $VMName - $date"
		$savestart = get-date -uformat "%Y-%m-%d %H:%M:%S"
		Add-Content $log "$savestart :: $VMName : Saving the state"
	}
 
	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)
	}
 
    ##  Export to Temp dir
 
	echo "Exporting the VM: $VMName"
	$exportstart = get-date -uformat "%Y-%m-%d %H:%M:%S"
	Add-Content $log "$exportstart :: $VMName : Exporting started"
	$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 rename folder to date of backup.
 
    ##  Check to see if directories exists before we move
    ##  and create them if they dont exist
 
    if (![IO.Directory]::Exists("$dest\$VMName"))
    {
          [IO.Directory]::CreateDirectory("$dest\$VMName")
    }
    if (![IO.Directory]::Exists("$dest\$VMName\$type"))
    {
          [IO.Directory]::CreateDirectory("$dest\$VMName\$type")
    }
    if (![IO.Directory]::Exists("$dest\$VMName\$type\$currentmonth"))
    {
          [IO.Directory]::CreateDirectory("$dest\$VMName\$type\$currentmonth")
    }
 
    ##  Move from Temp directory to correct directory, and rename to date of backup
 
    [IO.Directory]::Move("$dest\TmpDir\$VMName", "$dest\$VMName\$type\$currentmonth\$date")
    $exportdone = get-date -uformat "%Y-%m-%d %H:%M:%S"
    Add-Content $log "$exportdone :: $VMName : Exporting completed"
 
    ##  Bring virtual machine back online
 
    $VM.RequestStateChange($VMReturnState)
 
    ##  Compress folder with 7-zip (Normal compression level).
    ##  Make sure to have 7-Zip Command Line Version (7za) in C:\Windows\
    ##  7za version when this script was written: 7-Zip 4.65 2009-02-03
    ##  Copyright (c) 1999-2009 Igor Pavlov
 
    $startcompress = get-date -uformat "%Y-%m-%d %H:%M:%S"
    Add-Content $log "$startcompress :: $VMName : Compression started"
    & 7za a -mx3 -mmt -tzip $dest\$VMName\$type\$currentmonth\$date.zip $dest\$VMName\$type\$currentmonth\$date
 
    ##  Check to see if 7za succeeded.
    if ($?)  ## if previous command succeeded
    {
    ##  Delete folder if compression is finnished
    [IO.Directory]::Delete("$dest\$VMName\$type\$currentmonth\$date", $True)
    $allfinnish = get-date -uformat "%Y-%m-%d %H:%M:%S"
    Add-Content $log "$allfinnish :: Done with $VMName"
    Add-Content $log "Backup saved to: $dest\$VMName\$type\$currentmonth\$date.zip"
    }
    else
    {
    $allfinnish = get-date -uformat "%Y-%m-%d %H:%M:%S"
    Add-Content $log "$allfinnish :: $VMName : An error occured during compression!"
    }
  }
  echo "Done with $VMName $date"
}
$allfinnish = get-date -uformat "%Y-%m-%d %H:%M:%S"
Add-Content $log "$allfinnish :: Backup finnished"
Add-Content $log " "	
 
##  Cleanup previous month backups every 28th of current month
 
if ($currentmonth -eq 1)
{
    $prevmonth = 12
}
else
{
    $prevmonth = $currentmonth - 1
}
 
if (($currentday -eq 28) -and [IO.Directory]::Exists("$dest\$VMName\$type\$prevmonth"))
{
    Add-Content $log "$allfinnish :: Deleted old backups ($dest\$VMName\$type\$prevmonth)"
    [IO.Directory]::Delete("$dest\$VMName\$type\$prevmonth", $True)
}
 
##  Email the logfile
$file = $log
 
$smtpServer = $server
$msg = new-object Net.Mail.MailMessage
$att = new-object Net.Mail.Attachment($file)
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
 
$msg.From = $from
$msg.To.Add("$recipient")
$msg.Subject = "$type Backup ($allfinnish)"
$msg.Body = "Attached Logfile: $log"
$msg.Attachments.Add($att)
 
$smtp.Send($msg)
 
$att.Dispose()

17 Responses to “ Backup VMs in a Hyper-V Cluster (Core-version) ”

  1. Matjaz says:

    Hi!

    Is this script working with online virtual machine, or they’re in saved status when the process of exporting is active? How big TEMP place do you use for your environment. I’ve for about 10 servers W2k8 R2 in production, so i’m not sure about sizing of destination. For some virtuals i’ve fixed disks, and for some also physical access to disk. Is this script suuporting this scenario also with exclude physical disk??? Can you maybe provide option that i could exclude some virtual server from backup. Thanks in advance.

  2. Niklas says:

    1. The script will save the state of online machines for about 5 minutes.
    2. Then it will export them.
    3. Bring them back online.
    4. And in the background it will compress the VHD file using 7-zip.

    This script should work with whatever setup you have for you Hyper-V environment, clustered or none-clustered, fixed or dynamic disks doesn’t matter. It will export them just like they are setup. It will not convert a fixed disk to a dynamic disk or vice-versa.

    My environment uses a quite large NAS, about 6TB of space. But you will not have to worry about the tmp directory too much. It is only being used once per machine, and its being cleared whenever its done exporting one machine. So I would say that the tmp location needs to be as big as your largest VHD file.

    BTW, I have updated the script with some various fixes here and there. Please use the new version above.

  3. Niklas says:

    I have added your request to exclude some virtual machines. So the script has been updated once again.

  4. 2life says:

    Thanks for the script! This is what I need. But in the code you have two errors. This line 1931 ((“ElementName Name” should be replaced by “ElementName Name”), and 127 (extra character “&” before 7za). And do not come log e-mail.

  5. 2life says:

    Sorry, line 31)

  6. Niklas says:

    Thank you. Its now corrected.

    (The html editor converted “&” to &amp ; and “<” to &lt ; etc… thats why it wasn’t working.)

    Its now fixed.

  7. 2life says:

    E-mail send is ok. Problem with my gateway. Script is very good for everyday backup! I’m using this script for my job.

  8. wannascript says:

    Thank you for the great script!! Its almost exactly what I needed.
    I dont know scripting, would it be difficult to change this script to have a backup retetion time instead of deleting everything from the previous month? Maybe set it for a retention of 60 days or 14 days?
    Thank you

  9. jirm says:

    Hi Niklas.
    Sorry about my english (im spanish)

    I have read your script approach and seems awesome view of that problem.
    I find (and googgled too) for several hours for one really solution for backupU/restore host based VM in hyper-v server R2 in failover (CSV storage) environments without so much luck.
    I found several powershell scripts but dont have rigth solution for CSV host VM backing up (i think better is use diskshadow.exe) because diskshadow dont do consistent state for VM in CSV environments.
    I found one interesting treath that seems make a solution with using a VB6 tool (see: http://social.technet.microsoft.com/Forums/en/winserverhyperv/thread/6957409b-c348-47a8-8ac7-f3ece3c9fbee).

    I have one problem (one more…) when trying to run this tool in hyper-v server because hyper-v server dont have vb6 runtime installed…

    I think that if we can combinate all together (your script+the vb toool for diskshadow in csv) we can make a generic script for bakup/restore “all” hyper-v envioronments in failover cluster hyper-v (with CSV storage of course).

    Thank you

  10. Niklas says:

    wannascript: I’m glad you like it. Your request wouldn’t require much time to add. I can make you a custom script for that. I have your email.

    jirm: I will look into that at some point. I will however not update the script with the VB6 tool, I will probably just write a new one. So make sure you take a look at my blog every now and then..

  11. Arnaud Bigot says:

    Hi and THX for this great script !
    is it possible to stop VMs before exporting instead of saving state ?

    regards,

  12. Alireza says:

    Thank you for the great script! I’ve tested your script today und every thing seems to be OK. The script created the folders with the name of my VM and subfolders, TMPDir and the log file. But the folder VM-name\daily7\2010-07-07-Wensday.zip is empty:
    >>>>>>>>>>>>>>>>>>>>
    __GENUS : 2
    __CLASS : __PARAMETERS
    __SUPERCLASS :
    __DYNASTY : __PARAMETERS
    __RELPATH :
    __PROPERTY_COUNT : 2
    __DERIVATION : {}
    __SERVER :
    __NAMESPACE :
    __PATH :
    Job : \\Hyper-V-Server\root\virtualization:Msvm_ConcreteJob.InstanceID=”
    D500AA38-538D-4EB6-A11E-52DF794A30BF”
    ReturnValue : 4096
    Saving the state of VM_Gast – 2010-07-07-Mittwoch
    Exporting the VM: VM_Gast
    1
    Name : VM_Gast
    Parent : Hyper_V
    Exists : True
    Root : \\Backup-Server\VHD_Backup$
    FullName : \\Backup-Server\VHD_Backup$\Hyper_V\VM_Gast
    Extension :
    CreationTime : 07.07.2010 11:43:05
    CreationTimeUtc : 07.07.2010 09:43:05
    LastAccessTime : 07.07.2010 11:43:05
    LastAccessTimeUtc : 07.07.2010 09:43:05
    LastWriteTime : 07.07.2010 11:43:05
    LastWriteTimeUtc : 07.07.2010 09:43:05
    Attributes : Directory
    BaseName : VM_Gast
    Mode : d—-
    Name : Daily
    Parent : VM_Gast
    Exists : True
    Root : \\Backup-Server\VHD_Backup$
    FullName : \\Backup-Server\VHD_Backup$\Hyper_V\VM_Gast\Daily
    Extension :
    CreationTime : 07.07.2010 11:43:05
    CreationTimeUtc : 07.07.2010 09:43:05
    LastAccessTime : 07.07.2010 11:43:05
    LastAccessTimeUtc : 07.07.2010 09:43:05
    LastWriteTime : 07.07.2010 11:43:05
    LastWriteTimeUtc : 07.07.2010 09:43:05
    Attributes : Directory
    BaseName : Daily
    Mode : d—-
    Name : 07
    Parent : Daily
    Exists : True
    Root : \\Backup-Server\VHD_Backup$
    FullName : \\Backup-Server\VHD_Backup$\Hyper_V\VM_Gast\Daily7
    Extension :
    CreationTime : 07.07.2010 11:43:05
    CreationTimeUtc : 07.07.2010 09:43:05
    LastAccessTime : 07.07.2010 11:43:05
    LastAccessTimeUtc : 07.07.2010 09:43:05
    LastWriteTime : 07.07.2010 11:43:05
    LastWriteTimeUtc : 07.07.2010 09:43:05
    Attributes : Directory
    BaseName : 07
    Mode : d—-
    __GENUS : 2
    __CLASS : __PARAMETERS
    __SUPERCLASS :
    __DYNASTY : __PARAMETERS
    __RELPATH :
    __PROPERTY_COUNT : 2
    __DERIVATION : {}
    __SERVER :
    __NAMESPACE :
    __PATH :
    Job : \\Hyper-V-Server\root\virtualization:Msvm_ConcreteJob.InstanceID=”
    1C33D9F2-D1BE-4E9E-9A06-E806BF128ACB”
    ReturnValue : 4096

    7-Zip (A) 4.65 Copyright (c) 1999-2009 Igor Pavlov 2009-02-03
    Scanning
    Creating archive \\Backup-Server\VHD_Backup$\Hyper_V\VM_Gast\Daily7\2010-07-07-Mit
    twoch.zip

    Everything is Ok
    Done with VM_Gast 2010-07-07-Mittwoch
    >>>>>>>>>>>>>>>>>>>>>>>>>>

    Do you have any idea?
    Thank you in advance!

  13. nus says:

    Thanks a lot for this beautiful script

  14. hal says:

    Question: Script seems to run perfectly when run from the command line. However, via scheduler, it seems as though the request to put the VM into the saved state is ignored. The script just sits in the loop until you kill it.
    I have even tried to run the scheduled task as admin with the same result. Thoughts?

  15. Conrad says:

    Great Script ! Thx for sharing

  16. Brock says:

    Am I correct in that this script essentially automates Saving the state of the VM (which essentially turns it OFF) and Exporting the VM and turning it back on?

    Not exactly the same as backing up CSV with a CSV capable VSS writter.

  17. Buen says:

    This script works perfectly, but how do I restore ?
    With the import function of the Hyper-V console.

Leave a Reply