Update 11/25/2021: There’s anther variation of this function at: https://blog.kimconnect.com/powershell-create-hyper-v-guest-vm-from-virtual-disk-vhdx/
This function will dynamically detect source disks and clone each one. The down side to this method is that accessing the source cluster is a requirement to enable querying source VM objects.
# cloneVm.ps1
# Version 0.02
# Change these variables
$sourceVmName='WindowsGoldenImage'
$newVmNames=@(
'SERVER001',
'SERVER002',
'SERVER003'
)
$destinationFolder='C:\ClusterStorage\Volume5'
$memory='8GB'
$cpus=4
$network='IntranetZone'
$vlan='100'
$secureBoot=$false # Linux machines require this to be false - Windows can work with value of True
$extraDiskSize='200GB' # set this value to $Null if D: volume is not required
# Assuming single Vhdx file VM cloning
function createVmFromVhdx{
param(
$sourceVhdx,
$newVmName,
$destinationFolder,
$memory='4GB',
$cpus=2,
$network,
$vlan,
$extraDiskSize=$null,
$secureBoot=$false,
$addVmToCluster=$true,
$onlineVm=$true
)
$ErrorActionPreference = 'stop'
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){
Add-ClusterVirtualMachineRole -Cluster $targetCluster -VirtualMachine $vmName -EA Stop
$results+=[hashtable]@{$vmName=$true}
}else{
write-host "No clusters defined."
$results+=[hashtable]@{$vmName=$false}
}
}catch{
write-warning "$($error[0])"
$results+=[hashtable]@{$vmName=$false}
}
}
return $results
}
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 2 `
-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($extraDiskSize){
$diskFile=(join-path $destinationFolder $newVmName) + "\$newVmName`_disk1.vhdx"
NEW-VHD -Fixed $diskFile -SizeBytes (invoke-expression $extraDiskSize) -ea Stop
Add-VMHardDiskDrive -VMName $newVmName -Path $diskFile
}
if($addVmToCluster){
addVmToCluster $newVmname
$moved=if(get-cluster -ea SilentlyContinue){get-vm $newVmName|Move-ClusterVirtualMachineRole}else{$false}
if($moved){write-host "'$newVmname' has been moved to $($moved.OwnerNode)"}
}
if($onlineVm){
start-vm $newVmName
}
return $true
}catch{
write-warning "$($error[0])"
return $false
}
}
foreach($newVmName in $newVMNames){
createVmFromVhdx $sourceVhdx `
$newVmName `
$destinationFolder `
$memory `
$cpus `
$network `
$vlan `
$extraDiskSize `
$secureBoot `
$addVmToCluster `
$onlineVm
}
#####################
# Join domain
#
#####################
Add-Computer -DomainName 'intranet.kimconnect.com' `
-credential kimconnect.com\DomainAdmin `
-ComputerName $env:computernname `
-newname 'NEWCOMPUTERNAME' -restart
# cloneVm.ps1
# Version 0.01
# Change these variables
$sourceVmName='WindowsGoldenImage'
$newVmName='NewServerName'
$destinationFolder='C:\ClusterStorage\Volume5'
$memory='8GB'
$cpus=4
$network='IntranetZone'
$vlan='100'
$secureBoot=$false # Linux machines require this to be false - Windows can work with value of True
function cloneVM{
param(
$sourceVmName,
$newVmName,
$destinationFolder,
$memory='4GB',
$cpus=2,
$network,
$vlan,
$secureboot=$true
)
$ErrorActionPreference = 'stop'
function confirmation($content,$testValue="I confirm",$maxAttempts=3){
$confirmed=$false;
$attempts=0;
$content|write-host
write-host "Please 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";
if ($userInput.ToLower() -ne $testValue.ToLower()){
cls;
$content|write-host
write-host "Attempt number $attempts of $maxAttempts`: $userInput does not match $testValue. Try again..`r`n"
}else{
$confirmed=$true;
write-host "Confirmed!`r`n";
break;
}
}
return $confirmed;
}
try{
$newFolder="$destinationFolder\$newVmName"
if(!(test-path $newFolder)){
$null=new-item -ItemType Directory -Path $newFolder -force
write-host "Created New Folder: $newFolder"
}
$sourceDisks=(get-vhd (Get-VM $sourceVmName).VMid).Path
$newVhdxPaths=@{}
for ($i=0;$i -lt $sourceDisks.count;$i++){
$diskNumber='disk'+$i
if($sourceDisks.count -gt 1){
$sourceDisk=$sourceDisks[$i]
}
else{
$sourceDisk=$sourceDisks
}
$newVhdx="$newFolder\$newVmName`_$diskNumber.vhdx"
if(!(test-path $newVhdx)){
$null=New-Item -ItemType File -Path $newVhdx -Force # touch before copying contents
write-host "Copy-Item -Path $sourceDisk -Destination $newVhdx"
Copy-Item -Path $sourceDisk -Destination $newVhdx
}
else{
$confirm=confirmation "Destroy existing contents of $newVhdx ?"
if($confirm){
Copy-Item -Path $sourceDisk -Destination $newVhdx
write-host "Overwritten: $newVhdx" -ForegroundColor Yellow
}
else{
write-warning "Re-using the existing contents of: $newVhdx"
}
}
$newVhdxPaths[$diskNumber]=$newVhdx
}
write-host ($newVhdxPaths.GetEnumerator() | % { "$($_.Key)=$($_.Value)" })
pause
$createVmCommand="
New-VM -Name $newVmName ``
-MemoryStartupBytes $($memory/1) ``
-BootDevice VHD ``
-VHDPath $($newVhdxPaths['disk0']) ``
-Path $newFolder ``
-Generation 2 ``
-Switch $network"
write-host $createVmCommand
pause
Invoke-Expression $createVmCommand
if ($newVhdxPaths.Count -gt 1){
for ($j=1;$j -lt $newVhdxPaths.count;$j++){
$diskPath=$newVhdxPaths[$('disk'+$j)]
write-host "Attaching $diskPath..."
Add-VMHardDiskDrive -VMName $newVmName -path $newVhdxPaths[$newDiskNumber]
}
}
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}
Start-VM -Name $newVmName
VMConnect.exe $env:computername $newVmName
}
catch{
write-warning "$($error[0])"
}
}
cloneVM $sourceVmName $newVmName $destinationFolder $memory $cpus $network $vlan $secureBoot
# Troubleshooting:
# Error occurs when 'Path' was not specified during VM Creation, then such VM was being added to a cluster
#The path where the virtual machine hard disk is stored, TESTWINDOWS.vhdx, is not on storage managed by this failover cluster. Verify that the path is #accessible from all nodes of the cluster that can own this virtual machine.
#
#Disk path 'C:\ProgramData\Microsoft\Windows\Hyper-V' is not a path to storage in the cluster or to storage that can be added to the cluster. You must ensure this storage is available to every node in the cluster to make this virtual machine highly available.
This next function is convenient to generate new clone machines from a single VHDX file. The advantage of this function over the previous is that it only requires read-only access to the source VHDX file at the cost of not flexible enough to access multiple-disk source machines.
# Change these variables
$sourceVhdx='\\Intranet\WindowsGoldenImage\WindowsGoldenImage_disk0.vhdx'
$newVmName='NewServerName'
$destinationFolder='C:\ClusterStorage\Volume5'
$memory='8GB'
$cpus=4,
$network='IntranetZone'
$vlan='100'
$secureBoot=$true # Windows: True, Linux: False
# Assuming single Vhdx file VM cloning
function createVmFromVhdx{
param(
$sourceVhdx,
$newVmName,
$destinationFolder,
$memory='4GB',
$cpus=2,
$network,
$vlan
)
$ErrorActionPreference = 'stop'
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 2 `
-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}
Start-VM -Name $newVmName
VMConnect.exe $env:computername $newVmName
}
catch{
write-warning "$($error[0])"
}
}
createVmFromVhdx $sourceVhdx $newVmName $destinationFolder $memory $cpus $network $vlan $secureBoot
Optionally, guest VMs should be movable within a cluster of hosts. Thus, the following helper function makes that easy
$newVmName='New-Server-Name-Here'
$targetCluster='CLUSTER007'
function addVmToCluster{
param($vmName,$targetCluster)
try{
if(!$targetCluster){
$targetCluster=(get-cluster -ea SilentlyContinue).Name
}
if($targetCluster){
Add-ClusterVirtualMachineRole -Cluster $targetCluster -VirtualMachine $vmName -EA Stop
return $true
}
else{
write-host "No clusters defined."
return $false
}
}
catch{
write-warning "$($error[0])"
return $false
}
}
addVmToCluster $newVmName $targetCluster
Categories: