Public/Set-Win32ToolkitTestVMResource.ps1
|
function Set-Win32ToolkitTestVMResource { <# .SYNOPSIS Changes the CPU count and/or startup memory of the Hyper-V test VM and re-freezes its clean-base checkpoint. .DESCRIPTION Reconfigures an EXISTING test VM's hardware in place — it does NOT rebuild from ISO, so the installed golden guest and its disk are preserved (minutes, not the ~hour a full re-provision costs). Why this is more than a one-liner: the VM uses a STANDARD (memory-state) 'clean-base' checkpoint. Changing static memory / vCPU count requires the VM to be powered OFF, and the existing checkpoint encodes the OLD memory state — so a change would be silently reverted by the next Reset-Win32ToolkitTestVM unless the checkpoint is recreated. This command therefore: Stop-VM -TurnOff -> remove checkpoints -> Set-VMProcessor / Set-VMMemory -> Start-VM -> wait for the AutoLogon desktop -> re-take the Standard 'clean-base' checkpoint -> persist The chosen values are saved (HyperVProcessorCount / HyperVMemoryStartupBytes) and become the defaults for the next New-Win32ToolkitTestVM. Requested CPU/RAM above the host's capacity is REFUSED. .PARAMETER ProcessorCount New virtual processor count (1-64). Omit to leave CPU unchanged. .PARAMETER MemoryStartupBytes New static startup memory in bytes (accepts PowerShell size literals, e.g. 6GB). Omit to leave RAM unchanged. .PARAMETER Name VM name. Defaults to the configured HyperVVMName ('win32tk-golden'). .PARAMETER CheckpointName Clean-base checkpoint name to recreate. Defaults to the configured HyperVCheckpoint ('clean-base'). .PARAMETER Credential Guest admin credential (to confirm the desktop is up before the warm checkpoint). Defaults to the stored DPAPI-protected guest credential. .EXAMPLE Set-Win32ToolkitTestVMResource -ProcessorCount 4 -MemoryStartupBytes 8GB .EXAMPLE Set-Win32ToolkitTestVMResource -MemoryStartupBytes 6GB # RAM only; CPU unchanged .OUTPUTS The reconfigured VM (Get-VM), or nothing under -WhatIf. #> [CmdletBinding(SupportsShouldProcess)] [OutputType('Microsoft.HyperV.PowerShell.VirtualMachine')] param( [ValidateRange(1, 64)] [int]$ProcessorCount, [uint64]$MemoryStartupBytes, [string]$Name, [string]$CheckpointName, [pscredential]$Credential ) # At least one thing to change. $changeCpu = $PSBoundParameters.ContainsKey('ProcessorCount') $changeMem = $PSBoundParameters.ContainsKey('MemoryStartupBytes') if (-not ($changeCpu -or $changeMem)) { throw 'Nothing to change: pass -ProcessorCount and/or -MemoryStartupBytes.' } if (-not $Name) { $Name = Get-Win32ToolkitConfigValue -Name 'HyperVVMName' -Default 'win32tk-golden' } if (-not $CheckpointName) { $CheckpointName = Get-Win32ToolkitConfigValue -Name 'HyperVCheckpoint' -Default 'clean-base' } # The VM must exist — this reconfigures an existing one, it does not create. $vm = Get-VM -Name $Name -ErrorAction SilentlyContinue if (-not $vm) { throw "Test VM '$Name' does not exist. Provision it first (New-Win32ToolkitTestVM / the TUI's Hyper-V test VM screen)." } # ── Hard-block above host capacity ───────────────────────────────────────────── # A guest cannot be given more vCPU than the host has logical processors, and giving it (nearly) all the # host's RAM will hang the host — so cap RAM at host physical RAM minus a reserve for the host OS. $vmHost = Get-VMHost $hostCpu = [int]$vmHost.LogicalProcessorCount $hostRam = [uint64]$vmHost.MemoryCapacity $hostReserve = [uint64]2GB $minRam = [uint64]2GB # Windows 11 needs ~2 GB if ($changeCpu) { if ($ProcessorCount -gt $hostCpu) { throw "Requested $ProcessorCount vCPU exceeds the host's $hostCpu logical processors." } } if ($changeMem) { if ($MemoryStartupBytes -lt $minRam) { throw "Requested $([math]::Round($MemoryStartupBytes / 1GB, 2)) GB is below the $([math]::Round($minRam / 1GB, 1)) GB minimum (Windows 11 needs it)." } $maxRam = if ($hostRam -gt $hostReserve) { $hostRam - $hostReserve } else { [uint64]0 } if ($MemoryStartupBytes -gt $maxRam) { throw ("Requested {0} GB exceeds usable host RAM: the host has {1} GB, and a {2} GB reserve is kept for the host OS (max {3} GB)." -f ` [math]::Round($MemoryStartupBytes / 1GB, 2), [math]::Round($hostRam / 1GB, 1), [math]::Round($hostReserve / 1GB, 1), [math]::Round($maxRam / 1GB, 2)) } } # Current specs (for the summary + to fill in the value we are NOT changing). $curCpu = [int](Get-VMProcessor -VMName $Name).Count $curMem = [uint64](Get-VMMemory -VMName $Name).Startup $newCpu = if ($changeCpu) { $ProcessorCount } else { $curCpu } $newMem = if ($changeMem) { $MemoryStartupBytes } else { $curMem } if ($newCpu -eq $curCpu -and $newMem -eq $curMem) { Write-Host "VM '$Name' already has $curCpu vCPU / $([math]::Round($curMem / 1GB, 2)) GB — nothing to do." -ForegroundColor Yellow return $vm } $action = "Reconfigure to $newCpu vCPU / $([math]::Round($newMem / 1GB, 2)) GB (currently $curCpu vCPU / $([math]::Round($curMem / 1GB, 2)) GB) — this turns the VM OFF, recreates the '$CheckpointName' checkpoint, and cold-boots it" if (-not $PSCmdlet.ShouldProcess($Name, $action)) { return } # Need the guest credential to confirm the desktop is up before re-freezing. if (-not $Credential) { $Credential = Get-Win32ToolkitGuestCredential if (-not $Credential) { throw "No guest credential is stored — cannot verify the VM desktop before re-checkpointing. Re-run New-Win32ToolkitTestVM to (re)store it, or pass -Credential." } } Write-Host "Reconfiguring '$Name': $curCpu vCPU / $([math]::Round($curMem / 1GB, 2)) GB -> $newCpu vCPU / $([math]::Round($newMem / 1GB, 2)) GB" -ForegroundColor Cyan # Invalidate the process-local clean marker / readiness cache — the checkpoint is about to be # destroyed and recreated, so nothing cached about the VM's state may survive this. Clear-Win32ToolkitHyperVStateCache # 1. Off. Static memory / vCPU count cannot change on a running VM. CONFIRM it actually powered off before # anything destructive: if Stop-VM silently failed and we removed the checkpoint anyway, we'd destroy the # only recovery point for a change that then can't apply (VM stuck running, OLD hardware, NO checkpoint). Write-Verbose "Stopping '$Name'..." Stop-VM -Name $Name -TurnOff -Force -ErrorAction SilentlyContinue if ((Get-VM -Name $Name -ErrorAction SilentlyContinue).State -ne 'Off') { throw "Could not power off VM '$Name' — refusing to remove its clean-base checkpoint. Stop it manually (Stop-VM -Name '$Name' -TurnOff) and retry." } # 2. Remove ALL checkpoints — a Standard checkpoint encodes the OLD memory state, so keeping it would let # the next Reset revert the change (and can block the memory reconfigure). Get-VMCheckpoint -VMName $Name -ErrorAction SilentlyContinue | Remove-VMCheckpoint -ErrorAction SilentlyContinue # 3. Apply the new hardware (static memory, matching provisioning). if ($changeCpu) { Set-VMProcessor -VMName $Name -Count $newCpu } if ($changeMem) { Set-VMMemory -VMName $Name -DynamicMemoryEnabled $false -StartupBytes $newMem } # 4. Cold-boot and wait for the AutoLogon desktop, so the warm checkpoint captures a real logged-in shell. Write-Verbose "Starting '$Name' and waiting for the guest to be ready..." Start-VM -Name $Name -ErrorAction Stop Wait-Win32ToolkitVMReady -VMName $Name -Credential $Credential | Out-Null Write-Verbose 'Waiting for the guest desktop to settle (explorer)...' $deadline = (Get-Date).AddMinutes(10) $shellUp = $false do { Start-Sleep -Seconds 10 $shellUp = [bool](Invoke-Command -VMName $Name -Credential $Credential -ScriptBlock { [bool](Get-Process -Name explorer -ErrorAction SilentlyContinue) } -ErrorAction SilentlyContinue) } until ($shellUp -or (Get-Date) -gt $deadline) if (-not $shellUp) { throw "The guest desktop (explorer) did not come up within 10 minutes after the reconfigure — refusing to freeze a broken state. The VM '$Name' is running with the new hardware but has NO clean-base checkpoint; check AutoLogon / the console, then re-run." } # 5. Re-freeze the clean base at the new hardware level. Set-VM -Name $Name -CheckpointType Standard Checkpoint-VM -VMName $Name -SnapshotName $CheckpointName Write-Host "✓ '$CheckpointName' checkpoint recreated at $newCpu vCPU / $([math]::Round($newMem / 1GB, 2)) GB." -ForegroundColor Green # 6. Persist the new specs (and re-affirm the VM identity) so New-Win32ToolkitTestVM reuses them. Set-Win32ToolkitConfigValue -Name 'HyperVVMName' -Value $Name Set-Win32ToolkitConfigValue -Name 'HyperVCheckpoint' -Value $CheckpointName Set-Win32ToolkitConfigValue -Name 'HyperVProcessorCount' -Value $newCpu Set-Win32ToolkitConfigValue -Name 'HyperVMemoryStartupBytes' -Value $newMem return (Get-VM -Name $Name -ErrorAction SilentlyContinue) } |