Export Hyper-V Configuration Using PowerShell
When Windows 8 and Windows Server 2012 were released, we also received a new PowerShell module. Within this module are many cmdlets that are designed to make it easy to manage Hyper-V hosts and virtual machines directly from PowerShell. Many of these cmdlets and command line versions of functionality that exists within the graphical Hyper-V manager. But sometimes, even these cmdlets may not meet your needs. As a case in point, consider the Export-VM cmdlet. This cmdlet will export a virtual machine to disk including its disk files and snapshots. In other words, a backup.
Using PowerShell to Export a Hyper-V Configuration
I’m assuming that if you are running Hyper-V in a production environment, then you probably have invested in a backup solution. What I want to demonstrate in this article isn’t intended to replace those products, but rather supplement them. If you run a smaller shop, a lab environment, or client Hyper-V on a Windows 8 or later desktop, then this article may be especially handy.
The problem is that when you use the Export-VM cmdlet, you get everything and given the size of the virtual machine hard drives and snapshots, this process may take some time to complete. But perhaps you only want to export the configuration itself? I was working on this problem when I came across someone with this exact issue. He wanted to export the virtual machine configuration so that he could import it later.
The configuration that you see when you run Get-VM or look at a virtual machine settings in Hyper-V Manager are stored in an XML file. The file location is included in the virtual machine object.
1 2 3 4 |
PS C:\> $vm = Get-VM chi-dc01 PS C:\> $vm.ConfigurationLocation D:\vm\CHI-DC01 PS C:\> |
The name of the file is the same as the virtual machine’s id.
1 2 3 4 5 |
PS C:\> $vm.id Guid ---- fc070f20-cf83-41bb-9156-85e2bbe73437 |
It is pretty easy to get that file with PowerShell.
1 2 3 4 5 6 7 |
PS C:\> dir $vm.configurationlocation -filter "$($vm.id).xml" -recurse Directory: D:\vm\CHI-DC01\Virtual Machines Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 10/13/2014 12:17 PM 52612 FC070F20-CF83-41BB-9156-85E2BBE73437.XML |
The file should exist in a sub-folder called Virtual Machines, but sometimes it is not. There is also a possibility there might be multiple XML files, so I do this to get the full file name.
1 2 3 4 5 6 |
PS C:\> $config = dir $vm.configurationlocation -filter "$($vm.id).xml" -recurse | Select -first 1 - ExpandProperty fullname PS C:\> PS C:\> $config D:\vm\CHI-DC01\Virtual Machines\FC070F20-CF83-41BB-9156-85E2BBE73437.XML PS C:\> |
Next, I need to create the destination folder and copy the xml file to it.
1 2 |
PS C:\> mkdir "e:\exports\$($vm.name)\Virtual Machines" PS C:\> copy $config -Destination "e:\exports\$($vm.name)\Virtual Machines" -PassThru |
In terms of a quick and easy export or backup that’s all there is to it. But I’m always thinking about what other requirement someone might have. Assuming you might import the configuration file, it might be helpful to provide a new name for the virtual machine. Or remove hard drive references so that if you import on a different Hyper-V host you don’t get ugly errors. Or remove snapshot references. Plus, you most likely want to do this from the comfort of your desktop. So I created a PowerShell function called Export-VMConfiguration.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
#requires -version 3.0 Function Export-VMConfiguration { <# .Synopsis Export Hyper-V configuration file. .Description This command will export a Hyper-V virtual machine's configuration file to a new location. You can use this as an alternative to Export-VM which will also export hard drives and snapshots. Sometimes you just want the configuration file. The command has options to provide a new name, as well as remove hard drive or snapshot references. This can be helpful if you plan on importing the configuration later as the basis for a new virtual machine. The path must exist and is relative to the computer. The command will create a subfolder for each virtual machine under this path. The path must be a local, fixed drive. You cannot specify a UNC or shared drive. .Example PS C:\> get-vm chi* -computername chi-hvr2 | export-vmconfiguration -path d:\exports -verbose This command will export, or backup, the configuration files for all virtual machines that start with CHI* to a new location on CHI-HVR2. .Example PS C:\> Export-VMConfiguration chi-test01 -Path E:\exports -Verbose -NewName CHI-TEST2 -RemoveDrives -RemoveSnapshots This command will get the CHI-TEST01 virtual machine on the local host and export its configuration. The new configuration will reflect a new name and remove existing drives and snapshots. .Notes Last Updated: October 13, 2014 Version : 1.0 Learn more: PowerShell in Depth: An Administrator's Guide (http://www.manning.com/jones6/) PowerShell Deep Dives (http://manning.com/hicks/) Learn PowerShell in a Month of Lunches (http://manning.com/jones3/) Learn PowerShell Toolmaking in a Month of Lunches (http://manning.com/jones4/) **************************************************************** * DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED * * THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK. IF * * YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, * * DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING. * **************************************************************** .Link Get-VM Export-VM #> [cmdletbinding(DefaultParameterSetName="Name")] Param( [Parameter(Position=0,Mandatory=$True,HelpMessage="Enter the name of a virtual machine", ValueFromPipeline=$True, ParameterSetName="Name")] [alias("vmname")] [ValidateNotNullorEmpty()] [string[]]$Name, [Parameter(Position=0,Mandatory=$True,ValueFromPipeline=$True, ParameterSetName="VM")] [Microsoft.HyperV.PowerShell.VirtualMachine[]]$VM, [Parameter(Mandatory=$True,HelpMessage="Enter the destination path")] [string]$Path, [Alias("cn")] [Parameter(ValueFromPipelineByPropertyName=$True)] [ValidateNotNullorEmpty()] [string]$Computername = $env:computername, [Alias("rename")] [string]$NewName, [alias("NoDrives")] [switch]$RemoveDrives, [alias("NoSnapshots")] [switch]$RemoveSnapshots ) Begin { Write-Verbose -Message "Starting $($MyInvocation.Mycommand)" Write-Verbose "Using parameter set $($PSCmdlet.ParameterSetName)" } #begin Process { #create a scriptblock so this can run remotely $sb = { [cmdletbinding()] Param( [string]$VerbosePreference ) #$VerbosePreference = "continue" if ($using:vm) { Write-Verbose "Processing VM: $($using:VM.Name)" $myVM = $using:vm } else { Write-Verbose "Processing $using:name" #get the virtual machine Try { $myvm = Get-VM -Name $using:name } Catch { Write-Warning "Failed to find virtual machine $using:name on $($env:computername)" } } #else if ($myVM) { foreach ($v in $myVM) { #proceed if we have a virtual machine object #create the target configuration path $config = dir $v.configurationlocation -filter "$($v.id).xml" -recurse | Select -first 1 -ExpandProperty fullname Write-Verbose "Processing configuration file: $config" If ($config) { #define the target path #use the New name if specified if ($NewName) { $vmname = $NewName } else { $vmname = $v.name } $destination = Join-path $using:path "$vmname\Virtual Machines" Write-Verbose "Testing $destination" if (-Not (Test-Path -Path $destination)) { #create the folder Try { Write-Verbose "Creating $destination" New-Item -Path $destination -ItemType directory -ErrorAction stop | Out-Null } Catch { Throw } } #if destination doesn't exist if (Test-Path -Path $destination ) { Write-verbose "Exporting $config to $destination" Try { Copy-item $config -destination $destination -errorAction Stop -passthru } Catch { Throw } #post processing Write-Verbose "Post processing" $newConfig = Join-Path -Path $destination -ChildPath "$($v.id).xml" [xml]$new = Get-Content -path $newConfig $prop = $new | select-xml -XPath "//properties/name" #insert a note Write-Verbose "Inserting a note" $notes = $new.selectNodes("//notes") $noteText = @" Exported $(Get-date) from $($prop.node.innertext) $($notes[0].InnerText) "@ $notes[0].InnerText = $noteText if ($NewName) { #rename Write-Verbose "Renaming to $Newname" $prop.node.InnerText = $NewName } #if new name #remove drives if ($RemoveDrives) { Write-Verbose "Removing drive references" $drivenodes = $new | Select-Xml "//*[starts-with(name(),'drive')]" foreach ($item in $drivenodes) { $pn = $item.node.parentnode $pn.RemoveChild($item.node) | Out-Null } } #remove drives #remove snapshots if ($RemoveSnapshots) { Write-Verbose "Removing snapshot references" $snapnodes = $new | Select-Xml "//snapshots" $snapnodes.node.RemoveChild($snapnodes.node.list) | Out-Null $pii= $new | Select-Xml "//parent_instance_id" $new.configuration.properties.RemoveChild($pii.node) | Out-Null } #remove snapshots #save revised configuration Write-Verbose "Saving XML to $newconfig" $new.save($newconfig) } #if destination verified } #If configuration file found else { Write-Warning "Failed to find a configuration file for $($v.name)" } } #foreach } #if VM } #close scriptblock Invoke-Command -ScriptBlock $sb -ComputerName $Computername -ArgumentList $VerbosePreference } #process End { Write-Verbose -Message "Ending $($MyInvocation.Mycommand)" } #end } #end funtion |
The command has complete help and examples.
The heart of the command is a series of steps to modify the XML document and save it to the new location. For example, one of the tasks I wanted to accomplish was to add a note to the configuration that reflected this was exported. PowerShell makes it an easy chore to modify XML documents.
1 2 3 4 5 6 7 8 9 10 11 12 |
[xml]$new = Get-Content -path $newConfig $prop = $new | select-xml -XPath "//properties/name" #insert a note Write-Verbose "Inserting a note" $notes = $new.selectNodes("//notes") $noteText = @" Exported $(Get-date) from $($prop.node.innertext) $($notes[0].InnerText) "@ $notes[0].InnerText = $noteText |
Or I can rename and remove drives.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
if ($NewName) { #rename Write-Verbose "Renaming to $Newname" $prop.node.InnerText = $NewName } #if new name #remove drives if ($RemoveDrives) { Write-Verbose "Removing drive references" $drivenodes = $new | Select-Xml "//*[starts-with(name(),'drive')]" foreach ($item in $drivenodes) { $pn = $item.node.parentnode $pn.RemoveChild($item.node) | Out-Null } } #remove drives |
The function uses PowerShell remoting so that you can run the command against a Hyper-V host from your desktop. In fact, your desktop doesn’t need any of the Hyper-V commands because all of those are running on the remote server. This also means that the path you specify, which must already exist, is relative to the remote computer. The export process will create a subfolder under this path for each virtual machine. If you take advantage of the rename process, then the subfolder will reflect the new name.
Now I can export, or backup, just the configuration for select virtual machines on my Hyper-V server.
1 |
PS C:\> Export-VMConfiguration -Name CHI* -Path D:\exports -Computername chi-hvr2 –Verbose |
Here’s the result:
Or I can get a virtual machine and pipe it to my export command.
1 |
PS C:\> get-vm chi-win81 -ComputerName chi-hvr2 | Export-VMConfiguration -path d:\exports -NewName CHI-ClientBase -RemoveDrives –RemoveSnapshots |
This will get a single virtual machine, rename it to CHI-ClientBase, and remove hard drive and snapshot references.
You are welcome to use this function as-is after you test it in a non-production environment. While it works for me, there’s no way I can guarantee it will work for you in your environment. Or you might want to use it as a model or source material for your own Hyper-V and PowerShell projects.