# fixAllVmCpuCompatibility.ps1
# version 0.01
function getHyperVHostsInForest{
function includeRSAT{
$ErrorActionPreference='stop'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
#$rsatWindows7x32='https://download.microsoft.com/download/4/F/7/4F71806A-1C56-4EF2-9B4F-9870C4CFD2EE/Windows6.1-KB958830-x86-RefreshPkg.msu'
$rsatWindows7x64='https://download.microsoft.com/download/4/F/7/4F71806A-1C56-4EF2-9B4F-9870C4CFD2EE/Windows6.1-KB958830-x64-RefreshPkg.msu'
$rsatWindows81='https://download.microsoft.com/download/1/8/E/18EA4843-C596-4542-9236-DE46F780806E/Windows8.1-KB2693643-x64.msu'
$rsat1709 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS_1709-x64.msu"
$rsat1803 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS_1803-x64.msu"
$rsatWs2016 = "https://download.microsoft.com/download/1/D/8/1D8B5022-5477-4B9A-8104-6A71FF9D98AB/WindowsTH-RSAT_WS2016-x64.msu"
# This command does not work on Windows 2012R2
#$releaseId=(Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ReleaseId).ReleaseId
#Get-ItemProperty : Property ReleaseId does not exist at path HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows
#NT\CurrentVersion.
#At line:1 char:2
#+ (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Na ...
#+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# + CategoryInfo : InvalidArgument: (ReleaseId:String) [Get-ItemProperty], PSArgumentException
# + FullyQualifiedErrorId : System.Management.Automation.PSArgumentException,Microsoft.PowerShell.Commands.GetItemPropertyCommand
$releaseId=(Get-Item "HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion").GetValue('ReleaseID')
$osVersion=[System.Environment]::OSVersion.Version
[double]$osVersionMajorMinor="$($osVersion.Major).$($osVersion.Minor)"
$osName=(Get-WmiObject Win32_OperatingSystem).Name
#$osType=switch ((Get-CimInstance -ClassName Win32_OperatingSystem).ProductType){
# 1 {'client'}
# 2 {'domaincontroller'}
# 3 {'memberserver'}
# }
$windowsVersion=(Get-CimInstance Win32_OperatingSystem).Version
switch ($releaseId){
1607{write-host 'Windows Server 2016 Release 1607 detected';$link=$rsatWs2016;break}
1709{write-host 'Windows Server 2016 Release 1709 detected';$link=$rsat1709;break}
1803{write-host 'Windows Server 2016 Release 1803 detected';$link=$rsat1803}
}
switch ($osVersionMajorMinor){
{$_ -eq 6.0}{write-host 'Windows Server 2008 or Windows Vista detected';$link=$rsat1709;break}
{$_ -eq 6.1}{write-host 'Windows Server 2008 R2 or Windows 7 detected';$link=$rsatWindows7x64;break}
{$_ -eq 6.2}{write-host 'Windows Server 2012 or Windows 8.1 detected';$link=$rsatWindows81;break}
{$_ -eq 6.3}{write-host 'Windows Server 2012 R2 detected';$link=$rsatWindows81}
}
if (!(Get-Module -ListAvailable -Name ActiveDirectory -EA SilentlyContinue)){
Write-host "Prerequisite checks: module ActiveDirectory NOT currently available on this system. Please wait while the program adds that plugin..."
try{
# If OS is Windows Server, then install RSAT using a different method
if ($osName -match "^Microsoft Windows Server") {
# This sequence has confirmed to be valid on Windows Server 2008 R2 and above
Write-Verbose "Importing Windows Feature: RSAT-AD-PowerShell"
Import-Module ServerManager
Add-WindowsFeature RSAT-AD-PowerShell
}
else{
Write-Verbose "This sequence targets Windows Client versions"
$destinationFile= ($ENV:USERPROFILE) + "\Downloads\" + (split-path $link -leaf)
Write-Host "Downloading RSAT from $link..."
Start-BitsTransfer -Source $link -Destination $destinationFile
$fileCheck=Get-AuthenticodeSignature $destinationFile
if($fileCheck.status -ne "valid") {write-host "$destinationFile is not valid. Please try again...";break}
$wusaCommand = $destinationFile + " /quiet"
Write-host "Installing RSAT - please wait..."
Start-Process -FilePath "C:\Windows\System32\wusa.exe" -ArgumentList $wusaCommand -Wait
}
return $true
}
catch{
write-warning "$($error[0].Exception)"
return $false
}
}else{
Write-host "Prerequisite checks: module ActiveDirectory IS currently available on this system." -ForegroundColor Green
return $true
}
}
function listAllHyperVNodes($verbose=$true){
try{
$timer=[System.Diagnostics.Stopwatch]::StartNew()
$domains=(Get-ADForest).Name|%{(Get-ADForest -Identity $_).Name}
foreach ($domain in $domains){
#[string]$dc=(get-addomaincontroller -DomainName "$domain" -Discover -NextClosestSite).HostName
write-host "Collecting all Hyper-V Clusters in $domain. This may take a while, depending on cluster sizes."
$allClusters=(get-cluster -domain $domain).Name
if($verbose){
$elapsed=[math]::round($timer.Elapsed.TotalMinutes,2)
write-host "Minutes elapsed $elapsed`: cluster names collected"
}
$allHyperVNodes=@()
foreach ($cluster in $allClusters){
$nodes=.{$x=Get-ClusterNode -Cluster $cluster -ea SilentlyContinue
if($x){
$x|Where-Object{$_.State -eq 'Up'}|Select-Object Name,@{name='Cluster';e={$cluster}}
}else{
$false
}
}
if($nodes){$allHyperVNodes+=$nodes}
}
if($verbose){
$elapsed=[math]::round($timer.Elapsed.TotalMinutes,2)
write-host "Minutes elapsed $elapsed`: Hyper Node names collected..."
}
}
return $allHyperVNodes
}catch{
Write-Error $_
return $false
}
}
try{
$null=includeRSAT;
$hyperVHosts=listAllHyperVNodes
#$hyperVHostNames=sortArrayStringAsNumbers $hyperVHosts
$hyperVHostNames=$hyperVHosts|sort -property Cluster
return $hyperVHostNames
}catch{
Write-Error $_
return $false
}
}
function pickList($list){
# Although it's more efficient to obtain the index and set it as display,
# humans prefer see a list that starts with 1, instead of 0
$display=for ($i=0;$i -lt $list.count;$i++){
"$($i+1)`:`t$($list[$i])`r`n";
}
$lines=($display | Measure-Object -Line).Lines
write-host $($display)
$maxAttempts=3
$attempts=0;
while ($attempts -le $maxAttempts){
if($attempts++ -ge $maxAttempts){
write-host "Attempt number $maxAttempts of $maxAttempts. Exiting loop..`r`n"
break;
}
$userInput = Read-Host -Prompt 'Please pick a number from the list above';
try {
$value=[int]$userInput;
}catch{
$value=-1;
}
if ($value -lt 1 -OR $value -gt $lines){
cls;
write-host "Attempt number $attempts of $maxAttempts`: $userInput is an invalid value. Try again..`r`n"
write-host $display
}else{
$item=$list[$value-1];
write-host "$userInput corresponds to $item`r`n";
return $item
}
}
}
function selectCluster($clusters){
# Requires function named pickList
$uniqueClusters=$clusters|select -unique
$pickedCluster=$(pickList $uniqueClusters)
if($pickedCluster){
return $pickedCluster
}else{
write-warning 'No clusternames were picked.'
return $false
}
}
function pickHost($hosts){
# Requires function named pickList
$pickedHost=$(pickList $hosts)
if($pickedHost){
return $pickedHost
}else{
write-warning 'No clusternames were picked.'
return $false
}
}
function sortArrayStringAsNumbers([string[]]$names){
$hashTable=@{}
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])}
$x=.{[void]($name -match '(?:.(\d+)+)$');($name.substring(0,$name.length-$matches[1].length))+$matches[1].PadLeft(8,'0')}
$hashTable.Add($name,$x)
}
$sorted=foreach($item in $hashTable.GetEnumerator() | Sort Value){$item.Name}
return $sorted
}
function enableCpuCompatibility($vmName){
$compatibilityForMigration=(Get-VMProcessor $vmName).CompatibilityForMigrationEnabled
if(!$compatibilityForMigration){
$vmIsRunning=(get-vm $vmname).State -eq 'Running'
if($vmIsRunning){stop-vm $vmName}
Set-VMProcessor $vmName -CompatibilityForMigrationEnabled 1
if($vmIsRunning){start-vm $vmName}
}else{
write-host "$vmName already has CPU CompatibilityForMigrationEnabled set to True"
}
}
write-host "Obtaining cluster names and associated hosts..."
$hyperVHostsInForest=getHyperVHostsInForest
$pickedCluster=selectCluster $hyperVHostsInForest.Cluster
$pickedHosts=$hyperVHostsInForest|?{$_.Cluster -eq $pickedCluster}
$pickedHyperVHosts=sortArrayStringAsNumbers $pickedHosts.Name
$results=@()
foreach ($hyperVHost in $pickedHyperVHosts){
write-host "Performing discovery on $hyperVhost..."
$getVMCpuCompatiblity={
Get-VMProcessor *|Select-Object VMName,CompatibilityForMigrationEnabled,@{name='Online';e={if((get-vm $_.VMName).State -eq 'Running'){$true}else{$false}}}
}
$result=invoke-command -ComputerName $hyperVHost -ScriptBlock $getVMCpuCompatiblity|Select-Object -Property * -ExcludeProperty RunspaceId
write-host ($result|out-string).trim()
$results+=$result
}
$negatives=$results|?{$_.CompatibilityForMigrationEnabled -eq $false -and $_.Online -eq $true}
# Fix all items
foreach ($item in $negatives){
$vmHost=$item.PSComputerName
$vmName=$item.VMName
invoke-command -computername $vmHost -scriptblock {
param($enableCpuCompatibility,$vmName)
[scriptblock]::create($enableCpuCompatibility).invoke($vmName)
} -Args ${function:enableCpuCompatibility},$vmName
}
# Fix each item
$vmName='TESTVM008'
$vmHost=($negatives|?{$_.VMName -eq $vmName}).PSComputerName
if($vmHost){
invoke-command -computername $vmHost -scriptblock {
param($vmName)$vmIsRunning=(get-vm $vmname).State -eq 'Running'
if($vmIsRunning){stop-vm $vmName -Force}
Set-VMProcessor $vmName -CompatibilityForMigrationEnabled 1
if($vmIsRunning){start-vm $vmName}
} -Args $vmName
}else{
write-host "VMName $vmName doesn't match an existing record."
}
Categories: