PowerShell: How to Change Guest VM Names in Virtual Machine Manager

# changeGuessVmNameInVmm.ps1
# The following script prepend all VM's matching certain pattern
# This is useful to mark VM's to be deleted for ease of administration

$nameLike='test'
$prefix='(decom 02-01-2020) '

$targetVms=Get-SCVirtualMachine|?{$_.Name -like "*$nameLike*"} 
foreach($targetVm in $targetVms){
    try{
        $oldName=$targetVm.Name
        $newName="$($prefix+$oldName)"
        $changed=Set-SCVirtualMachine -VM $targetVm -Name $newName
        if($changed.name -eq $newName){
            write-host "$oldName's name has been changed to $newName" -foregroundcolor Green
        }else{
            write-host "$oldName's name has NOT been changed to $newName" -foregroundcolor Red
        }
    }catch{
        write-warning $_        
    }    
}

Resume All Guest VMs in a Clustered Hyper-V Environment

# resumeAllVms.ps1
# Significance: this script is useful in a Clustered Hyper-V environment
# where storage was down for over 60 seconds, causing Hyper-V to put all guest VM's into 'paused-critical' statuses.
# During such event, the cluster would be unreachable as well. Hence, knowing node names prior to pasting this script
# would be necessary.

$computerlist=@'
hyperVServer1
hyperVServer2
hyperVServer100
'@
$computernames=@($computerlist -split "`n")|%{$_.Trim()}
foreach ($computername in $computernames){
  invoke-command $computername {
    write-host "Connected to $env:computername..."
    #Get list of all VMs on this host
    #$vmNames=(get-vm).Name
    #write-host "$env:computername`:`r`n$($vmNames|out-string)"
    
    # Unpausing all VMs
    $pausedVms=get-vm | where state -eq 'paused'
    foreach($vm in $pausedVMs){        
        write-host $vm.Name -nonewline
      try{        
        $vm|resume-vm -ea stop
        write-host ": resumed" -foregroundcolor Green
      }catch{
        write-warning $_
        continue
      }    
    }   
  }
}

How To Delete Virtual Machine in Unsupported Configuration Status

Possible Errors in VMM Console:

Error (20408)
VMM could not get the specified instance Msvm_VirtualSystemSettingData.InstanceID="Microsoft:1D78A299-C989-40FC-BC5C-B54934A126B7" of class Msvm_VirtualSystemSettingData on the server NODE002. The operation failed with error HRESULT 0x80004005 The WS-Management service cannot process the request. The service cannot find the resource identified by the resource URI and selectors.

Recommended Action
Ensure the provider is running, and then try the operation again.
Error (20411)
VMM could not invoke method ImportSystemDefinition on Msvm_VirtualSystemManagementService: Hyper-V Virtual System Management Service (Name = "vmms", CreationClassName = "Msvm_VirtualSystemManagementService", SystemCreationClassName = "Msvm_ComputerSystem", SystemName = "NODE002") on the server NODE002. Failed with error HRESULT 0x80338029 The WS-Management service cannot complete the operation within the time specified in OperationTimeout.

Recommended Action
Ensure the provider is running, and then try the operation again.

Warning (10655)
Hardware changes while cloning a Hyper-V virtual machine, VMware virtual machine, or stored virtual machine are not supported and were ignored.

Recommended Action
Make any hardware changes to the virtual machine after the cloning operation is complete.
Error (809)
VMM cannot remove the virtual machine because it is in the Unsupported Cluster Configuration state.

Recommended Action
Change the virtual machine's state, and then try the operation again.

Resolution:

# Resolution
$vmName='TESTVM003'
Get-SCVirtualMachine $vmName|Remove-SCVirtualMachine -Force

Upgrade Virtual Hardware Version in VMM

The following script will upgrade all guest virtual machines from a lower version to the desired version:

# upgradevHardwareVersion.ps1

# User inputs
$desiredVersion='9.0'
$excludeKeywords=@(
  'lax',
  'aws'
)
$vmmServer=$env:computername

function updateVmVersion($vmNames,$desiredVersion='9.0',$vmmServer=$env:computername){
    $results=@()
    foreach($vmName in $vmNames){
        $version=.{            
            try{
                $vm=get-scvirtualmachine $vmName -vmmServer $vmmServer
                $previousVersion=$vm.Version
                if($vm.VirtualMachineState -eq 'Running' -and [version]$previousVersion -lt [version]$desiredVersion){
                    $null=stop-scvirtualmachine -vm $vm
                    $null=Update-SCVMVersion -vm $vm
                    $vm=start-scvirtualmachine -vm $vm
                    if($vm.status -eq 'Running'){
                        write-host "$vmName has been updated from version $previousVersion to $($vm.Version) and is now back online"
                    }else{
                        write-host "$vmName has been updated from version $previousVersion to $($vm.Version) but it's now offline"
                        return $previousVersion
                    }
                }elseif($vm.VirtualMachineState -eq 'PowerOff' -and [version]$previousVersion -lt [version]$desiredVersion){
                    $null=Update-SCVMVersion -vm $vm
                }
                return $vm.Version
            }catch{
                write-warning $_
                return $previousVersion
            }
        }
        $results+=@{$vmName=$version}
    }
    return $results
}

# Get the report
$vmsWithLowerVersions=get-scvirtualmachine|?{[version]$_.version -lt [version]$desiredVersion}
write-host "Processing list:`r`n$($vmsWithLowerVersions|select name,version|out-string)"
$filteredList=$vmsWithLowerVersions|?{$name=$_.Name;$null -eq $($excludeKeywords|?{$name -like "*$_*"})}
write-host "Filtered list:`r`n$($filteredList|select name,version|out-string)"
$inversedList=$vmsWithLowerVersions|?{$name=$_.Name;$null -ne $($excludeKeywords|?{$name -like "*$_*"})}
write-host "Inverted list:`r`n$($inversedList|select name,version|out-string)"

# Upgrade all VMs with lower versions
updateVmVersion $vmsWithLowerVersions.Name $desiredVersion $vmmServer

Upgrade a selected list of VMs

$vmsToUpgrade=@"
eqwebmdadfs01
test
test1
"@
$vmmServer='lax-vmm01-kimconnect.com'
$desiredVersion='9.0'

function updateVmVersion($vmNames,$desiredVersion='9.0',$vmmServer=$env:computername){
    $results=@()
    foreach($vmName in $vmNames){
        $version=.{            
            try{
                $vm=get-scvirtualmachine $vmName -vmmServer $vmmServer
                $previousVersion=$vm.Version
                if($vm.VirtualMachineState -eq 'Running' -and [version]$previousVersion -lt [version]$desiredVersion){
                    $null=stop-scvirtualmachine -vm $vm
                    $null=Update-SCVMVersion -vm $vm
                    $vm=start-scvirtualmachine -vm $vm
                    if($vm.status -eq 'Running'){
                        write-host "$vmName has been updated from version $previousVersion to $($vm.Version) and is now back online"
                    }else{
                        write-host "$vmName has been updated from version $previousVersion to $($vm.Version) but it's now offline"
                        return $previousVersion
                    }
                }elseif($vm.VirtualMachineState -eq 'PowerOff' -and [version]$previousVersion -lt [version]$desiredVersion){
                    $null=Update-SCVMVersion -vm $vm
                }
                return $vm.Version
            }catch{
                write-warning $_
                return $previousVersion
            }
        }
        $results+=@{$vmName=$version}
    }
    return $results
}

$vmnames=@($vmsToUpgrade -split "`n")|%{$_.Trim()}
write-warning "Please confirm that we're ready to have these virtual machines rebooted:`r`n$vmnames"
pause
updateVmVersion $vmnames $desiredVersion $vmmServer

Simple version – upgrading only VMs that are offline

$vmsToUpgrade=@"
TEST
TEST1
"@
$vmmServer='lax-vmm01-kimconnect.com'

$vmnames=@($vmsToUpgrade -split "`n")|%{$_.Trim()}
foreach($vmName in $vmNames){
    $vm=get-scvirtualmachine $vmName -vmmServer $vmmServer
    if($vm.VirtualMachineState -eq 'PowerOff'){
        try{
            $oldVersion=$vm.Version
            $null=Update-SCVMVersion -vm $vm
            $newVersion=$vm.Version
            write-host "$vmName virtual hardward has been upgraded from version $oldVersion to $newVersion"
            pause
        }catch{
            write-warning $_
        }

    }
}

How to Upgrade Kubernetes Ingress Nginx Deployed via Helm

# How to upgrade ingress-nginx:
helm upgrade --reuse-values ingress-nginx ingress-nginx/ingress-nginx -n ingress-nginx

# Sample output of a failure scenario:

brucelee@k8-controller:~$ helm upgrade --reuse-values ingress-nginx ingress-nginx/ingress-nginx -n ingress-nginx
Error: UPGRADE FAILED: template: ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml:52:24: executing "ingress-nginx/templates/admission-webhooks/job-patch/job-patchWebhook.yaml" at <.Values.controller.admissionWebhooks.patchWebhookJob.resources>: nil pointer evaluating interface {}.resources

 

# How to force uninstall ingress-nginx and re-install it:
 
helm delete ingress-nginx -n ingress-nginx
# Delete a pod stuck in terminating status
podName=ingress-nginx-controller-zlqbl
kubectl delete pod $podName --grace-period=0 --force --namespace ingress-nginx
# Pull most updated versions of applications from source
helm repo update
# Export helm's ingress-nginx yaml config file
helm show values ingress-nginx/ingress-nginx > ingress-nginx.yaml
# edit the values
vim ingress-nginx.yaml

# change these three (3) values

  hostNetwork: true # change this value to true
 

  hostPort:
    enabled: true # set this value to true


  kind: DaemonSet # modify this from Deployment to DaemonSet
# Reinstall ingress-nginx using the edited yaml file:
helm install ingress-nginx ingress-nginx/ingress-nginx -n ingress-nginx --values ingress-nginx.yaml
# Check results:
k -n ingress-nginx get all

PowerShell: Move Guest VM to Different Cluster in Hyper-V

# Assumptions:
# - Guest VM files are stored at a single location
# - The service account or admin user has access to source and destination
# - Source and destination storage paths are joined to the same domain

$vmName='TESTWINDOWS'
$sourceStorage='C:\ClusterStorage\Blob1\TESTWINDOWS'
$destinationStorage='\\LAX-SMB1\VMS\TESTWINDOWS'

# step 1: Stop VM at Source and copy its files to Destination Storage
$vm=try{get-vm $vmName}catch{$null}
if($null -ne $vm){
    stop-vm $vmName -force -Confirm:$false
    # Remove-VM -VM $vm -DeletePermanently:$false -Confirm:$false # Unregister VM
    robocopy $sourceStorage $destinationStorage /E /R:0 /NP
}else{
    write-warning "$vmName is not found."
}

# Step 2: Register VM at destination
$parentPath='\\LAX-SMB1\VMS\TESTWINDOWS'
$vmPath=join-path $parentPath '\Virtual Machines\'
$vmcxFile=(gci $vmpath|?{$_.extension -like '.vmcx'}).FullName
Import-VM -Path $vmcxFile -copy $parentPath -GenerateNewId

# Step 3: Make adjustments

# While disconnected from network, run this command within the Windows VM
C:\Windows\System32\Sysprep\sysprep.exe /generalize
# Connect VM to the correct subnet / vlan
# Join to domain if necessary
# Verify that all disks are attached

# Step 4: Add vm into cluster
$vmName='TESTWINDOWS'
$targetCluster=(get-cluster).Name
 
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 $vmName $targetCluster

PowerShell: How To Install VMM Agent on Hyper-V Nodes

# Update these variables to match your system
$appName='Microsoft System Center Virtual Machine Manager Agent (x64)'
$desiredVersion='10.22.1287.0'
$msiFile='X:\System Center Virtual Machine Manager\amd64\Setup\msi\Agent\vmmAgent.msi'
$maxWaitSeconds=120
$computersList = @'
    HYPERV001
    HYPERV002
'@

function installMsiOnRemoteComputer{
    param(
        $computernames=$env:computername,
        $msiFile,
        $destinationLocalTempFolder='C:\Temp',
        $testFileName='testfile.txt'
    )
    
    function translateLocalPathToSmbPath($computername,$localPath,$testFileName){
        $adminDriveLetter=[regex]::match($localPath,'^([\w\W])\:').captures.groups[1].value
        $partialPath=[regex]::match($localPath,'^([\w\W])\:(.*)').captures.groups[2].value
        $testPath=join-path "\\$computername\$adminDriveLetter`$" "$partialPath"
        if(!(test-path $testPath)){
            try{
                New-Item -Path $testPath -ItemType "directory" -force
            }catch{
                write-warning "Unable to create $testPath"
                return $false
            }
        }
        try{
            $null=New-Item -Path $testPath -Name $testFileName -ItemType "file" -force
            Remove-Item "$testPath\$testFileName" -force
            return $testPath
        }catch{
            write-warning "Unable to read or write to $testPath"
            return $false
        }        
    }

    $results=[hashtable]@{}
    $msiLocalFilePath=join-path $destinationLocalTempFolder $(split-path $msiFile -leaf)
    foreach ($computername in $computernames){
        $translatedDestination=translateLocalPathToSmbPath $computername $destinationLocalTempFolder $testFileName
        if($translatedDestination){
            copy-item $msiFile $translatedDestination
        }else{
            write-warning "Unable to copy $msiFile to $translatedDestination"
            $results+=[hashtable]@{$computername=$false}
            continue
        }        
        $psSession=new-psSession $computername
        if($psSession.State -eq 'Opened'){
            $result=invoke-command -session $pssession -scriptblock{
                param($filePath)
                $file=gi $filePath
                $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
                )
                try{
                    [diagnostics.process]::start("msiexec.exe", $MSIArguments).WaitForExit()
                    write-host "MSIEXEC has been called for file $filePath on $env:computername"
                    return $true
                }catch{
                    write-warning $_
                    return $false
                }             
            } -Args $msiLocalFilePath

            # Note Error:
            # Resolved by not using -wait switch and calling [diagnostics.process] instead of Start-Process "msiexec.exe"
            # Although, this error would still being thrown with the [diagnostics.process] result of success
            # Processing data for a remote command failed with the following error message: The I/O operation has been aborted
            # because of either a thread exit or an application request. For more information, see the about_Remote_Troubleshooting
            # Help topic.
            #     + CategoryInfo          : OperationStopped: (:String) [], PSRemotingTransportException
            #     + FullyQualifiedErrorId : JobFailure
            #     + PSComputerName        : 

            $results+=[hashtable]@{$computername=$result}
            remove-psSession $psSession
        }else{
            write-warning "Unable to connect to $computername"
            $results+=[hashtable]@{$computername="Unable to connect to $computername"}
        }
    }
    return $results
}

function removeAppwizProgram($computernames=$env:computername,$appName='Firefox'){
    $results=[hashtable]@{}
    foreach($computer in $computernames){
        $session=new-pssession $computer
        if($session.State -eq 'Opened'){
            $result=invoke-command -session $session -scriptblock{
                param($appName)
                write-host "Checking $env:computername..."
                try{
                    # Method 1: try using the Uninstall method of the application packager
                    $app=Get-WmiObject -Class Win32_Product -Filter "Name='$appName'"
                    if($app.Name -eq $appName){
                        write-host "Uninstalling $app"
                        # pause
                        $null=$app.Uninstall()
                        $appStillExists=Get-WmiObject -Class Win32_Product -Filter "Name='$appName'"
                        if($appStillExists){
                            write-host "'$appName' still exists"
                            # Method 2: Using Registry
                            $uninstallStringRegPaths='HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall','HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
                            $uninstallStrings=Get-ChildItem -Path $uninstallStringRegPaths
                            $uninstallString=($uninstallStrings|Get-ItemProperty|Where-Object {$_.DisplayName -match $appName}).UninstallString
                            if($uninstallString.count -eq 1){
                                $appCode=[regex]::match($uninstallString,'\{(.*)\}').Value
                                $uninstallCommand="& msiexec.exe /x $appCode /quiet /norestart"
                                write-host "Invoking uninstall Command: $uninstallCommand"
                                Invoke-Expression $uninstallCommand
                                $appStillExists=Get-WmiObject -Class Win32_Product -Filter "Name='$appName'"
                                if($appStillExists){
                                    write-warning "Uninstall has been unsuccessful at removing $appName"
                                    return $false
                                }else{
                                    return $true
                                }
                            }else{
                                write-warning "Please check this/these uninstall string(s):`r`n$uninstallString"
                                return $false
                            }
                        }else{
                            write-host "'$appName' has been removed"
                            return $true
                        }
                    }else{
                        write-host "No matches for $appName"
                        return $true
                    }                   
                }catch{
                    write-warning $_
                    return $false
                }
            } -Args $appName
            $results+=@{$computer=$result}
            remove-pssession $session
        }else{
            write-warning "Unable to connect to $computer"
            $results+=@{$computer=$null}
        }        
    }
    return $results
}

function sortArrayStringAsNumbers([string[]]$names){
    $hashTable=@{}
    $maxLength=($names | Measure-Object -Maximum -Property Length).Maximum
    foreach ($name in $names){
        #[int]$x=.{[void]($name -match '(?:.(\d+))+$');$matches[1]}
        #$x=.{[void]($name -match '(?:.(\d+)+)$');@($name.substring(0,$name.length-$matches[1].length),$matches[1])}
        $originalName=$name
        $x=.{Clear-Variable matches
            [void]($name -match '(?:.(\d+)+)\w{0,}$');
            if($matches){
                [int]$trailingNonDigits=([regex]::match($name,'\D+$').value).length
                if($trailingNonDigits){
                    $name=$name.substring(0,$name.length-$trailingNonDigits)
                }
                return ($name.substring(0,$name.length-$matches[1].length))+$matches[1].PadLeft($maxLength,'0');
            }else{
                return $name+''.PadLeft($maxLength,'0');
            }}
        $hashTable.Add($originalName,$x)
        }
    $sorted=foreach($item in $hashTable.GetEnumerator() | Sort Value){$item.Name}
    return $sorted
}

function main{
    $computerNames=sortArrayStringAsNumbers(@($computersList -split "`n" -replace "\..*$")|%{$_.tostring().trim()})
    $results=[hashtable]@{}
    foreach($node in $computernames){
        write-host "Processing $node ..."
        $appVersionPassed=invoke-command -computername $node {
            param($appName,$desiredVersion)
            $matchedApp=Get-WmiObject -Class Win32_Product -Filter "Name='$appName'"
            if($matchedApp.Version -eq $desiredVersion){
                return $true
            }else{
                return $false
            }
        } -Args $appName,$desiredVersion
        
        if(!$appVersionPassed){
            $vcredist140Installed=invoke-command -computername $node {        
                $null=choco install vcredist140 -y
                $chocoApps=choco list -l
                if($chocoApps -match 'vcredist140'){
                    return $true
                }else{
                    return $false
                }
            }
            if($vcredist140Installed){
                $appRemoved=removeAppwizProgram $node $appName
                if(($appRemoved|out-string) -match "True"){
                    try{
                        installMsiOnRemoteComputer $node $msiFile
                    }catch{
                        write-warning $_                    
                    }
                    write-host "Now waiting up to $maxWaitSeconds seconds before checking on the install result"
                    $timer=[System.Diagnostics.Stopwatch]::StartNew()
                    do{                        
                        start-sleep -seconds 5
                        $appVersionPassed=invoke-command -computername $node {
                            param($appName,$desiredVersion)
                            $matchedApp=Get-WmiObject -Class Win32_Product -Filter "Name='$appName'"
                            if($matchedApp.Version -eq $desiredVersion){
                                return $true
                            }else{
                                return $false
                            }
                        } -Args $appName,$desiredVersion
                        if($appVersionPassed){
                            write-host "$appName $desiredVersion installed on $node successfully"
                            $results+=[hashtable]@{$node='$appName $desiredVersion installed'}                            
                        }
                        $exitCondition=$timer.elapsed.totalseconds -ge $maxWaitSeconds 
                    }until($appVersionPassed -or $exitCondition)
                    if(!$appVersionPassed){                        
                            write-host "$appName $desiredVersion has NOT been installed successfully on $node"
                            $results+=[hashtable]@{$node='$appName $desiredVersion NOT installed'}
                    }
                    $timer.stop()
                }else{
                    write-warning "Unable to uninstall outdated app on $node"
                    $results+=[hashtable]@{$node='Unable to uninstall outdated app'}
                }
            }else{
                $results+=[hashtable]@{$node='Unable to install vcredist140'}
            }
        }else{
            write-host "$appName is already at $desiredVersion"
            $results+=[hashtable]@{$node='$appName is already at $desiredVersion'}
        }
    }
    return sortArrayStringAsNumbers($results.GetEnumerator()|select Name,Value)
}

main

PowerShell: Remove Virtual Machine Snapshots in VMM

Base Cmdlets:

$vmmServer='SOMETHINGHERE'
$vmName='VMNAMEHERE'
$snapshots=Get-SCVMCheckpoint -vmmserver $vmmServer -vm $(get-scvirtualmachine $vmName)
$snapshots|%{Remove-SCVMCheckpoint -VMCheckpoint $_}

Automation:

# removeVmSnapshotsInVmm.ps1

$vmmServers=@(
    'vmm01.kimconnect.com',
    'vmm02.kimconnect.com'
)
$daysThreshold=30
$maxConfirmationsCount=3           

function removeVmSnapshotsInVmm{
    param(
        $vmmServers=$env:computername,
        $daysThreshold=30,
        $maxConfirmationsCount=3
    )
    foreach($vmmServer in $vmmServers){
        $session=new-pssession -computername $vmmServer -credential (get-credential -message "Admin Credential to $vmmServer")
        if($session.State -eq 'Opened'){
            invoke-command -session $session{
                param($daysThreshold,$maxConfirmationsCount)
            
                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. Input CANCEL to skip this item.";
                        if ($userInput.ToLower() -eq $testValue.ToLower()){
                            $confirmed=$true;
                            write-host "Confirmed!`r`n";
                            break;                
                        }elseif($userInput -like 'cancel'){
                            write-host 'Cancel command received.'
                            $confirmed=$false
                            break
                        }else{
                            cls;
                            $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;
                }
                
                $snapshotsToRemove=Get-SCVMCheckpoint -vmmserver localhost|?{$_.AddedTime -lt (get-date).adddays(-$daysThreshold)}
                foreach($snapshot in $snapshotsToRemove){
                    $snapshotName=$snapshot.Name
                    $vm=$snapshot.VM
                    $vmState=$vm.Status
                    $confirmed=if($maxConfirmationsCount-- -gt 1){confirmation "Delete snapshot $snapshotName ?"}else{$true}
                    if($confirmed){
                        $ready=if($vmState -eq 'Running'){
                            $true
                        }else{
                            try{                                
                                    Repair-SCVirtualMachine -VM $vm -Dismiss                                
                                    $true
                                }catch{
                                    write-warning $_
                                    $false
                                }
                        }
                        if($ready){Remove-SCVMCheckpoint -VMCheckpoint $snapshot}                        
                    }else{
                        write-host "Snapshot $snapshotName NOT removed."
                        break
                    }
                }
            } -Args $daysThreshold,$maxConfirmationsCount
            Remove-PSSession $session
        }else{
            write-warning "Unable to connect to VMM Server $vmmServer."
        }
    }
}

removeVmSnapshotsInVmm $vmmServers $daysThreshold $maxConfirmationsCount

Virtual Machine Manager Error ID 23351 FirstBootDevice Invalid

When moving, importing, exporting Generation 2 template in VMM, the following error occurs when trying to deploy :

The input string "
" for the FirstBootDevice parameter cannot be parsed and may be invalid.
Make sure the string uses one of the following formats: "SCSI,BusId(integer),LunId(integer)" or "NIC,SlotId(integer)"
ID: 23351

Here’s a quick Fix:

Set-SCVMTemplate -Name 'TEMPLATENAME' -FirstBootDevice 'SCSI,0,0'

Better yet, it’s advisable to include the cmdlet above into the importing function of VM templates. A Try-Catch clause with error 23351 keyword could be coded to handle these exceptions during VM provisioning automations.

Hyper-V Dependency on Docker’s Networking Bridge Drivers

Background:

Some naive systems engineer (yours truly) has provisioned a buck load of Hyper-V servers and added them into a cluster. All functionalities seem to be working fine, except for certain anomalies where client DNS records seem to be dropped from Active Directory randomly. This has caused user problems as Active Directory requires that each machine has a resolvable a-record to be joined into its domain. Moreover, certain services, such as DHCP servers sometimes could not reach their replicating counterparts. Worst yet, since DHCP affects multiple VLANs, when those services are off-line, many users are affected. An investigation has been called to address this problem.

Issues:

Certain Windows Virtual Machine clients in Hyper-V are not able to connect to Active Directory, and their DNS records are purged by AD-Integrated DNS. Moreover, DHCP servers are placed in the same VLAN as AD/DNS servers. Sometimes, those services are unreachable as well.

Immediate Causes:

– Virtual machines cannot reach DNS server that are on the same VLAN
– DNS registrations are ‘dynamic’; hence, DNS records would get scavenged after a certain period of time if DNS clients do not contact the DNS server
– DHCP server’s config settings such as ‘Discard A and PTR records when lease is deleted’ and ‘Dynamically update DNS records for DHCP clients that do not request update (for example, clients running Windows NT 4.0), would cause client DNS records to be purged

Root Cause:

– Since the DNS virtual machine resides in VLAN that is out of scope of the main switch’s subnet or supernet, its MAC address does not advertise within the VM’s VLAN. However, Hyper-V does have a MAC Table of such DNS server. Hence, Hyper-V can route traffic from other VLAN’s toward the DNS server, while it cannot switch/bridge layer 2 frames of VM’s of the same VLAN where DNS server belongs.
– Hyper-V has a built-in protocol suite named NetworkVirtualization that is adequate for most purposes. However, it doesn’t perform bridging of out-of-scope VLAN automatically
– Docker for Windows suite named Containers has an important networking protocol, “Bridge Driver” that can perform bridging of all VLAN’s within the virtual switches
– NetworkVirtualization (a component of Hyper-V) cannot be installed along with l2bridge.sys (a component of Containers)
– Thus, the Docker component must be installed along with Hyper-V to enable intra-vlan bridging.

Summary of proposed changes:
 - Record IP config prior to changes:

   IPv4 Address. . . . . . . . . . . : 172.16.20.98(Preferred)
  Subnet Mask . . . . . . . . . . . : 255.255.255.0
   Default Gateway . . . . . . . . . : 172.16.20.1
   DNS Servers . . . . . . . . . . . : 172.16.10.30
                                      172.16.10.31

 - Create a local account: New-LocalUser 'backupadmin' -Password $(Read-Host -AsSecureString)

 - Add account into Administrators group: Add-LocalGroupMember -Group "Administrators" -Member 'backupadmin'

 - Include FailoverClusters module into PowerShell: Import-Module FailoverClusters

 - Put node in maintenance mode (cordon): Suspend-ClusterNode -Name $env:computername

 - Stop clustering services on this node: Stop-ClusterNode -Name $env:computername

 - Remove vSwitch: Remove-VMSwitch -Name 'External-Connection' -force

 - Remove conflicting features: remove-windowsfeature NetworkVirtualization -force

 - Install 'Bridge Drivers' software suite: Install-WindowsFeature -Name Containers

 - Reboot node: Restart-Computer -Force

 - Run ncpa.cpl to verify that the vEthernet is purged

   - If Not purged:

    - run this command: netcfg -d
    - Also do this: run devmgmt.msc > remove virtual ethernet adapter
    - Remove Hyper-V to force purging of vSwitches: Remove-windowsfeature Hyper-V,NetworkVirtualization -Force -restart
     - Reinstall Hyper-V: Install-windowsfeature Hyper-V,Containers -restart

   - If Purged: go to the next step

 - Install virtual network adapter and switch:

  - Install vSwitch: New-VMSwitch -Name 'Trunk' -NetAdapterName "TEAM1" -AllowManagementOS $true

  - Add Management VLAN: Get-VMNetworkAdapter -SwitchName 'Trunk' -ManagementOS|Set-VMNetworkAdapterVlan -Access -VlanId 101

# Script
$switchName='Trunk'
$adapterName='NIC1' # change this value to reflect the correct interface
$vlanId=100 # change this to the correct VLAN
function addVirtualSwitch($switchName,$adapterName,$vlanId){
New-VMSwitch -name $switchName -NetAdapterName $adapterName -AllowManagementOS $true
Enable-VMSwitchExtension -VMSwitchName $switchName -Name "Microsoft Windows Filtering Platform"
if($vlanId)
{ Get-VMNetworkAdapter -SwitchName $switchName -ManagementOS|Set-VMNetworkAdapterVlan -Access -VlanId $vlanId }
}
addVirtualSwitch $switchName $adapterName $vlanId

 - Configure vEthernet adapter (if necessary)

  - Enable these protocols
     client for Microsoft Networks
     File and Printer Sharing for Microsoft Networks
     QoS Packet Scheduler
     Bridge Driver
     Internet Protocol Version 4 (TCP/IPv4)
     Microsoft LLDP Protocol Driver
     Internet Protocol Version 6 (TCP/IPv6)
     Link-Layer Topology Discovery Responder
     Link-Layer Topology Discovery Mapper I/O Driver
  - Set Static IP
  - Set default GW
   - Set DNs1/DNS2

 - Register with DNS Server: ipconfig /registerdns
 - Ensure that all auto-start services are running
 - Start clustering services on this node: Start-ClusterNode -Name $env:computername
 - Wait a few minutes for node to re-associate with its original cluster
  - Resume roles of all suspended nodes: Resume-ClusterNode -Name $env:computername -Failback Immediate

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

Quick Script:

# fixIncompleteVmConfig.ps1

$IncompleteVMConfig=Get-SCVirtualMachine|?{$_.StatusString -eq 'Incomplete VM Configuration'}
if($IncompleteVMConfig.count){
  foreach($vm in $IncompleteVMConfig){
    try{
      write-host "Fixing $($vm.Name)..."
      $vm|refresh-vm
      $dvd=Get-SCVirtualDVDDrive -VM $vm
      if ($null -ne $dvd.Connection -and $dvd.Connection -ne 'None') {
        Set-SCVirtualDVDDrive -VirtualDVDDrive $dvd -NoMedia
      }      
    }catch{
      write-warning $_
    }
  }
}

Errors related to this issue:

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
# version 0.02
# The following function assigns a guest VM into a 'cloud' in Virtual Machine Manager

#$vmNames=Get-Content ~\Desktop\computernames.txt
$vmsList=@'
TESTVM1
TESTVM2
'@
$cloudsList=@(
	'DEV'
	'QA'
	'Stage'
	'Prod'	
)
$rolesList=@('Dev Admins','QA Admins','Stage Admins','Prod Admins')
$vmmServer=$env:computername

function assignVMsToCloud{
	param(
		$vmNames='testVm',
		$cloudsList=@('Dev','QA','Stage','Prod'),
		$rolesList=@('Dev Admins','QA Admins','Stage Admins','Prod Admins'),
        $vmmServer='localhost'
	)

	function assignVmToCloud{
		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
			}else{
				if($vm.Cloud.Name -eq $cloudAssign){
					write-host "$vmName has already been assigned to Cloud $cloudAssign"
				}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
		}
	}

	function pickItem($list){
		do {
			try {
				$flag = $true
				clear-host
				for($i=0;$i -lt $list.count; $i++){
					write-host "$i`: $(($list[$i]|out-string))"
				}
				[int]$pick=Read-Host -Prompt "`n--------------------------------------------------------`nPlease type the number corresponding to the desired item`n--------------------------------------------------------"
			}catch{
				$flag = $false
			}
			}
		until ($pick -lt $list.count -and $pick -ge 0 -and $flag)
		$pickIndex=$pick
		$pickedItem=$list[$pickIndex]
		clear-host
		write-host "Selected item:`n--------------------------------------------------------`n$($pickedItem|out-string)"
		return $pickedItem
	}

	$cloudAssign=pickItem $cloudsList
	$roleName=pickItem $rolesList
	if($vmnames){
		$results=[hashtable]@{}
		foreach($vmname in $vmNames){
			$result=assignVmToCloud $vmName $cloudAssign $roleName $vmmServer
			$results+=@{$vmName=$result}
		}
	}else{
		write-warning "`$vmNames variable is empty - cannot proceed."
	}
	return $results
}

$vmNames=@($vmsList -split "`n" -replace "\..*$")
assignVMsToCloud $vmNames $cloudsList $rolesList $vmmServer
# assignVmsToCloud.ps1
# version 0.01
# 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’