# 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