PowerShell: Increase CPU Count or Memory of VMs via Virtual Machine Manager

# IncreaseCpuandRamViaVMM.ps1

# User Input Variables
$vmNames=@(
    'TESTWINDOWS',
    'TESTWINDOWS2'
)
$vmmServer=$env:computername
$setCpuCount=8
$setDynamicMemory=$false
$dynamicMemoryMinimumGB='2GB'
$dynamicMemoryMaximumGB='16GB'

# Get all VMs beloging to specific cloud(s) - this is to dynamically configure all VMs within a virtual cloud
# $vms=Get-SCVirtualMachine -VMMServer $vmmServer|?{$_.Cloud.Name -in $cloudNames}
$dynamicMemoryMinimumMB=$dynamicMemoryMinimumGB/1MB
$dynamicMemoryMaximumMB=$dynamicMemoryMaximumGB/1MB
$results=@()
foreach ($vm in $vms){
    $vmName=$vm.Name
    $previousCpuCount=$vm.CPUCount
    write-host "Upgrading $vmName..."
    if($vm.Status -eq 'Running'){
        Stop-SCVirtualMachine -VM $vm -Shutdown
    }    
    if($setCpuCount -and !$setDynamicMemory){
        if($vm.CPUCount -lt $setCpuCount){
            Set-SCVirtualMachine -VM $vm -RunAsSystem -CPUCount $setCpuCount -CPUExpectedUtilizationPercent 20
        }else{
            write-host "$($vm.Name) CPU Count is $previousCpuCount, which is already equal or higher than $setCpuCount"
        }
        $results+=@{
            vmName=$vmName;
            previousCpuCount=$previousCpuCount;
            currentCpuCount=$setCpuCount
            }
    }elseif($setCpuCount -and $setDynamicMemory){
        Set-SCVirtualMachine -VM $vm -RunAsSystem `
        -CPUCount $setCpuCount -CPUExpectedUtilizationPercent 20 `
        -DynamicMemoryEnabled $setDynamicMemory -DynamicMemoryBufferPercentage 20 -MemoryWeight 5000 `
        -MemoryMB $dynamicMemoryMinimumMB -DynamicMemoryMinimumMB $dynamicMemoryMinimumMB -DynamicMemoryMaximumMB $dynamicMemoryMaximumMB        
        $results+=@{
            vmName=$vmName;
            previousCpuCount=$previousCpuCount;
            currentCpuCount=$setCpuCount;
            StartupMemory=$dynamicMemoryMinimumMB;
            DynamicMemoryMinimumMB=$dynamicMemoryMinimumMB;
            DynamicMemoryMaximumMB=$dynamicMemoryMaximumMB
            }
    }elseif(!$setCpuCount -and $setDynamicMemory){
        Set-SCVirtualMachine -VM $vm -RunAsSystem `
                -DynamicMemoryEnabled $setDynamicMemory -DynamicMemoryBufferPercentage 20 -MemoryWeight 5000 `
                -MemoryMB $dynamicMemoryMinimumMB -DynamicMemoryMinimumMB $dynamicMemoryMinimumMB -DynamicMemoryMaximumMB $dynamicMemoryMaximumMB        
                $results+=@{
                    vmName=$vmName;
                    StartupMemory=$dynamicMemoryMinimumMB;
                    DynamicMemoryMinimumMB=$dynamicMemoryMinimumMB;
                    DynamicMemoryMaximumMB=$dynamicMemoryMaximumMB
                    }
    }else{    
        write-warning "User must input the `$setDynamicMemory and/or `$setCpuCount variables"
        break
    }
    if($vm.Status -eq 'PowerOff'){
        Start-SCVirtualMachine -VM $vm
    }    
}
write-host $($results|out-string).trim()

PowerShell: Virtual Machine Snapshots Report from VMM Servers

# vmSnapshotReport.ps1
# Requirements:
# - Credentials to access VMM Servers with Administrator role
# - Firewall open WinRM ports from jump host to VMM Servers

# Variables
$vmmServers=@(
    @{servername='LAX-VMM01.kimconnect.com';adminname='intranet\clusterReport';password=$env:kimconnectcom}
    @{servername='PHX-VMM01.kimconnect.net';adminname='intranet\clusterReport';password=$env:kimconnectnet}
)

# Email relay parameters
$emailFrom='admins@kimconnect.com'
$emailTo='admins@kimconnect.com'
$subject='Hyper-V Snapshots Report'
$smtpRelayServer='email-relay.kimconnect.com'
$emailDay='Monday'
$css="
    <style>
    .h1 {
        font-size: 18px;
        height: 40px;
        padding-top: 80px;
        margin: auto;
        text-align: center;
    }
    .h5 {
        font-size: 22px;
        text-align: center;
    }
    .th {text-align: center;}
    .table {
        padding:7px;
        border:#4e95f4 1px solid;
        background-color: white;
        margin-left: auto;
        margin-right: auto;
        width: 100%
        }
    .colgroup {}
    .th { background: #0046c3; color: #fff; padding: 5px 10px; }
    .td { font-size: 11px; padding: 5px 20px; color: #000;
            width: 1px;
            white-space: pre;
        }
    .tr { background: #b8d1f3;}
    .tr:nth-child(even) {
        background: #dae5f4;
        width: 1%;
        white-space: nowrap
    }
    .tr:nth-child(odd) {
        background: #b8d1f3;
        width: 1%;
        white-space: nowrap
    }
    </style>
"

$results=$null
foreach($vmmServer in $vmmServers){
    $servername=$vmmServer.servername
    $username=$vmmServer.adminName
    $password=convertto-securestring $vmmServer.password -AsPlainText -Force
    $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username,$password
    $pssession=new-pssession -computername $servername -credential $credential
    if($pssession.State -eq 'Opened'){
        $results+=invoke-command -session $pssession {Get-SCVMCheckpoint -vmmserver localhost}
        remove-pssession $pssession
    }else{
        write-warning "Unable to connect to $servername"
        break
    }
}
$report=$results|select @{name='VMMServer';e={$_.PSComputername}},VM,Name
if($null -ne $report){
    $currentReport=$report | ConvertTo-Html -Fragment | Out-String
    $currentReportHtml=$currentReport -replace '\<(?<item>\w+)\>', '<${item} class=''${item}''>'
    $emailContent='<html><head>'+$css+"</head><body><h5 class='h5'>$subject</h5>"+$currentReportHtml+'</body></html>'
    write-host "####################################################################################################`r`nVM Snapshots Report:`r`n####################################################################################################`r`n$(($report|out-string).trim())"
    $today=(get-date).DayOfWeek
    if ($today -eq $emailDay){
        Send-MailMessage -From $emailFrom `
        -To $emailTo `
        -Subject $subject `
        -Body $emailContent `
        -BodyAsHtml `
        -SmtpServer $smtpRelayServer
        write-host "$subject email has been sent to $emailTo with time stamp $((get-date|out-string).trim())"
    }
}

PowerShell: Set VM Dynamic Memory in Virtual Machine Manager

# setVmDynamicMemoryInVmm.ps1

# Optimize Dynamic RAM
$minGb='16GB'
$maxGb='32GB'
$startupGb='2GB'
$buffer=20
$memoryWeight=5000
$forcedRestart=$false
$vmmServer='localhost'

function getUnoptimizedMemoryVms{
    param(
        $minGb='16GB',
        $maxGb='1024GB',
        $vmmServer='localhost'
    )
    Import-Module -Name "virtualmachinemanager"
    $vms=Get-VM -vmmserver $vmmServer
    $targetVms=$vms|?{$($_.MemoryAssignedMB -ge $minGb/1MB -and $_.MemoryAssignedMB -le $maxGb/1MB) -and $($_.DynamicMemoryDemandMB -ne $_.MemoryAssignedMB)} # -and !$_.DynamicMemoryEnabled
    # Memory = startup memory
    # DynamicMemoryDemandMB = memory demand
    # MemoryAssignedMB = memory currently assigned
    # DynamicMemoryMaximumMB = Maximum Memory (non-zero value only if Dynamic Memory is enabled)
    return $($targetVMs|select-object Name,Version,DynamicMemoryEnabled,DynamicMemoryDemandMB,MemoryAssignedMB|sort-object -Property Name)
}

function setVmDynamicMemoryInVmm{
    param(
        $vmName,
        $setMinMb,
        $setMaxMb,
        $setStartupMb,
        $forcedRestart=$false,
        $buffer=20,
        $memoryWeight=5000,
        $vmmServer='localhost'
    )
    
    Import-Module -Name "virtualmachinemanager"
    $vm=get-scvirtualmachine -name $vmName -vmmserver $vmmServer
    $staticMemory=!$vm.DynamicMemoryEnabled # if Dynamic Memory is enabled and VM generation is 9.0 or higher, then Dynamic RAM can be configured while VM remains online
    $maximumMB=if($setMaxMb -gt $vm.MemoryAssignedMB){$setMaxMb}elseif($vm.MemoryAssignedMB -gt $vm.DynamicMemoryDemandMB){$vm.MemoryAssignedMB}elseif($vm.DynamicMemoryDemandMB -gt $vm.Memory){$vm.DynamicMemoryDemandMB}else{4096}
    $startupMb=if($startupMb){$startupMb}elseif($vm.DynamicMemoryDemandMB -lt $maximumMB/2){$vm.DynamicMemoryDemandMB}else{2048}
    $minimumMB=if($setMinMb){$setMinMb}elseif($vm.DynamicMemoryDemandMB -lt $maximumMB/2){$vm.DynamicMemoryDemandMB}else{$startupMb}

    if(!$vm -or $vm.count -gt 1){
        write-warning "VM Name $vmName cannot be queried"
        return $false
    }
    
    $vmHasFailed=$vm.Status -eq 'Failed'
    if($vmHasFailed){
        try{
            # Manually repair virtual machine in a failed state
            Repair-SCVirtualMachine $vm -Dismiss 
        }catch{
            write-warning $_
            return $false
        }
    }

    $vmIsOnline=$vm.VirtualMachineState -eq 'Running'
    if($vmIsOnline -and $forceRestart -and $staticMemory){
        Stop-SCVirtualMachine -VM $vm
        write-host "$vmName has been stopped temporarily"
    }elseif($vmIsOnline -and !$forceRestart -and $staticMemory){
        write-warning "VM Name $vmName status is 'Running' and the `$forceRestart flag has been set to `$false"
        return $false
    }elseif(!$staticMemory){
        write-host "VM name $vmName memory config is detected as dynamic"
    }
    # Hyper-V Commands - will not work in VMM
    # $startupBytes=$startupGb/1
    # $minBytes=$minGb/1
    # $maxBytes=$maxGb/1
    # Set-VMMemory $vmName -DynamicMemoryEnabled $true -MinimumBytes $minBytes -StartupBytes $startupBytes -MaximumBytes $maxBytes -Priority $priority -Buffer $buffer
    
    # VMM Command
    try{
        Set-SCVirtualMachine -VM $vm -DynamicMemoryEnabled $true `
            -MemoryMB $startupMb -DynamicMemoryMinimumMB $minimumMB -DynamicMemoryMaximumMB $maximumMB `
            -DynamicMemoryBufferPercentage $buffer -MemoryWeight $memoryWeight
        if($vmIsOnline -and $forceRestart){        
            Start-SCVirtualMachine -VM $vm
            if($vm.Status -eq 'Running'){
                write-host "$vmName has started"
            }else{
                write-host "$vmName has NOT started successfully"
                return $false
            }
        }
        write-host "VM name $vmname memory has been set to dynamic with min of $minGb GB and $maxGb GB"
        return $true
    }catch{
        write-warning $_ 
        return $false
    }

    # Set Static RAM
    # Set-VM -StaticMemory -Name $vmName -MemoryStartupBytes $($startupGb/1)
}

# setVmDynamicMemoryInVmm $vmName $setMinMb $setMaxMb $forceRestart $buffer $memoryWeight $vmmServer
function optimizeMemoryVms{
    param(
        $minGb='16GB',
        $maxGb='64GB',
        $startupGb='2GB',
        $forcedRestart=$false,
        $buffer=20,
        $memoryWeight=5000,
        $vmmServer='localhost'
    )
    $targetVMs=getUnoptimizedMemoryVms $minGb $maxGb $vmmServer
    if($targetVMs.count){
        $results=[hashtable]@{}
        foreach($vm in $targetVMs){
            $vmName=$vm.Name
            $setMaxMb=if($vm.DynamicMemoryDemandMB -gt $vm.MemoryAssignedMB){$vm.DynamicMemoryDemandMB}else{$vm.MemoryAssignedMB}
            $setStartupMb=if($startupGb){$startupGb/1MB}elseif($vm.DynamicMemoryDemandMB -lt $setMaxMb/2){$vm.DynamicMemoryDemandMB}else{2048}
            $setMinMb=if($($vm.MemoryAssignedMB/2) -lt $setStartupMb){$vm.MemoryAssignedMB/2}else{$setStartupMb}
            $result=setVmDynamicMemoryInVmm $vmName $setMinMb $setMaxMb $setStartupMb $forceRestart $buffer $memoryWeight $vmmServer
            write-host "$vmName Memory config as been set to Dynamic with Min $setMinMb MB and Max $setMaxMb MB"
            $results+=@{$vmName=$result}
        }
        return $results
    }
}

optimizeMemoryVms $minGb $maxGb $startupGb $forcedRestart $buffer $memoryWeight $vmmServer



# Set-SCVirtualMachine -Name TESTWINDOWS -DynamicMemoryEnabled $true -DynamicMemoryMinimumMB $(8*1024)

# Set-VMMemory TESTWINDOWS -StartupBytes $(8GB/1)

# PS C:\Windows\system32> Set-VMMemory $vmName -StartupBytes $($startupGb/1)
# Set-VMMemory : Failed to modify device 'Memory'.
# 'TESTWINDOWS' failed to modify device 'Memory'. (Virtual machine ID
# 0A1E6930)
# Changing memory size of running virtual machine 'TESTWINDOWS' failed. VM version must be at least 6.0.
# At line:1 char:1
# + Set-VMMemory $vmName -StartupBytes $($startupGb/1)
# + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#     + CategoryInfo          : NotSpecified: (:) [Set-VMMemory], VirtualizationException
#     + FullyQualifiedErrorId : OperationFailed,Microsoft.HyperV.PowerShell.Commands.SetVMMemory

# Install-WindowsFeature -Name Hyper-V-PowerShell
# Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Management-PowerShell

How To Use CredSSP to Move Virtual Machines In a Hyper-V Cluster

# Prep on Client
$domain='intranet.kimconnect.com'
Enable-WSManCredSSP -Role "Client" -DelegateComputer "*.$domain"

# Prep on Server
Enable-WSManCredSSP -Role "Server"

# Test a command the requires CredSSP
$adminUsername='intranet\brucelee'
$plaintextPassword='SOMEPASSWORD'
$encryptedPassword=ConvertTo-securestring $plaintextPassword -AsPlainText -Force
$adminCredential=New-Object -TypeName System.Management.Automation.PSCredential -Args $adminUsername,$encryptedPassword

$vmName='testWindows'
$currentNode='lax-node002'
$newOwnerNode='lax-node008'

invoke-command -computername $currentNode -credential $adminCredential -scriptblock {
		param($vmName,$newOwnerNode)
		Move-ClusterVirtualMachineRole -Name $vmName -MigrationType Live -Node $newOwnerNode
	} -Args $vmName,$newOwnerNode

Incomplete VM Configuration

Symptom:

refresh-vm : VMM could not find the specified path C:\win10iso\KMS
SW_DVD9_Win_Pro_10_20H2.2_64BIT_English_Pro_Ent_EDU_N_MLF_-2_X22-46651.ISO on the HYPERV-NODE25 server.
(Error ID: 2904, Detailed Error: The system cannot find the path specified (0x80070003))

Ensure that you have specified a valid file name parameter, and then try the operation again.

To restart the job, run the following command:
PS> Restart-Job -Job (Get-VMMServer localhost | Get-Job | where { $_.ID -eq "{fce569ca-bd01-4493-b27c-e3a433cee5f4}"})
At line:1 char:38
+ $incompleteVmConfigs|select -first 1|refresh-vm -force
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : ReadError: (:) [Read-SCVirtualMachine], CarmineException
+ FullyQualifiedErrorId : 2904,Microsoft.SystemCenter.VirtualMachineManager.Cmdlets.RefreshVmCmdlet

Resolution:

Remove the virtual DVD ISO that is being attached to the guest VM. 

PowerShell: Find Duplicate Mac Addresses of Guest VMs in Virtual Machine Manager

Here’s a quick snippet to check whether you have any duplicate mac addresses on existing virtual machines in the cluster:

import-module virtualmachinemanager
$macAddresses=get-vm | Get-VirtualNetworkAdapter | select name,EthernetAddress

$a=$macAddresses.EthernetAddress

$b=$a | select –unique

Compare-object –referenceobject $b –differenceobject $a

PowerShell: Assign Guest Virtual Machine to a Cloud in VMM

# assignVmsToCloud.ps1
# The following function assigns a guest VM into a 'cloud' in Virtual Machine Manager

$vmNames=@(
	'guest-vm1',
	'guest-vm2',
	'guest-vm3'
)
$cloudAssign='DEV'
$vmmServer=$env:computername
$roleName='Developers'

function assignVmsToCloud{
	param(
		$vmName='testVm',
		$cloudAssign='Test',
		$roleName='Administrators',
        $vmmServer='localhost'
	)
	$ErrorActionPreference='Stop'
	try{
        $null=import-module virtualmachinemanager
		$vm=Get-SCVirtualMachine -Name $vmName -VMMServer $vmmServer
		$cloud=Get-SCCloud -Name $cloudAssign -VMMServer $vmmServer
		$userRole=Get-SCUserRole -Name $roleName -VMMServer $vmmServer
		$jobGroup=[System.Guid]::NewGuid().Guid
        if(!$vm){
            write-warning "Unable to find VM named $vmName on VMM Server $vmmServer"
            return $false
        }elseif($vm.Cloud.Name -eq $cloudAssign){
			write-host "$vmName has already been assigned to Cloud $cloudAssign" -ForegroundColor Green
			return $true
		}else{	
			$null=Set-SCVirtualMachine -VM $vm -Cloud $cloud
			$null=Set-SCUserRoleQuota -Cloud $cloud -JobGroup $jobGroup
			$null=Set-SCUserRoleQuota -Cloud $cloud -JobGroup $jobGroup -QuotaPerUser
			$null=Add-SCUserRolePermission -Cloud $cloud -JobGroup $jobGroup -AllowLocalAdmin -Checkpoint -CheckpointRestoreOnly -DeployFromTemplateOnly -Deploy -DeployShielded -PauseAndResume -RemoteConnect -Remove -Save -Shutdown -Start -Stop -Store -ManageAzureProfiles
			$null=Grant-SCResource -Resource $vm -JobGroup $jobGroup
			$null=Set-SCUserRole -UserRole $userRole -JobGroup $jobGroup
			$vm=Get-SCVirtualMachine -VMMServer $vmmServer -Name $vmName
			if($vm.Cloud.Name -eq $cloudAssign){
				write-host "$vmName has been assigned to Cloud $cloudAssign successfully" -ForegroundColor Green
				return $true
			}else{
				write-host "$vmName has NOT been assigned to Cloud $cloudAssign" -ForegroundColor Red
				return $false
			}
		}
	}catch{
		write-warning $_
		return $false
	}
}

######## Set VM as defined ########
$results=[hashtable]@{}
foreach($vmname in $vmNames){
    $result=assignVmsToCloud $vmName $cloudAssign $roleName $vmmServer
	$results+=@{$vmName=$result}
}
######## Dynamically Gather VM by Keywords and assign to cloud ########
$keyword='stg'
$cloudAssign='STAGE'
$vms=.{$null=import-module virtualmachinemanager;get-vm}
$targetVms=$vms.Name|?{$_ -like "*$keyword*"}

$results=[hashtable]@{}
foreach($vmName in $targetVms){
    $result=assignVmsToCloud $vmName $cloudAssign $roleName $vmmServer
	$results+=@{$vmName=$result}
}

The Process of Adding a New Hyper-V Server Into a Cluster and the Associated Virtual Machine Manager (VMM)

These are the steps:

  1. Install Windows
    Windows 2019 Data Center Edition is the standard as of May 20, 2022
    Certain hardware may require slipstreamed ISO’s to load RAID drivers so that the installation wizard would recognize the underlying hardware
    Install Windows with GUI. Although, headless Windows would still work, our Admins prefer to have GUI, boo
  2. Setup Network connectivity
    Obtain a static IP for the server
    Assign an IP for new server using
    Setup Interface with static IP, Gateway, and DNS
  3. Ensure that machine is accessible to VMM on these ports
    Ports required by SCVMM:
    TCP/22
    TCP/80
    TCP/135
    TCP/139
    TCP/445
    TCP/443
    TCP/623
    TCP/1433
    TCP/5985
    TCP/5986
    TCP/8530
    TCP/8531
    TCP/8100
    TCP/8101
    TCP/8102
    TCP/49152 to TCP/65535
    Ports required by Prometheus Windows Exporter:
    TCP/9182
  4. Enable RDP
    This feature would automatically be installed when a machine joins the domain. However, we’re ensuring that the hardware and OS would not go into a recovery loop due to driver issues. Hence, we would be patching Windows and attempting enable its Hyper-V features as a precursor to joining the machine to to domain, only if the machine is stable after these functions are added.
  5. Disable Startup repair
    Run this command: bcdedit /set {current} recoveryenabled no
  6. Update Windows
    There’s a PowerShell method to updating Windows directly from Microsoft
    Alternative, the equivalent GUI method would suffice
  7. Install Hyper-V Features: Install-WindowsFeature -Name Hyper-V -IncludeManagementTools -Restart
    Be advised that Windows will reboot after this command
    If there were any driver conflicts, Windows would go into a recovery loop. Hopefully, that doesn’t happen to the machine you’re preparing
  8. Join Domain
  9. Run these PowerShell Command as Administrator
    # Install Chocolatey
    if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))}

    # Defining $ENV:ChocotaleyInstall so that it would be called by refreshenv
    $ENV:ChocolateyInstall = Convert-Path "$((Get-Command choco).Path)\..\.."
    Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
    Update-SessionEnvironment
    # Add Private Repo
    $privateRepoName='kimconnectrepo'
    $privateRepoUrl='https://choco.kimconnect.com/chocolatey'
    $priority=1
    $privateRepo="'$privateRepoName' -s '$privateRepoUrl' --priority=$priority"
    choco source add -n=$privateRepo

  10. Uninstall Windows Defender
    Run this command: Remove-WindowsFeature Windows-Defender
  11. Install Enterprise Antivirus
  12. Install Prometheus Windows Exporter
    Use Chocolatey to install Prometheus Windows_Exporter
    choco install prometheus-windows-exporter.install -y --ignore-checksums
    $null=& sc.exe failure $serviceName reset= 30 actions= restart/100000/restart/100000/""/300000
    Set-Service -Name $serviceName -StartupType 'Automatic'
    Enabling Collectors for a Hyper-V Server
    $serviceName='Windows_Exporter'
    $enabledCollectors='os,cpu,cs,logical_disk,net,tcp,hyperv,service,textfile'
    $switches=" --log.format logger:eventlog?name=$serviceName --collectors.enabled $enabledCollectors"
    function modifyService{
    param(
    $serviceName,
    $switches
    )
    $wmiService=Get-WmiObject win32_service| ?{$_.Name -like "*$serviceName*"}
    $exePath=[regex]::match($wmiService.PathName,'\"(.*)\"').groups[1]
    $binarySwitches='\"' + $exePath + '\"' + $switches
    sc.exe config $serviceName binpath= $binarySwitches
    sc.exe qc windows_exporter
    restart-service $serviceName
    }
    # Local execution
    modifyService $serviceName $switches
  13. Install Failover Clustering Features
    # After restart, install Failover clustering features
    Install-WindowsFeature Failover-Clustering -IncludeManagementTools; Install-WindowsFeature RSAT-Clustering-MGMT,RSAT-Clustering-PowerShell,Multipath-IO
  14. Setup NIC Teaming (if appropriate)
    # Check Teaming setup
    Get-NetLbfoTeam

    # Initialize Teaming, if required
    $teamName='Team1'
    $vlanId=100
    Add-NetLbfoTeamNIC -Team $teamName -VlanID $vlanId

    # Set Teaming mode, if required
    $teamName='Team1'
    $teamMode='LACP'
    $lbAlgorithm='Dynamic'
    Set-NetLbfoTeam -Name $teamName -TeamingMode $teamMode -LoadBalancingAlgorithm $lbAlgorithm

    # Add Team members, if required
    $teamName='Team1'
    $nicTeamMembers='NIC1','NIC2'
    $nicTeamMembers|%{Add-NetLbfoTeamMember -Name $_ -Team $teamName}

    # Check Teaming setup, if required
    Get-NetLbfoTeam

    # Check NIC names
    Get-NetAdapter

  15. Enable WinRM: Enable-PSRemoting -Force
  16. Enable CredSSP: Enable-WSManCredSSP -Role Server -Force
  17. Create New Virtual Switch(es)
    $switchName='External-Connection' # change this label to reflect the actual vSwitch name to be added
    $adapterName='TEAM1' # change this value to reflect the correct interface
    $vlanId=101 # change this to the correct VLAN
    function addVirtualSwitch($switchName,$adapterName,$vlanId){
    New-VMSwitch -name $switchName -NetAdapterName $adapterName -AllowManagementOS $true
    if($vlanId){
    Get-VMNetworkAdapter -SwitchName $switchName -ManagementOS|Set-VMNetworkAdapterVlan -Access -VlanId $vlanId
    }
    }
    addVirtualSwitch $switchName $adapterName $vlanId
  18. Include appropriate Admins
    # Example for HQ
    $admins=@(
    "$env:USERDOMAIN\$($env:computername)$",
    "$env:USERDOMAIN\service-hv",
    "$env:USERDOMAIN\service-vmm"
    )
    Add-localgroupmember -group Administrators -member $admins
  19. Join Node to Cluster
    # Example
    $clusterName='hyperv-cluster'
    Add-ClusterNode -Name $env:computername -Cluster $clusterName
  20. Install VMM Agent
    The easy method:
    # Install VMM Agent
    $vmmServer='hq-vmm01' # change this value to reflect the correct VMM node
    $version='10.19.2591.0' # change to value to reflect the latest expected version
    $agentMsiFile="\\$vmmServer\C$\Program Files\Microsoft System Center\Virtual Machine Manager\agents\amd64\$version\vmmAgent.msi"

    # Installing VMM Agent using its MSI File
    $file=gi $agentMsiFile
    $DataStamp = get-date -Format yyyyMMddTHHmmss
    $logFile ="C:\" + '{0}-{1}.log' -f $file.name,$DataStamp
    $MSIArguments = @(
    "/i"
    ('"{0}"' -f $file.fullname)
    "/qn"
    "/norestart"
    "/L*v"
    $logFile
    )
    Start-Process "msiexec.exe" -ArgumentList $MSIArguments -Wait -NoNewWindow
  21. Add This New Hyper-V Node to VMM
    Access VMM as Administrator > Navigate to Fabric > select the appropriate cluster > right-click the newly introduced node > Associate with Cluster > Done

Hyper-V Guest VM CPU Compatibility Feature Down Side

Symptom: guest VM could not install applications that require’SSE4.2 and POPCNT instruction sets’

Cause:

The compatibility feature has been turned on for this guest VM; thus, these CPU features have been disabled:

  • AMD: SSSE3, SSE4.1, SSE4.A, SSE5, POPCNT, LZCNT, Misaligned SSE, AMD 3DNow!, Extended AMD 3DNow!
  • Intel: SSSE3, SSE4.1, SSE4.2, POPCNT, Misaligned SSE, XSAVE, AVX

Resolution:

Disable (uncheck) the ‘allow migration to a virtual machine host with a different processor version’

How To Install VMM Agent (SCVMM) Manually

The following snippet assumes that a New Hyper-V Server has been added to the cluster; yet, it’s SCVMMAgent Service doesn’t get installed or is corrupted such as:

PS C:\Windows\system32> get-service SCVMMAgent
get-service : Cannot find any service with service name 'SCVMMAgent'.
At line:1 char:1
+ get-service SCVMMAgent
+ ~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (SCVMMAgent:String) [Get-Service], ServiceCommandException
    + FullyQualifiedErrorId : NoServiceFoundForGivenName,Microsoft.PowerShell.Commands.GetServiceCommand
# Version 0.01
# No robot crawler to locate the highest VMMAgent version automatically (will code that when I feel like it)

# Install VMM Agent
$vmmServer='vmmServerName'
$version='10.19.2591.0'
$agentMsiFile="\\$vmmServer\C$\Program Files\Microsoft System Center\Virtual Machine Manager\agents\amd64\$version\vmmAgent.msi"

# Installing VMM Agent using its MSI File
$file=gi $agentMsiFile
$DataStamp = get-date -Format yyyyMMddTHHmmss
$logFile ="C:\" + '{0}-{1}.log' -f $file.name,$DataStamp
$MSIArguments = @(
    "/i"
    ('"{0}"' -f $file.fullname)
    "/qn"
    "/norestart"
    "/L*v"
    $logFile
)
Start-Process "msiexec.exe" -ArgumentList $MSIArguments -Wait -NoNewWindow

PowerShell: Add Network Sites (VLAN) into a Virtual Machine Manager Logical Network

# addVmmNetworkSites.ps1
# version 0.01

# User Defined Variables
$networkSites=@(
    @{
        'newNetworkSitename'="test 1"
        'vlanId'=100
        'vlanCidr'='192.168.1.0/24'
        'logicalNetworkName'='Trunk'
        'logicalNetworkDescription'=''
        'hostGroup'='All Hosts'
    }
    @{
        'newNetworkSitename'="test 2"
        'vlanId'=200
        'vlanCidr'='192.168.2.0/24'
        'logicalNetworkName'='Trunk'
        'logicalNetworkDescription'=''
        'hostGroup'='All Hosts'
    }    
)

function addVmmNetworkSite{
    param(
        $newNetworkSitename,
        $vlanId,
        $vlanCidr,
        $logicalNetworkName='Trunk',
        $logicalNetworkDescription='',
        $hostGroup='All Hosts',        
        $runAsynchronously=$true
    )

    # Set the logical network with required features of network virtualization
    $logicalNetwork = Get-SCLogicalNetwork -Name $logicalNetworkName
    $setLogicalNetwork="Set-SCLogicalNetwork -Name '$logicalNetworkName' ``
        -Description '$logicalNetworkDescription' ``
        -LogicalNetwork `$logicalNetwork ``
        -EnableNetworkVirtualization `$true ``
        -UseGRE `$true ``
        -LogicalNetworkDefinitionIsolation `$false ``
        $(if($runAsynchronously){'-RunAsynchronously'})"
    write-host $setLogicalNetwork
    invoke-expression $setLogicalNetwork

    # Add network site
    $allHostGroups = @()
    $allHostGroups += Get-SCVMHostGroup -Name $hostGroup
    $allSubnetVlan = @()
    $allSubnetVlan += New-SCSubnetVLan -Subnet $vlanCidr -VLanID $vlanId
    $addNetworkSite="New-SCLogicalNetworkDefinition -Name '$newNetworkSitename' ``
        -LogicalNetwork `$logicalNetwork ``
        -VMHostGroup `$allHostGroups ``
        -SubnetVLan `$allSubnetVlan ``
        $(if($runAsynchronously){'-RunAsynchronously'})"
    write-host $addNetworkSite
    invoke-expression $addNetworkSite
}

foreach($networkSite in $networkSites){
    $command="addVmmNetworkSite -newNetworkSitename '$($networkSite.newNetworkSitename)' ``
        -vlanId '$($networkSite.vlanId)' ``
        -vlanCidr '$($networkSite.vlanCidr)' ``
        -logicalNetworkName '$($networkSite.logicalNetworkName)' ``
        -logicalNetworkDescription '$($networkSite.logicalNetworkDescription)' ``
        -hostGroup '$($networkSite.hostGroup)'"
    write-host $command
    #invoke-expression $command
}

Virtual Machine Networking Error 15011

Creating New Logical Network

$logicalNetworkID="somehash-hash-hash"
$newNetworkName='Test Network'
$subnet="192.168.500.0/24"
$logicalNetwork = Get-SCLogicalNetwork -ID $logicalNetworkID

$vmNetwork = New-SCVMNetwork -Name $newNetworkName -LogicalNetwork $logicalNetwork -IsolationType "WindowsNetworkVirtualization" -CAIPAddressPoolType "IPV4" -PAIPAddressPoolType "IPV4"
Write-Output $vmNetwork
$subnet = New-SCSubnetVLan -Subnet $subnet
New-SCVMSubnet -Name $newNetworkName -VMNetwork $vmNetwork -SubnetVLan $subnet -EnableEncryption $false

Remove Logical Network in VMM

$scvmNetworkName='Test Network'
Get-SCVMNetwork -Name $scvmNetworkName |Remove-SCVMNetwork

Error Message

Remove-SCVMNetwork : VMM is unable to delete the VMNetwork 'Test Network' because other objects, such as VMSubnets, Load balancer templates and  Virtual network adapters depend on it. (Error ID: 15011)

Remove the VMNetwork association with all dependent resources, and then remove the VMNetwork.

To restart the job, run the following command:
PS> Restart-Job -Job (Get-VMMServer localhost | Get-Job | where { $_.ID -eq "{somehash-hash-hash}"})
At line:1 char:47
+ Get-SCVMNetwork -Name "Test Network"|Remove-SCVMNetwork
+                                               ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ReadError: (:) [Remove-SCVMNetwork], CarmineException
    + FullyQualifiedErrorId : 15011,Microsoft.SystemCenter.VirtualMachineManager.Cmdlets.RemoveSCVMNetworkCmdlet

Resolve Error 15011

Run Virtual Machine Manager Console > Navigate to 'VMs and services' >  'VM Networks' > right-click the Logical Network > Properties > Depedencies > Delete all subnet dependencies > OK > right-click the Logical Network again > Delete > OK

Virtual Machine Manager (VMM) Error ID: 1730

Symptom:
$vmName='bad-guestvm'
$vm = Get-SCVirtualMachine -Name $vmName
Read-SCVirtualMachine -VM $vm

Read-SCVirtualMachine : The selected action could not be completed because the virtual machine is not in a state in
which the action is valid. (Error ID: 1730)

Check the state of the virtual machine, and verify that the selected job can be run on a virtual machine in that state.

To restart the job, run the following command:
PS> Restart-Job -Job (Get-VMMServer localhost | Get-Job | where { $_.ID -eq "{442cfeda-2df8-4ee8-9c3d-f36390653124}"})
At line:1 char:1
+ Read-SCVirtualMachine -VM $vm
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ReadError: (:) [Read-SCVirtualMachine], CarmineException
    + FullyQualifiedErrorId : 1730,Microsoft.SystemCenter.VirtualMachineManager.Cmdlets.RefreshVmCmdlet

Resolution: remove guest VM clustered role an then re-add it to the cluster
Resolution:
remove guest VM clustered role an then re-add it to the cluster

1. Remove Clustered Role

# Experimental: remove clustered roles via PowerShell
# Note: currently, these commands don't remove the roles properly; hence, the GUI method is necessary
$vmName='bad-guestvm0001'
$clusteredRoles=Get-ClusterResource -name *$vmName*
foreach($role in $clusteredRoles){
    $ownerNode=$role.OwnerNode
    $roleName=$role.Name
    write-warning "Remove-ClusterResource -Name $roleName -Force"
    pause
    if($env:computername -ne $role.OwnerNode){
        $session=new-pssession $ownerNode
        if($session.State -eq 'Opened'){            
            invoke-command -session $session {param($roleName)Remove-ClusterResource -Name $roleName -Force} -Args $roleName
            remove-pssession $session
        }
    }else{
        Remove-ClusterResource -Name $roleName -Force
    }
}

2. Re-add Guest VM to clustered role

# Required: console or RDP session onto the owner node

# Experimental: running this command via Remote WinRM has failed with this error
# WARNING: If you are running Windows PowerShell remotely, note that some failover clustering cmdlets do not work
# remotely. When possible, run the cmdlet locally and specify a remote computer as the target. To run the cmdlet
# remotely, try using the Credential Security Service Provider (CredSSP). All additional errors or warnings from this
# cmdlet might be caused by running it remotely.
# WARNING: You do not have administrative privileges on the cluster. Contact your network administrator to request
# access.
#     Access is denied
$vmName='bad-guestvm'
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
}

addVmToCluster $vmName

Repair with the Ignore Option:

Kubernetes Broken Due To Unknown Reasons

Problem 1: Admin User Unable to Login to Cluster via Controller (Master Node)

# SSL Error:
The connection to the server x.x.x.x:6443 was refused - did you specify the right host or port?

# Resolution to the SSL problem:
sudo -i
swapoff -a
exit
strace -eopenat kubectl version

# User privilege error:
kim@controller01:~$ kubectl cluster-info
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
error: You must be logged in to the server (Unauthorized)

kim@controller01:~$ kubectl version
Client Version: version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.10", GitCommit:"8152330a2b6ca3621196e62966ef761b8f5a61bb", GitTreeState:"clean", BuildDate:"2021-08-11T18:06:15Z", GoVersion:"go1.15.15", Compiler:"gc", Platform:"linux/amd64"}
error: You must be logged in to the server (the server has asked for the client to provide credentials)

# Resolution to Admin user privilege error:
# Grant current user admin privileges on Kubernetes
# mkdir -p $HOME/.kube # this was done during previous setup
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
# sudo chown $(id -u):$(id -g) $HOME/.kube/config # this was done during previous setup

Problem 2: SSL Certificates Not Automatically Renewed

# Error message with kimconnect.com SSL Cert
Warning  Failed   84m (x328 over 13d)  cert-manager  The certificate request has failed to complete and will be retried: Failed to wait for order resource "kimconnect-cert-qlnl9-1800784958" to become ready: order is in "invalid" state:

kim@controller01:~$ k get certificaterequests.cert-manager.io
NAME                          READY   AGE
kimconnect-cert-jqlvf         True    90d
kimconnect-cert-qlnl9         False   30d

# Try to delete cert requests and secrets, and wait for cert to regenerate
k delete certificaterequests kimconnect-cert-qlnl9
k delete secret kimconnect-cert

# Try to force cert to renew before 1440 hours (immediately)
kubectl patch certificate kimconnect-cert --patch '
- op: replace
  path: /spec/renewBefore
  value: 1440h
' --type=json

# Wait for cert to become ready, then reverse the change
kubectl patch certificate kimconnect-cert --patch '
- op: remove
  path: /spec/renewBefore
' --type=json

# Worst case scenario, delete the cert and recreate it
k delete cert kimconnect-cert
cat <<EOF > kimconnect-cert.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: kimconnect-cert
  namespace: default
  annotations:
    kubernetes.io/ingress.class: "nginx"
    acme.cert-manager.io/http01-edit-in-place: "true"
    kubernetes.io/tls-acme: "true"
spec:
  dnsNames:
    - kimconnect.com
    - www.kimconnect.com
  secretName: kimconnect-cert
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
EOF
kubectl apply -f kimconnect-cert.yaml

PowerShell: Move Virtual Machine Storage Using VMM

# moveVmStorageUsingVmm.ps1
# Version 0.01

$vmNames=@(
  'TESTVM0001',  
  'TESTVM0002',
  'TESTVM0003'
)
$storageLocations=@(
  'C:\ClusterStorage\BLOB001',
  'C:\ClusterStorage\BLOB002',
  'C:\ClusterStorage\BLOB003'
)
$storageMaxPercent=79
$confirmation=$true

function moveVmStorageUsingVmm($vmName,$newStorage,$storageMaxPercent=80){  
  try{
    $vmHosts=Get-SCVMHost
    $vm=Get-SCVirtualMachine -Name $vmName
    if($vm.count -eq 1){
      $currentHost=$vm.Hostname
      $storage=Get-SCStorageVolume -VMHost $currentHost|?{$_.Name -eq $newStorage}
      $capacity=$storage.Capacity
      $freespace=$storage.FreeSpace
      $storageUtilizedPercent=[math]::round(($capacity-$freespace)/$capacity*100,2)
      $totalSize=0
      $disks = Get-SCVirtualDiskDrive -VM $vmname
      $disks.VirtualHardDisk.Size|%{$totalSize+=$_}      
      $projectedPercent=[math]::round(($capacity-$freespace+$totalSize)/$capacity*100,2)
      write-host "$newStorage current utilization percentage: $storageUtilizedPercent`% and projected: $projectedPercent`% after adding $([math]::round($totalSize/1GB,2))GB's"
      $storageFeasible=if($projectedPercent -lt $storageMaxPercent){$true}else{$false}
      if($storageFeasible){
        $vmHost=$vmHosts|?{$_.Name -eq $currentHost}
        Move-SCVirtualMachine -VM $vm -VMHost $vmHost -Path $newStorage -UseLAN -UseDiffDiskOptimization # -RunAsynchronously
        return $true
      }else{
        write-warning "Infeasible storage location: Available storage volume is $storageMaxPercent`% and projected is $projectedPercent`%"
        return $false
      }
    }else{
      write-warning "$vmName matches more than 1 guest VM's; hence, this item is skipped."
      return $null
    }
  }catch{
    write-warning $_
    return $false
  }
}

function moveStorage($vmNames,$storageLocations,$storageMaxPercent,$confirmation){
  $storageIndex=0
  $useSameStorage=$true
  if($vmNames.count -gt 1){
    for($i=0;$i -lt $vmNames.count;$i++){
      $vmName=if($useSameStorage){$vmNames[$i]}else{$vmNames[--$i]}
      try{
        $null=Get-SCVirtualMachine -Name $vmName|Read-SCVirtualMachine -ea Stop
        $storageLocation=if($useSameStorage){$storageLocations[$storageIndex]}else{$storageLocations[++$storageIndex]}
        if($storageLocation){
          if($confirmation){
            write-host "Move $vmName storage to $storageLocation`?"
            pause
          }else{
            write-host "Moving $vmName storage to $storageLocation ..."
          }
          $useSameStorage=moveVmStorageUsingVmm $vmName $storageLocation $storageMaxPercent
        }else{
          write-warning "Exhausted storage locations to move VM's"
          return $false
        }
      }catch{
        write-warning $_
        # Get-SCVirtualMachine -Name $vmName|Repair-SCVirtualMachine -force # I haven't tested this
        return $false
      }
    }
  }else{
    [string]$vmname=$vmNames
    try{
      $null=Get-SCVirtualMachine -Name $vmname|Read-SCVirtualMachine -ea Stop
      foreach($storageLocation in $storageLocations){
        if($storageLocation){
          if($confirmation){
            write-host "Move $vmname storage to $storageLocation`?"
            pause
          }else{
            write-host "Moving $vmname storage to $storageLocation ..."
          }
          $success=moveVmStorageUsingVmm $vmName $storageLocation $storageMaxPercent
          if($true -eq $success){
            return $true
          }else{
            write-warning "$vmname cannot be moved to $storageLocation"
          }
        }else{
          write-warning "Exhausted storage locations to move VM's"
          return $false
        }
      }      
    }catch{
      write-warning $_
      return $false
    }
  }
}

moveStorage $vmNames $storageLocations $storageMaxPercent $confirmation

Get Hyper-V Cluster Automatic Balancing Configurations

# getHyperVLoadBalanceMode.ps1

$clustername=(Get-Cluster).Name
function getHyperVLoadBalanceMode($clustername=(Get-Cluster).Name){

    $AutoBalancerLevel=[hashtable]@{
        '1'='Low | Move when host is more than 80% loaded (default)'
        '2'='Medium | Move when host is more than 70% loaded'
        '3'='High | Average nodes and move when host is more than 5% above average'
    }
    
    $AutoBalancerMode=[hashtable]@{
        '0'='Disabled'
        '1'='Load balance on node joins'
        '2'='Load balance on node joins and every 30 minutes (default)'
    }
    try{
        $level=(Get-Cluster $clustername).AutoBalancerLevel.toString()
        $levelDescription=$AutoBalancerLevel[$level]
        $mode=(Get-Cluster $clustername).AutoBalancerMode.toString()
        $modeDescription=$AutoBalancerMode[$mode]
        write-host "Clustername: $clusterName`r`n - AutoBalanceLevel: $level $levelDescription`r`n - AutoBalanceMode: $mode $modeDescription"
    }catch{
        write-warning $_
    }

}

getHyperVLoadBalanceMode $clustername
# Sample output
PS C:\Windows\system32> getHyperVLoadBalanceMode
Clustername: cluster1
 - AutoBalanceLevel: 1 (default) Low Move when host is more than 80% loaded
 - AutoBalanceMode: 2 (default) Load balance on node join and every 30 minutes

PowerShell: Create Hyper-V Guest VM From Virtual Disk (VHDX)

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

PowerShell: Find Guest VMs Associated with a Certain Storage Path

# findGuestMvsByStorage.ps1

$storagePath='\\SMBSERVER009'

function getAllGuestVms($clusterName){
  try{
    Import-Module FailoverClusters
    $clusterName=if($clusterName){
      $clustername
    }else{
      (get-cluster).name
    }
    $allHyperVHosts={(Get-ClusterNode -Cluster $clusterName|?{ $_.State -eq "Up" }).Name | %{$_.ToLower()}}.Invoke()
    $allVms=foreach ($hyperVHost in $allHyperVHosts){
      invoke-command -computername $hyperVHost -scriptblock{
        write-host "Getting VM List on $env:computername";
        Get-VM |select-object Name,State,Status,Path
      }|select-object * -ExcludeProperty RunspaceId,PSShowComputerName
    }
    if($allVms){return $allVms}else{return $null}
  }catch{
    write-warning $_
    return $false
  }
}
function findGuestMvsByStorage{
  param (
    $storagePath,
    $clusterName=$null
    )
  try{
      $allVms=getAllGuestVms $clusterName
      if(!$allVms){return $null}
      $matchedStorage=$allVms|?{$_.Path -like "*$storagePath*"}
      $online=$matchedStorage|?{$_.State.Value -like 'Running*'}
      $offline=$matchedStorage|?{$_.State.Value -notlike 'Running*'}
      if($online){
        write-host "There are $($online.count) online VMs associated with '$storagePath'"
      }
      if($offline){
        write-host "There are $($offline.count) offlined VMs associated with '$storagePath'"
      }
      if($matchedStorage){
        $matchedStorage
        return $matchedStorage
      }else{
        write-host "'$storagePath' is not being associated with any VM in cluster '$((get-cluster).Name)'"
        return $null
      }
  }catch{
      write-warning $_
      return $false
  }
}

$result=findGuestMvsByStorage $storagePath
$result|select Name,State,PSComputerName
# Sample Output
PS C:\Windows\system32> $result=findGuestMvsByStorage $storagePath
Getting VM List on HYV01
Getting VM List on HYV02
Getting VM List on HYV03
Getting VM List on HYV04
Getting VM List on HYV05
Getting VM List on HYV06
Getting VM List on HYV07
Getting VM List on HYV08
Getting VM List on HYV09
Getting VM List on HYV10
Getting VM List on HYV11
Getting VM List on HYV12
Getting VM List on HYV13
Getting VM List on HYV14
Getting VM List on HYV15
Getting VM List on HYV16
Getting VM List on HYV17
Getting VM List on HYV18
Getting VM List on HYV19
There are 6 online VMs associated with '\\FILESERVER009'
There are 16 offlined VMs associated with '\\FILESERVER009'

PS C:\Windows\system32> $result|select Name,State
Name                           State
----                           -----
TESTVM01                       OffCritical
TESTVM02                       Off
TESTVM03                       OffCritical
TESTVM04                       OffCritical
TESTVM05                       RunningCritical
Other project: DragonCoin.com