Skip to content

Instantly share code, notes, and snippets.

@RobinBeismann
Last active May 2, 2023 09:12
Show Gist options
  • Save RobinBeismann/66db1795769ed6ee1d8f07471d6f2e62 to your computer and use it in GitHub Desktop.
Save RobinBeismann/66db1795769ed6ee1d8f07471d6f2e62 to your computer and use it in GitHub Desktop.
<#
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) &lt;= 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