Part 1: Creating Hyper-V Guest VM From a Virtual Disk
# createHyperVGuestVmFromDisk.ps1
# Version 0.02
# The intent of this script is to create a Hyper-V Guest VM basing on existing backup VHDX file(s)
$sourceVhdx='\\FILESERVER008\_Images\Windows2019_Image.vhdx'
$destinationFolder='\\VIRTUALMACHINES\STAGE'
$network='External-Connection'
$vlan='3000'
$newVmName='TESTVM009'
$memory='8GB'
$cpus=4
$secureBoot=$true # Windows: True, Linux: False
$extraDisks=@($null) # $extraDisks=@('test')
$generation=2
$deleteSourceDisks=$true
$onlineVm=$true
function createHyperVGuestVmFromDisk{
param(
$sourceVhdx,
$newVmName,
$destinationFolder,
$memory='4GB',
$cpus=2,
$network,
$vlan,
$extraDisks=$null,
$generation=2,
$secureBoot=$false,
$onlineVm=$true,
$deleteSourceDisks=$false
)
$ErrorActionPreference = 'stop'
function confirmation($content,$testValue="I confirm",$maxAttempts=3){
$confirmed=$false
$cancelCondition=@('cancel','no','exit','nope')
$attempts=0
clear-host
write-host $($content|out-string).trim()
write-host "`r`nPlease review this content for accuracy.`r`n"
while ($attempts -le $maxAttempts){
if($attempts++ -ge $maxAttempts){
write-host "A maximum number of attempts have reached. No confirmations received!`r`n"
break;
}
$userInput = Read-Host -Prompt "Please type in this value => $testValue <= to confirm. Input CANCEL to skip this item";
if ($userInput.ToLower() -eq $testValue.ToLower()){
$confirmed=$true;
write-host "Confirmed!`r`n";
break;
}elseif($userInput.tolower() -in $cancelCondition){
write-host 'Cancel command received.'
$confirmed=$false
break
}else{
clear-host
$content|write-host
write-host "Attempt number $attempts of $maxAttempts`: $userInput does not match $testValue. Try again or Input CANCEL to skip this item`r`n"
}
}
return $confirmed
}
try{
$newFolder="$destinationFolder\$newVmName"
if(!(test-path $newFolder)){new-item -ItemType Directory -Path $newFolder -force}
$newVhdx="$newFolder\$newVmName`_disk0.vhdx"
if(!(test-path $newVhdx)){
New-Item -ItemType File -Path $newVhdx -Force # touch before copying contents
Copy-Item -Path $sourceVhdx -Destination $newVhdx
}else{
write-warning "Volume $newVhdx already exists. Thus, that VMDK will be used instead of a clone."
}
New-VM -Name $newVmName `
-MemoryStartupBytes $($memory/1) `
-BootDevice VHD `
-VHDPath $newVhdx `
-Path $newFolder `
-Generation $generation `
-Switch $network
if($vlan){Set-VMNetworkAdapterVlan -VMName $newVmName -Access -VlanId $vlan}
if($cpus -gt 1){Set-VMProcessor $newVmName -Count $cpus}
Set-VMProcessor $newVmName -CompatibilityForMigrationEnabled $true
if(!$secureBoot){Set-VMFirmware -VMName $newVmName -DisableSecureBoot}
# Adding disks (optional)
if($extraDisks){
for($i=0;$i -lt $extraDisks.count;$i++){
$extraDisk=$extraDisks[$i]
$isvalidDisk=if(test-path $extraDisk){$extraDisk}else{$null}
if($isvalidDisk){
$newDiskPath=(join-path $destinationFolder $vmName) + "\$vmName`_disk$($i+1).vmdk"
if(!(test-path $newDiskPath)){
New-Item -ItemType File -Path $newDiskPath -Force # touch before copying contents
Copy-Item -Path $extraDisk -Destination $newDiskPath
}else{
write-warning "Volume $newDiskPath already exists. Thus, that VMDK will be used instead of a copy."
}
Add-VMHardDiskDrive -VMName $newVmName -Path $newDiskPath
}else{
write-warning "Disk path '$extraDisk' in invalid."
}
}
}else{
write-host "$newVmName has no extra disks to attach."
}
$disksToRemove=[array]$extraDisks+$sourceVhdx|?{$_} # join string to array and remove empty entries
foreach($diskToRemove in $disksToRemove){
$confirmed=confirmation "Remove source disk $diskToRemove"
if($confirmed){
remove-item $diskToRemove -force
}else{
write-host "$diskToRemove NOT removed."
}
}
if($onlineVm){
start-vm $newVmName
}
return $true
}catch{
write-warning "$($error[0])"
return $false
}
}
createHyperVGuestVmFromDisk $sourceVhdx `
$newVmName `
$destinationFolder `
$memory `
$cpus `
$network `
$vlan `
$extraDisks `
$generation `
$secureBoot `
$onlineVm `
$deleteSourceDisk
Part 2: Adding New VM to Cluster
function addVmToCluster{
param($vmNames,$targetCluster)
$results=@()
foreach ($vmName in $vmNames){
try{
#Start-VM -Name $vmName -EA Stop
if(!$targetCluster){$targetCluster=(get-cluster -ea SilentlyContinue).Name}
if($targetCluster){
$null=Add-ClusterVirtualMachineRole -Cluster $targetCluster -VirtualMachine $vmName -EA Stop
$results+=[hashtable]@{$vmName=$true}
}else{
write-host "No clusters defined."
$results+=[hashtable]@{$vmName=$false}
}
$moved=if(get-cluster -ea SilentlyContinue){Move-ClusterVirtualMachineRole $newVmName}else{$false}
if($moved){write-host "'$newVmname' has been moved to $($moved.OwnerNode)"}
}catch{
write-warning "$($error[0])"
$results+=[hashtable]@{$vmName=$false}
}
}
return $results
}
addVmToCluster $newVmName
Possible Error Message:
Microsoft Hyper-V UEFI
Virtual Machine Boot Summary
1.SCSI Disk (0,0) the boot loader did not load an operating system
2. Network adapter (00155D406142) a boot image was not found
No operating system was loaded. Your virtual machine may be configured incorrectly. Exit and rec-configure your VM or click restart to retry the current boot sequence again.
Resolution:
The case where this error has been thrown has been associated with an incorrect virtual machine generation type. Hence, the resolution has been:
A. Convert Generation 2 machine type back to Generation 1 as the original source disk VM must match its re-creation.
B. The misconfigured VM must be ‘deleted’ and re-created as a Generation 1 VM.
# Converting Generation 2 virtual disk to Gen 1
$diskFile='\\VIRTUALMACHINES\HyperV\originalDisk_gen2.vhdx'
$fixedFile='\\VIRTUALMACHINES\HyperV\originalDisk_gen1.vhdx'
Convert-VHD -Path $diskFile -DestinationPath $fixedFile -VHDType Dynamic
Categories: