Last active
May 2, 2023 09:12
-
-
Save RobinBeismann/66db1795769ed6ee1d8f07471d6f2e62 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
You running this script/function means you will not blame the author(s) if this breaks your stuff. | |
This script/function is provided AS IS without warranty of any kind. Author(s) disclaim all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. | |
The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. In no event shall author(s) be held liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the script or documentation. | |
Neither this script/function, nor any part of it other than those parts that are explicitly copied from others, may be republished without author(s) express written permission. Author(s) retain the right to alter this disclaimer at any time. | |
#> | |
#region Static Definitions | |
[array]$Clusters = @( | |
"HVCL01" | |
) | |
$regBasePath = "HKEY_LOCAL_MACHINE\SOFTWARE\Contoso\HyperV-VMMS-RCTWorkaround" | |
#endregion | |
#region Filters | |
$filter = @" | |
<QueryList> | |
<Query Id="0" Path="Microsoft-Windows-Hyper-V-VMMS-Admin"> | |
<Select Path="Microsoft-Windows-Hyper-V-VMMS-Admin">*[System[(EventID=19080) and TimeCreated[timediff(@SystemTime) <= 86400000]]]</Select> | |
</Query> | |
</QueryList> | |
"@ | |
#endregion | |
#region Cluster Processing | |
$Clusters | ForEach-Object { | |
try{ | |
$ClusterName = $_ | |
Write-Host("[$ClusterName] Processing cluster $_..") | |
$Cluster = Get-SCVMHostCluster -Name $_ -ErrorAction Stop | |
# Get current dynamic optimization setting | |
$DynamicOptimizationBackup = Get-SCDynamicOptimizationConfiguration -VMHostGroup $Cluster.HostGroup | |
$DynamicOptimization = Get-SCDynamicOptimizationConfiguration -VMHostGroup $Cluster.HostGroup | |
# Disable dynamic optimization | |
if($DynamicOptimizationBackup.Automatic -eq $true){ | |
Write-Host("[$ClusterName] Disabling Dynamic Optimization..") | |
$null = Set-SCDynamicOptimizationConfiguration -DynamicOptimizationConfiguration $DynamicOptimization -ManualMode | |
} | |
$Nodes = $Cluster.Nodes | |
$Nodes | ForEach-Object { | |
$Node = $_.Name | |
$NodeObject = $_ | |
$NodeJobs = @{} | |
Write-Host("[$Node] processing host..") | |
# Get Events | |
if($events = Get-WinEvent -FilterXml $filter -ComputerName $Node -ErrorAction SilentlyContinue){ | |
$groupedEvents = $events | Group-Object -Property { $_.Properties[0].Value } | |
$actualEvents = $groupedEvents | ForEach-Object { | |
$_.Group | Sort-Object -Property $_.TimeCreated -Descending | Select-Object -First 1 | |
} | |
# Process Events | |
$actualEvents | ForEach-Object { | |
if($VMName = $_.Properties[0].Value){ | |
$channelPath = "Registry::$($regBasePath)\$($Node)_$($VMName)" | |
# Check if the VM exists on our host | |
if(Hyper-V\Get-VM -Name $VMName -ErrorAction SilentlyContinue -ComputerName $Node){ | |
Write-Verbose("[$Node - $VMName] Finished a snapshot removal operation and VM is still on this host.") | |
# Create registry VM Path if required | |
if(!(Test-Path -Path $channelPath -ErrorAction SilentlyContinue)){ | |
$null = New-Item -Path $channelPath -Force | |
} | |
[string]$currentRecordID = $_.RecordID | |
$Items = Get-ItemProperty -Path $channelPath | |
if( | |
# Check if we already have records saved for this VM | |
!([array]$recordIDs = $items.PSObject.Members | Where-Object { $_.MemberType -eq 'NoteProperty' -and $_.Name -notlike 'PS*' } | Select-Object -ExpandProperty 'Name') -or | |
# And if this Eventlog Record is already contained (which would mean that we migrated this VM already and don't need to) | |
!($currentRecordID -in $recordIDs) | |
){ | |
# Looks like we need to take action on this one | |
Write-Host("[$Node - $VMName] VM was not yet migrated, migrating..") | |
$SCVM = Get-SCVirtualMachine -Name $VMName | |
#Get best suited host | |
Write-Verbose("[$Node - $VMName] Getting best suited Host.") | |
$vmHostRating = Get-SCVMHostRating –VM $SCVM -VMHostGroup $NodeObject.VMHostGroup | Where-Object { $_.Name -ne $Node -and $NodeObject.HostCluster.Name -eq $_.VMHost.HostCluster.Name } | Sort-Object -Descending Rating | |
$vmHost = Get-SCVMHost –ComputerName $vmHostRating[0].Name | |
Write-Verbose("[$Node - $VMName] Best suited host is: " + $vmHostRating[0].Name) | |
#Move VM to the best suited host | |
$CurrentHost = (Get-SCVirtualMachine -Name $VMName).VMHost | |
if($CurrentHost -ne $vmHost){ | |
Write-Verbose("[$Node - $VMName] Current Host is " + $CurrentHost.Name) | |
Write-Host("[$Node - $VMName] Moving VM to: " + $vmHostRating[0].Name) | |
try{ | |
# Move the VM to the host | |
$job = Move-SCVirtualMachine -VM $VMName -VMHost $vmHost -ErrorAction Stop -RunAsynchronously -JobVariable "JobVar" | |
$NodeJobs.$VMName = @{ | |
RegPath = $channelPath | |
RecordId = $_.RecordId | |
Job = $JobVar | |
} | |
}catch{ | |
Write-Host("[$Node - $VMName] Failed moving VM, won't save state! Error: $_") | |
if($_.ErrorDetails.Message -like "*Repair virtual machine*"){ | |
try{ | |
Write-Host("[$Node - $VMName] VM seems to be in repairable state, trying to repair..") | |
# Repair VM | |
Repair-SCVirtualMachine -VM $SCVM -Dismiss -ErrorAction Stop | |
# Move the VM to the host | |
$job = Move-SCVirtualMachine -VM $VMName -VMHost $vmHost -ErrorAction Stop -RunAsynchronously -JobVariable "JobVar" | |
$NodeJobs.$VMName = @{ | |
RegPath = $channelPath | |
RecordId = $_.RecordId | |
Job = $JobVar | |
} | |
}catch{ | |
Write-Host("[$Node - $VMName] Failed moving VM for a 2nd time, won't save state! Error: $_") | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
$NodeJobs.GetEnumerator() | ForEach-Object { | |
$VMName = $_.Name | |
$Job = $_.Value.Job | |
while( | |
($JobStatus = Get-SCJob -ID $Job.ID) -and | |
($JobStatus.Status -eq "Running") | |
){ | |
Write-Host("[$Node - $VMName] Still under migration, waiting..") | |
Start-Sleep -Seconds 3 | |
} | |
if($JobStatus.Status -eq "Completed"){ | |
# Get the VM in Failover Cluster | |
$null = Set-ItemProperty -Path $_.Value.RegPath -Name $_.Value.RecordId -Value ([string](Get-Date -Format "yyyyMMddHHmmss")) | |
} | |
} | |
Write-Host("[$Node] Finished processing host.") | |
} | |
}catch{ | |
Write-Host("[$ClusterName] Error processing cluster! Error: $_") | |
}finally{ | |
Write-Host("[$ClusterName] Finished processing cluster $_.") | |
if($DynamicOptimizationBackup.Automatic -eq $true){ | |
Write-Host("[$ClusterName] Activating Dynamic Optimization again..") | |
Set-SCDynamicOptimizationConfiguration -DynamicOptimizationConfiguration $DynamicOptimizationBackup -AutomaticMode | |
} | |
} | |
} | |
#endregion |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment