Script to Recover a Hyper-V Guest

Recently, a colleague of mine had a customer that had a Hyper-V server whose operating system had failed. They did not have any backups of the Operating system and had not taken any backups of the Hyper-V guests using Export\Import (I won't bang on about Best Practices here; I think you probably guess what I would say if I did though :-)). They had access to the disks on which the Hyper-V guest VHD's and configuration xml files were located and intended re-installing the the OS and Hyper-V Role. They were asking whether there is a method of recovering the Hyper-V guests to the new server. Fortunately another one of my colleagues suggested the method detailed on this great blog here https://blogs.msdn.com/b/robertvi/archive/2008/12/19/howto-manually-add-a-vm-configuration-to-hyper-v.aspx which I am led to believe worked.

Funnily enough, I had to use parts of this same method to recover a couple of Hyper-V guests on my PC when I had done some disk volume management and moved the VHD's and config files around a bit, which caused permissions changes obviously; honestly guv'nor I had forgotten I had these guests running from the said volume (I know Best Practice and all that ;-) , but it is just my laptop and not a production service providing environment).

My colleague then commented that it would be cool if such a process were to be scripted. Tada ...... "his wish was my command". To be honest I had put this on my list of things to do, to get me to extend my scripting skills from VBScript to Powershell and having to actually go through the process myself made me agree with my colleague a script would be so much easier and useful to anybody else having to do it. There may actually be another script out there that does this, but at least I actually got to do some Powershell scripting :-) . It may not be the most correctly written script, but it is my first crack of the whip (as the saying goes). There is currently no error trapping and\or exception handling, but I have provided some command line markers that should make it easier to determine at which points it fails (if it does). I intend adding error handling later, but if I didn't share the script now it would always be sat on my laptop as a work in progress without being used (and tested).

What I must say is, that after recovering the Hyper-V Guest using this script, the Hyper-V Guest is in an unsupported state and it is highly recommended that before starting the guest OS, you should use Hyper-V Manager to Export the Guest, remove it from Hyper-V Manager and then Import again into Hyper-V Manager. This will set all the permissions on all files as they should be.

This script definitely works with Windows Server 2008 R2 Hyper-V and I suspect it also works on Windows Server 2008 (I don't have a n environment to check this though). The script utilises icacls and mklink (both provided as command line applications with the OS), because I was unable to use inbuilt Powershell functionality without some deep coding knowledge (not quite there yet) requiring that I write some additional libraries that someone has actually already made available here https://pscx.codeplex.com/ . Basically, the Powershell cmdlet Set-ACL is not able to change owner when setting permissions on certain types of files and folders, this requires some special jiggery-pockery of which I am not yet averse. You may ask why I didn't decide to use the PSCX myself; well I thought it best to produce a script that is standalone and does not require anything that is not already available through Powershell.

Additionally, the script will only work correctly if the volume paths (i.e. drive letters) are the same as from before the issue that caused the Hyper-V guests to be disconnected from the Hyper-V Host Management Services.

The only other thing you will need to do when using this script is to open Powershell with elevated privileges and to run "set-executionpolicy remotesigned".

It may be worth checking the formatting of the code if you copy and paste it from this page as some of the lines have wrapped to the next line on this page. Alternatively, just download the txt file at the bottom of this post.

Enjoy. Oh and please provide feedback if it works or not for you.

####################################################################
# Recover-VM.ps1
#
# Syntax: Recover-VM.ps1 "<drive:\path\VM_Config_File_as_GUID.xml>"
# (Use quotes in the argument)
#
# Example: Recover-VM.ps1 "v:\VirtualMachine1\Virtual Machines\7660AA46-BA41-4171-8820-CDD7C71050A0.xml"
#
# Purpose: Creates Symbolic Links and sets the required permissions
# to make a Hyper-V Virtual Machine available in Hyper-V
# Management Console. This script is intended for use in
# recovery scenario where a Virtual Guests files have been
# manually moved to another Virtual Server, but the volume
# drive mappings are the same, only.
# The methodology that this script employs is as discussed
# in the below blog.
# It should be noted that once this script has been used
# the Virtual Guest is in an unsupported state and should
# be exported and re-imported in Hyper-V Manager to
# correctly set all the Virtual Machines permissions.
#
# Params: The drive, folder and filename of the Virtual Machine
# xml configuration file.
#
# Req: Windows 2008 or above, Powershell V2, mklink, icacls.
# run "set-executionpolicy remotesigned" in Powershell
#
# Ref: https://blogs.msdn.com/b/robertvi/archive/2008/12/19/howto-manually-add-a-vm-configuration-to-hyper-v.aspx
# https://blogs.technet.com/b/carlh
#
# Author: CarlH
#
# Myself or my employer do not warantee this script in any way and use
# of it is entirely at the users own risk. The script is intended to
# Recover Virtual Machines only in a recovery scenario, and such VM's
# should be exported and imported onto the Hyper-V server to ensure
# a supported state.
#
# Version: 1.0 - First cut
#
####################################################################

Param($VMConfigFile)

Write-Host ""
Write-Host "---------------Recover-VM Script Start"
$SystemDrive = (Get-item env:systemdrive).Value
$VMFolder = (Get-Item $VMConfigFile).directoryname
$VMFolder = (Get-Item $VMFolder).parent
$VMFolderName = $VMFolder.fullname
Write-Host ""

#Read from the VM Configuration file. VM GUID for Service account. VM Name.
Write-Host "---------------Reading VM Configuration File"
[xml]$VMXML = Get-Content $VMConfigFile
$VMSvcAccount = $VMXML.configuration.properties.global_id."#text"
$VMName = $VMXML.configuration.properties.name."#text"
Write-Host "---------------Completed reading VM Configuration File"
Write-Host ""

#This function takes a file name including the full path as a ThisItem string parameter and gets the current ACL.
#It adds the VM Service account to the current ACL and then Sets the new ACL
function SetACL
{
Param($ThisItem)
Write-Host ""
Write-Host "---------------Setting Permissions for $ThisItem"
Write-Host ""
$Acl = Get-Acl $ThisItem
$AccessRule = New-Object system.security.accesscontrol.filesystemaccessrule("NT VIRTUAL MACHINE\$VMSvcAccount","FullControl","Allow")
$Acl.SetAccessRule($AccessRule)
Set-Acl $ThisItem $Acl
Write-Host ""
Write-Host "---------------Finished Permissions for $ThisItem"
}

#This Function uses the cmd line utility mklink to create a symbolic link of the physical file passed to it in the HardFile parameter as a string
#The LinkLocation string parameter defines what folder the link will be created in under systemdrive\programdata\Microsoft\Windows\Hyper-V\

function MkSymLink
{
Param($HardFile, $LinkLocation)
Write-Host ""
Write-Host "---------------Creating Symbolic Link for $HardFile"
Write-Host ""
$LinkFile = (get-item $HardFile).name
#Write-Host "%systemdrive%\programdata\Microsoft\Windows\Hyper-V\$LinkLocation\$LinkFile" $HardFile
cmd /c mklink "%systemdrive%\programdata\Microsoft\Windows\Hyper-V\$LinkLocation\$LinkFile" $HardFile
Write-Host ""
Write-Host "---------------Finished Symbolic Link for $HardFile"
}

#Get the VM config xml Fully Qualified Name and pass it to the MKSymLink function with the location for the for storing the Symbolic Link
$VMFileString = (get-item $VMConfigFile).name
MkSymLink -HardFile $VMConfigFile -LinkLocation "Virtual Machines"

#Call the SetACL function and pass it the FQN of the Symbolic Link to which we need to set the ACL
$ConfigFileSymLink = "$SystemDrive\programdata\Microsoft\Windows\Hyper-V\Virtual Machines\$VMFileString"
SetACL -ThisItem $ConfigFileSymLink

#Get the FQN of the boot VHD from the VM Config xml file passed into the script. Then pass this to the SetACL function
$VMVHDBootFile = $VMXML.configuration.'_83f8638b-8dca-4152-9eda-2ca8b33039b4_'.controller0.drive0.pathname."#text"
SetACL -ThisItem $VMVHDBootFile

#Build a string that defines the VM Service Account and use it with the cmd line utility icacls to set the permissions for all folders and files of the VM
Write-Host ""
Write-Host "---------------Setting Permissions for Folder $VMFolderName"
Write-Host ""
$VMSvcAccountFQN = 'NT VIRTUAL MACHINE\'+$VMSvcAccount
cmd /c icacls ""$VMFolderName"" /T /grant ""$VMSvcAccountFQN""':(F)'
Write-Host ""
Write-Host "---------------Finished Permissions for Folder $VMFolderName"

#Discover if there are any Snapshots. If so, for each one, create a Symbolic link for each by passing the FQN of the snapshot and snapsot link location
#Also, call SetACL to set permissions for the VM Services account on the Symbolic Link
foreach ($Thing in (Get-ChildItem $VMFolderName))
{
If ($Thing.name -eq "Snapshots")
{
$SnapshotsFolder = $Thing.Fullname
foreach ($Thing2 in (Get-ChildItem $SnapshotsFolder))
{
If ($Thing2.extension -eq ".xml")
{
$VMSnapshotFileString = $Thing2.name
MkSymLink -HardFile $Thing2.fullname -LinkLocation "Snapshots"
$SnapshotFileSymLink = "$SystemDrive\programdata\Microsoft\Windows\Hyper-V\Snapshots\$VMSnapshotFileString"
SetACL -ThisItem $SnapshotFileSymLink
}
}
}
}
Write-Host ""
Write-Host "----------------End Script Recover-VM.ps1"

 

Recover-VM.ps1.txt