This is kinda cool, but I got published in InfoWorld, in a roundabout manner. J. Peter Bruzzese writes a column for InfoWorld on enterprise Windows. His latest column is about exporting Hyper-V virtual machines using PowerShell. In Windows Server 2012 R2 (and Windows 8.1) you can export a virtual machine even while it is running. Peter wanted to demonstrate with a weekly and monthly scenario using PowerShell to export virtual machines to a new folder and also delete older versions. The end result would be a set of 4 weekly folders and 2 monthly folders. But he needed help with some of the PowerShell so I pulled together a script which is linked in the InfoWorld article.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
The script I wrote is a bit more complicated than what Peter originally envisioned. Now, I doubt his goal could be accomplished with a one-liner. So since I needed to write a script, I took the time to make it robust with items such as error handling and parameter validation. I only wanted to develop the script once so why not be thorough?
As I as finishing up the script to Peter's requirements, I realized this could also be tackled using a PowerShell workflow. One of the limitations in the original script is that it needs to run on the Hyper-V server. I didn't include any provision for connecting to a remote server. I also recognized that exporting multiple virtual machines could be done in parallel. Although my original script allows the use of background jobs which is sort of like running in parallel. But I thought a workflow version might at least be educational. Here is the Export-MyVM workflow.
#requires -version 3.0 #requires -module Hyper-V Workflow Export-MyVM { Param( [Parameter(Position=0,Mandatory=$True, HelpMessage="Enter the virtual machine name or names")] [ValidateNotNullorEmpty()] [Alias("name")] [string[]]$VM, [Parameter(Position=0,Mandatory=$True, HelpMessage="Enter the root backup path")] [ValidateNotNullorEmpty()] [string]$Path, [switch]$Monthly ) Write-Verbose -Message "Starting $workflowcommandname" #define some variables if we are doing weekly or monthly backups if ($monthly) { $type = "Monthly" $retain = 2 } else { $type = "Weekly" $retain = 4 } Write-Verbose -message "Processing $type backups. Retaining last $retain." #manage folders #get backup directory list Try { Write-Verbose -Message "Checking $path for subfolders" #get only directories under the path that start with Weekly or Monthly $subFolders = Get-ChildItem -Path $path\$type* -Directory -ErrorAction Stop } Catch { Write-Warning "Failed to enumerate folders from $path. $($_.Exception.Message)" #bail out of the script return } #check if any backup folders if ($subFolders) { #if found, get count Write-Verbose -message "Found $($subfolders.count) folder(s)" #if more than the value of $retain, delete oldest one if ($subFolders.count -ge $retain ) { #get oldest folder based on its CreationTime property $oldest = $subFolders | Sort-Object -property CreationTime | Select-Object -first 1 Write-Verbose -message "Deleting oldest folder $($oldest.fullname)" #delete it $oldest | Remove-Item -Recurse -Force } } #if $subfolders else { #if none found, create first one Write-Verbose -Message "No matching folders found. Creating the first folder" } #create the folder #get the current date $now = Get-Date #name format is Type_Year_Month_Day_HourMinute $childPath = "{0}_{1}_{2:D2}_{3:D2}_{4:D2}{5:D2}" -f $type,$now.year,$now.month,$now.day,$now.hour,$now.minute #create a variable that represents the new folder path $newpath = Join-Path -Path $path -ChildPath $childPath Try { Write-Verbose -message "Creating $newpath" #Create the new backup folder $BackupFolder = New-Item -Path $newpath -ItemType directory -ErrorAction Stop } Catch { Write-Warning -message "Failed to create folder $newpath." throw $_ #failed to create folder so bail out of the script Return } #export VMs if ($BackupFolder) { #export each machine in parallel foreach -parallel ($item in $VM) { Write-Verbose -Message "Exporting $item" #define a hashtable of parameters to splat to Export-VM $exportParam = @{ Path = $newPath Name=$item ErrorAction="Stop" } Try { Export-VM @exportParam } Catch { Write-Warning "Failed to export virtual machine(s)." Throw $_ } } #foreach parallel } #if backup folder exists Write-Verbose -Message "Ending $workflowcommandname" } #close workflow
Most of the code is the same as the original script, although I removed the WhatIf parameter. You can't use SupportsShouldProcess in a workflow and I didn't have the time to fully write my own. The only code that is really workflow specific is this:
#export each machine in parallel foreach -parallel ($item in $VM) { Write-Verbose -Message "Exporting $item" #define a hashtable of parameters to splat to Export-VM $exportParam = @{ Path = $newPath Name=$item ErrorAction="Stop" } Try { Export-VM @exportParam } Catch { Write-Warning "Failed to export virtual machine(s)." Throw $_ } } #foreach parallel
Perhaps the biggest advantage is that with a workflow I get automatic support for background jobs and remoting. Now I can execute the workflow against the Hyper-V server.
PS C:\> export-myvm -name 'chi-client02','chi-dctest' -path d:\backup -Verbose -AsJob -PSComputerName chi-hvr2.globomantics.local Id Name PSJobTypeName State HasMoreData Location Command -- ---- ------------- ----- ----------- -------- ------- 100 Job100 PSWorkflowJob Running True chi-hvr2.globoman... export-myvm PS C:\> get-job 100 -IncludeChildJob Id Name PSJobTypeName State HasMoreData Location Command -- ---- ------------- ----- ----------- -------- ------- 100 Job100 PSWorkflowJob Running True chi-hvr2.globoman... export-myvm 101 Job101 PSWorkflowJob Running True chi-hvr2.globoman... Export-MyVM
And I could still create a PowerShell scheduled job on my computer to run this workflow.
By the way, I'm sure you are aware that there are plenty of Hyper-V backup products from companies like Altaro, Veeam and Unitrends (all of whom help support my blog). Some of them even have free versions of their products. So while you can use PowerShell to export VMs that doesn't mean you should. Although I can see value for a quick and dirty backup. Ultimately, I suppose it is a good thing to have options.
Enjoy.
Dear Jeffery,
You did an amazing job in giving a direction in solving every administrator’s headache – automate a backup process. I have just one question. I can’t realize how to make it work in network environment. I get this 0*80070005 access denied error when trying to export VM to shared folder. I understand that this happens because I didn’t point the credetials to accsess shared folder. How can I specify credentials to access to my shared folder for “-path” parameter? Couple hours of googling brought nothing. Thanks in advance!
Sergio
If you are running this from a client pointed to a Hyper-V box and trying to backup to a network share that would account for the access denied. This is the 2nd hop issue with PowerShell remoting. By default you can’t remote to one machine and then hop from there to another remote machine. For security reasons that is not allowed. Unless you enable CredSSP which is the official solution.
Thank you so much for your advice. I’ll try to dig something in this direction. Maybe I’ll also try to automate moving files to network folder via script . But anyway your script is brilliant and advices very helpful. Thanks!
Or run the script ON the server as a PowerShell scheduled job. Thanks for reading.