Key takeaways
- Dynamic Memory becomes Static Memory. The module reads each VM's Startup Memory, turns Dynamic Memory off and sets that value as the fixed size.
- Only running VMs are restarted. A VM that was powered off stays off, and a VM that already uses static memory is never shut down.
- Every change is read back. If VMM does not report the value you asked for, the module stops before the restart and leaves the VM for you to check.
- Safe to rehearse.
-WhatIfprints the planned action for every VM, and each VM returns a result object you can export.
Why a post-migration module?
After a VMware to Hyper-V migration, every VM needs the same clean-up. Memory should be a fixed size, and the VMM details (Description, Cost Center, Cloud and Tag) should be filled in. Doing that in the VMM console one VM at a time is slow, and every Dynamic Memory VM needs a shutdown first.
This module does it for a list of VMs in one command. You pass the values once, confirm each VM, and it handles the shutdown, the change, the checks and the restart.
The flow at a glance
Each VM follows one of two paths, chosen by whether Dynamic Memory is on.
Both paths end with the same read-back check. Only the Dynamic Memory path touches the VM's power state.
The logic, step by step
The command is Set-VMMStaticMemory. It is an advanced function with begin, process and end blocks, and that split explains most of how it behaves.
1Connect and validate once
Before any VM is touched, begin connects to VMM, trims your values and checks the Cloud name against VMM. A Cloud that does not exist, or exists twice, stops the command with an error that lists the available Clouds and says no VMs were modified.
2Collect the names
-VMName accepts an array, a comma-separated string, or names piped in. process only collects them, trimmed and de-duplicated, so nothing runs until the whole list has arrived. Objects with a Name property, such as VMs from Get-SCVirtualMachine, bind too.
3Handle one VM at a time
end loops over the names inside a labelled loop. Each name is looked up by exact name, ignoring case. A missing VM, or two VMs with the same name, produces a result with the reason and the loop moves on, so one bad VM never stops the batch.
:VMLoop foreach ($Name in $names) {
# ... find the VM, check it, update it ...
[pscustomobject]$result; continue VMLoop
}
4Pick the path from Dynamic Memory
Two facts are recorded before anything changes: whether Dynamic Memory is on, and whether the VM is running. With Dynamic Memory on, the VM's Memory value is its Startup Memory and becomes the static size. If it is missing or zero, the VM is skipped before any shutdown. A VM that already uses static memory skips the memory work, and with no properties requested it just reports that no changes were needed.
$MemoryUpdateRequired = [bool]$VM.DynamicMemoryEnabled
$WasRunning = $VM.StatusString -notmatch 'PowerOff|Stopped'
$StaticMemoryMB = $null
if ($null -ne $VM.Memory -and [int64]$VM.Memory -gt 0) {
$StaticMemoryMB = [int]$VM.Memory
}
5Ask before changing anything
The function declares SupportsShouldProcess with ConfirmImpact = 'High', so PowerShell asks before each VM unless you pass -Confirm:$false. The prompt names the exact action, such as "Shut down, convert to Static Memory (4096 MB), update properties, restart". With -WhatIf the same line is printed and the VM is skipped.
if (-not $PSCmdlet.ShouldProcess($VM.Name, $action)) {
$result.Message = 'Skipped (not confirmed / WhatIf)'
[pscustomobject]$result; continue VMLoop
}
6Shut down only when needed
Only a VM that needs the memory change is shut down. The module asks for a graceful shutdown, then polls every 10 seconds for up to 5 minutes. If the VM is still running it asks whether to force a power-off, or goes straight to it when you passed -ForcePowerOff. A forced power-off gets another 5 minutes. Before memory changes, the VM is re-read to confirm it really is off.
7Change only what you asked for
The update is built as a hashtable and splatted into Set-SCVirtualMachine. Memory settings go in only for the conversion, and Description, Cost Center, Tag and Cloud only when you supplied a value, so leaving one out never blanks what is already there.
$params = @{ VM = $VM; ErrorAction = 'Stop' }
if ($MemoryUpdateRequired) {
$params['DynamicMemoryEnabled'] = $false
$params['MemoryMB'] = $StaticMemoryMB
}
if ($UpdateDescription) { $params['Description'] = $Description }
if ($UpdateCostCenter) { $params['CostCenter'] = $CostCenter }
if ($UpdateTag) { $params['Tag'] = $Tag }
if ($UpdateCloud) { $params['Cloud'] = $NewCloud }
Set-SCVirtualMachine @params | Out-Null
8Read back, restart, report
Afterwards the VM is read back from VMM and every changed value is compared. Every mismatch is listed, and the VM stops there, still powered off if it was converted. If all is well, it is restarted only when memory was converted and it was running before, and the module waits up to 5 minutes for the Running status. Whatever the outcome, each VM ends with a result object.
$result = [ordered]@{
VMName = $Name; Success = $false; MemoryConverted = $false
GeneralUpdated = $false; Status = $null; Message = $null
}
Design decisions
| Decision | Why |
|---|---|
| Startup Memory is the static size | The VM boots with the memory it was configured to start with. |
| Parameters, not prompts | It works in scripts and pipelines, and -WhatIf and -Confirm come from PowerShell itself. |
| Parameters built from your input | Empty values never overwrite existing ones. |
| Read back, then restart | A VM never comes back up half configured. |
| A result object per VM | Failures are data you can export, not just text on screen. |
Install and use it
You need PowerShell 5.1 or later, the VMM PowerShell module (VirtualMachineManager) on the machine you run it from, and an account that can modify, stop and start VMs in VMM. Save the file, import it, and pass your VMM server with -VMMServerName.
# 1. Import the module
Import-Module .\VMMDynamicMemory.psm1
# 2. Preview first. Nothing is changed
Set-VMMStaticMemory -VMName "app-web-01","app-web-02" -CostCenter "CC1234" -Tag "Prod" `
-CloudName "Cloud-A" -VMMServerName "vmm01.example.com" -WhatIf
# 3. Run it. You confirm each VM
Set-VMMStaticMemory -VMName "app-web-01","app-web-02" -CostCenter "CC1234" -Tag "Prod" `
-CloudName "Cloud-A" -VMMServerName "vmm01.example.com"
# Unattended: no confirmations, and force a power-off if a VM will not shut down
"app-web-01","app-web-02" | Set-VMMStaticMemory -Description "Migrated" `
-VMMServerName "vmm01.example.com" -Confirm:$false -ForcePowerOff |
Export-Csv .\results.csv -NoTypeInformation
Rehearse first. Run it with -WhatIf, then against a non-production VM, before you use it on a batch.
Things to know
- Startup Memory is the static size. If a VM was configured with a large Startup Memory, it keeps that much. Review sizing afterwards.
- Any status other than Power Off or Stopped counts as running. Check VMs in unusual states, such as Saved or Paused, before you include them.
- Unattended runs need
-ForcePowerOff.-Confirm:$falsedoes not silence the separate question about forcing a power-off. - A failed read-back leaves two flags at False. The result shows
MemoryConvertedandGeneralUpdatedas False even if part of the change was applied. ReadMessageand check the VM. - It only changes memory and four VMM fields. It never moves VM files or touches networking.
The module
407 lines. Copy it, download it, or get it from GitHub, then save it as VMMDynamicMemory.psm1.
#Requires -Version 5.1
# ============================================================
# VMMDynamicMemory module
# Converts SCVMM VMs from Dynamic to Static Memory (using the
# Startup Memory value) and updates Description, Cost Center,
# Cloud and Tag.
#
# Based on script v1.5. Module version 1.5.0
# Author: ravi@automatewithravi.com
# ============================================================
function Get-VMMRefreshedVM {
[CmdletBinding()]
param($VM, $Server)
Get-SCVirtualMachine -ID $VM.ID -VMMServer $Server -ErrorAction Stop
}
function Wait-VMMVMState {
# Polls the VM until it reaches the wanted state or times out.
[CmdletBinding()]
param(
$VM,
$Server,
[ValidateSet('Off', 'Running')][string]$State,
[int]$TimeoutSeconds = 300,
[int]$IntervalSeconds = 10
)
$pattern = if ($State -eq 'Off') { 'PowerOff|Stopped' } else { 'Running' }
$elapsed = 0
while ($elapsed -lt $TimeoutSeconds) {
Start-Sleep -Seconds $IntervalSeconds
$elapsed += $IntervalSeconds
$VM = Get-VMMRefreshedVM -VM $VM -Server $Server
Write-Host "Waiting for $State : $elapsed of $TimeoutSeconds seconds. Status: $($VM.StatusString)"
if ($VM.StatusString -match $pattern) {
return [pscustomobject]@{ Reached = $true; VM = $VM }
}
}
[pscustomobject]@{ Reached = $false; VM = $VM }
}
function Set-VMMStaticMemory {
<#
.SYNOPSIS
Converts Dynamic Memory to Static Memory on SCVMM VMs and updates General properties.
.DESCRIPTION
For each VM:
- If Dynamic Memory is enabled: captures Startup Memory, gracefully shuts the VM down
(if running), disables Dynamic Memory, sets Static Memory = Startup Memory,
updates the requested properties, then restarts the VM only if it was running.
- If Dynamic Memory is already disabled: memory is untouched, the VM is not shut down
or restarted, and only the requested properties are updated.
Properties left out are not changed.
Supports -WhatIf and -Confirm. The default prompts per VM; use -Confirm:$false to skip.
.PARAMETER VMName
One or more exact VM names (array, comma-separated, or via pipeline).
.PARAMETER Description
New Description for every VM.
.PARAMETER CostCenter
New Cost Center for every VM.
.PARAMETER CloudName
Name of an existing SCVMM Cloud to assign to every VM.
.PARAMETER Tag
New Tag for every VM.
.PARAMETER VMMServerName
SCVMM server. Defaults to vmm01.example.com. Pass your own, or change the default.
.PARAMETER ForcePowerOff
Force power off without asking if graceful shutdown times out.
.EXAMPLE
Set-VMMStaticMemory -VMName "VM01","VM02" -CostCenter "CC1234" -Tag "Prod" -Cloud "Cloud-A"
.EXAMPLE
Set-VMMStaticMemory -VMName VM01 -WhatIf
.EXAMPLE
"VM01","VM02" | Set-VMMStaticMemory -Description "Migrated" -Confirm:$false
#>
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
param(
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true)]
[Alias('Name')]
[string[]]$VMName,
[string]$Description,
[string]$CostCenter,
[Alias('Cloud')][string]$CloudName,
[string]$Tag,
[string]$VMMServerName = 'vmm01.example.com',
[switch]$ForcePowerOff
)
begin {
$names = New-Object System.Collections.Generic.List[string]
Write-Host "Connecting to SCVMM server $VMMServerName..." -ForegroundColor Cyan
try {
$VMMServer = Get-SCVMMServer -ComputerName $VMMServerName -ErrorAction Stop
Write-Host "Connected to SCVMM successfully." -ForegroundColor Green
}
catch {
throw "Unable to connect to SCVMM server $VMMServerName. $($_.Exception.Message)"
}
# Trim entered values
if ($Description) { $Description = $Description.Trim() }
if ($CostCenter) { $CostCenter = $CostCenter.Trim() }
if ($CloudName) { $CloudName = $CloudName.Trim() }
if ($Tag) { $Tag = $Tag.Trim() }
$UpdateDescription = -not [string]::IsNullOrWhiteSpace($Description)
$UpdateCostCenter = -not [string]::IsNullOrWhiteSpace($CostCenter)
$UpdateTag = -not [string]::IsNullOrWhiteSpace($Tag)
$UpdateCloud = $false
$NewCloud = $null
# Validate the Cloud once
if (-not [string]::IsNullOrWhiteSpace($CloudName)) {
$clouds = @(Get-SCCloud -VMMServer $VMMServer -ErrorAction Stop |
Where-Object { $_.Name -ieq $CloudName })
if ($clouds.Count -eq 0) {
$available = (Get-SCCloud -VMMServer $VMMServer | Sort-Object Name |
ForEach-Object { $_.Name }) -join ', '
throw "Cloud '$CloudName' was not found. Available clouds: $available. No VMs were modified."
}
if ($clouds.Count -gt 1) {
throw "More than one Cloud named '$CloudName' was found. Operation cancelled."
}
$NewCloud = $clouds[0]
$UpdateCloud = $true
Write-Host "Validated Cloud: $($NewCloud.Name)" -ForegroundColor Green
}
}
process {
# Collect names from the pipeline / parameter (supports commas inside a string too)
foreach ($entry in $VMName) {
foreach ($n in ($entry -split ',')) {
$t = $n.Trim()
if ($t -and -not $names.Contains($t)) { $names.Add($t) }
}
}
}
end {
$GeneralUpdateRequired = $UpdateDescription -or $UpdateCostCenter -or $UpdateCloud -or $UpdateTag
$total = $names.Count
$index = 0
:VMLoop foreach ($Name in $names) {
$index++
Write-Host ""
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host "Processing VM $index of $total : $Name" -ForegroundColor Cyan
Write-Host "============================================================" -ForegroundColor Cyan
$result = [ordered]@{
VMName = $Name; Success = $false; MemoryConverted = $false
GeneralUpdated = $false; Status = $null; Message = $null
}
# ---- Find the VM ----
try {
$VMs = @(Get-SCVirtualMachine -VMMServer $VMMServer -ErrorAction Stop |
Where-Object { $_.Name -ieq $Name })
}
catch {
Write-Host "Failed to retrieve VMs from SCVMM. $($_.Exception.Message)" -ForegroundColor Red
$result.Message = "Failed to retrieve VMs: $($_.Exception.Message)"
[pscustomobject]$result; continue VMLoop
}
if ($VMs.Count -eq 0) {
Write-Host "VM '$Name' was not found in SCVMM." -ForegroundColor Red
$result.Message = 'Not found'
[pscustomobject]$result; continue VMLoop
}
if ($VMs.Count -gt 1) {
Write-Host "More than one VM named '$Name' was found. Skipping." -ForegroundColor Red
$VMs | Select-Object Name, ID, HostName, StatusString | Format-Table -AutoSize | Out-Host
$result.Message = 'Multiple VMs with that name'
[pscustomobject]$result; continue VMLoop
}
try { $VM = Get-VMMRefreshedVM -VM $VMs[0] -Server $VMMServer }
catch {
Write-Host "Failed to refresh VM. $($_.Exception.Message)" -ForegroundColor Red
$result.Message = "Refresh failed: $($_.Exception.Message)"
[pscustomobject]$result; continue VMLoop
}
# ---- Work out what is needed ----
$MemoryUpdateRequired = [bool]$VM.DynamicMemoryEnabled
$WasRunning = $VM.StatusString -notmatch 'PowerOff|Stopped'
$StaticMemoryMB = $null
if ($null -ne $VM.Memory -and [int64]$VM.Memory -gt 0) {
$StaticMemoryMB = [int]$VM.Memory
}
elseif ($MemoryUpdateRequired) {
Write-Host "Unable to determine a valid Startup Memory for '$($VM.Name)'. Skipping." -ForegroundColor Red
$result.Message = 'Invalid Startup Memory'
[pscustomobject]$result; continue VMLoop
}
$CurrentCloudName = if ($null -ne $VM.Cloud) { $VM.Cloud.Name } else { 'Not assigned' }
Write-Host ""
Write-Host "Selected VM details:" -ForegroundColor Green
Write-Host "VM Name : $($VM.Name)"
Write-Host "Host : $($VM.HostName)"
Write-Host "Status : $($VM.StatusString)"
Write-Host "Dynamic Memory : $($VM.DynamicMemoryEnabled)"
Write-Host "Memory : $StaticMemoryMB MB"
Write-Host "Description : $($VM.Description)"
Write-Host "Cost Center : $($VM.CostCenter)"
Write-Host "Tag : $($VM.Tag)"
Write-Host "Cloud : $CurrentCloudName"
Write-Host ""
if (-not ($MemoryUpdateRequired -or $GeneralUpdateRequired)) {
Write-Host "No changes requested for '$($VM.Name)'." -ForegroundColor Yellow
$result.Success = $true; $result.Status = $VM.StatusString
$result.Message = 'No changes requested'
[pscustomobject]$result; continue VMLoop
}
$action = if ($MemoryUpdateRequired -and $WasRunning) {
"Shut down, convert to Static Memory ($StaticMemoryMB MB), update properties, restart"
} elseif ($MemoryUpdateRequired) {
"Convert to Static Memory ($StaticMemoryMB MB) and update properties (VM stays powered off)"
} else {
'Update General properties (no shutdown, memory unchanged)'
}
if (-not $PSCmdlet.ShouldProcess($VM.Name, $action)) {
$result.Message = 'Skipped (not confirmed / WhatIf)'
[pscustomobject]$result; continue VMLoop
}
# ---- Shut down if converting memory ----
if ($MemoryUpdateRequired) {
if ($WasRunning) {
Write-Host "Attempting graceful shutdown..." -ForegroundColor Cyan
try { Stop-SCVirtualMachine -VM $VM -Shutdown -ErrorAction Stop | Out-Null }
catch { Write-Host "Graceful shutdown request returned: $($_.Exception.Message)" -ForegroundColor Yellow }
try { $w = Wait-VMMVMState -VM $VM -Server $VMMServer -State Off -TimeoutSeconds 300 -IntervalSeconds 10 }
catch {
Write-Host "Failed during shutdown monitoring. $($_.Exception.Message)" -ForegroundColor Red
$result.Message = "Shutdown monitoring failed: $($_.Exception.Message)"
[pscustomobject]$result; continue VMLoop
}
$VM = $w.VM
if (-not $w.Reached) {
if (-not ($ForcePowerOff -or $PSCmdlet.ShouldContinue(
"Graceful shutdown failed. Force power off VM '$($VM.Name)'?", 'Force power off'))) {
Write-Host "Cancelled. No VM properties were changed." -ForegroundColor Yellow
$result.Message = 'Graceful shutdown failed; force power off declined'
[pscustomobject]$result; continue VMLoop
}
Write-Host "Force powering off VM..." -ForegroundColor Yellow
try {
Stop-SCVirtualMachine -VM $VM -Force -ErrorAction Stop | Out-Null
$w = Wait-VMMVMState -VM $VM -Server $VMMServer -State Off -TimeoutSeconds 300 -IntervalSeconds 5
}
catch {
Write-Host "Failed to force power off. $($_.Exception.Message)" -ForegroundColor Red
$result.Message = "Force power off failed: $($_.Exception.Message)"
[pscustomobject]$result; continue VMLoop
}
$VM = $w.VM
if (-not $w.Reached) {
Write-Host "VM did not power off within five minutes. Check SCVMM jobs." -ForegroundColor Red
$result.Message = 'VM did not power off'
[pscustomobject]$result; continue VMLoop
}
}
}
else {
Write-Host "VM is already powered off and will remain so." -ForegroundColor Green
}
try { $VM = Get-VMMRefreshedVM -VM $VM -Server $VMMServer }
catch {
$result.Message = "Refresh failed: $($_.Exception.Message)"
[pscustomobject]$result; continue VMLoop
}
if ($VM.StatusString -notmatch 'PowerOff|Stopped') {
Write-Host "VM is not powered off (status: $($VM.StatusString)). No changes attempted." -ForegroundColor Red
$result.Message = 'VM not powered off'
[pscustomobject]$result; continue VMLoop
}
}
# ---- Build and apply the update ----
$params = @{ VM = $VM; ErrorAction = 'Stop' }
if ($MemoryUpdateRequired) {
$params['DynamicMemoryEnabled'] = $false
$params['MemoryMB'] = $StaticMemoryMB
}
if ($UpdateDescription) { $params['Description'] = $Description }
if ($UpdateCostCenter) { $params['CostCenter'] = $CostCenter }
if ($UpdateTag) { $params['Tag'] = $Tag }
if ($UpdateCloud) { $params['Cloud'] = $NewCloud }
try {
Set-SCVirtualMachine @params | Out-Null
Write-Host "VM properties were updated successfully." -ForegroundColor Green
}
catch {
Write-Host "Failed to update VM properties. $($_.Exception.Message)" -ForegroundColor Red
if ($MemoryUpdateRequired) { Write-Host "The VM remains powered off." -ForegroundColor Yellow }
$result.Message = "Update failed: $($_.Exception.Message)"
[pscustomobject]$result; continue VMLoop
}
# ---- Validate ----
try { $VM = Get-VMMRefreshedVM -VM $VM -Server $VMMServer }
catch {
$result.Message = "Post-update refresh failed: $($_.Exception.Message)"
[pscustomobject]$result; continue VMLoop
}
Write-Host "Validating the updated VM properties..." -ForegroundColor Cyan
$failed = New-Object System.Collections.Generic.List[string]
if ($MemoryUpdateRequired) {
if ($VM.DynamicMemoryEnabled) { $failed.Add('Dynamic Memory still enabled') }
if ([int]$VM.Memory -ne $StaticMemoryMB) { $failed.Add("Memory is $($VM.Memory) MB, expected $StaticMemoryMB MB") }
}
if ($UpdateDescription -and $VM.Description -ne $Description) { $failed.Add('Description mismatch') }
if ($UpdateCostCenter -and $VM.CostCenter -ne $CostCenter) { $failed.Add('Cost Center mismatch') }
if ($UpdateTag -and $VM.Tag -ne $Tag) { $failed.Add('Tag mismatch') }
if ($UpdateCloud -and ($null -eq $VM.Cloud -or $VM.Cloud.ID -ne $NewCloud.ID)) { $failed.Add('Cloud mismatch') }
if ($failed.Count -gt 0) {
$failed | ForEach-Object { Write-Host "Validation failed: $_" -ForegroundColor Red }
if ($MemoryUpdateRequired) { Write-Host "The VM will remain powered off for investigation." -ForegroundColor Yellow }
$result.Message = 'Validation failed: ' + ($failed -join '; ')
$result.Status = $VM.StatusString
[pscustomobject]$result; continue VMLoop
}
Write-Host "All validations passed." -ForegroundColor Green
# ---- Restart if it was running ----
$startOk = $true
if ($MemoryUpdateRequired -and $WasRunning) {
Write-Host "Starting VM..." -ForegroundColor Cyan
try {
Start-SCVirtualMachine -VM $VM -ErrorAction Stop | Out-Null
$w = Wait-VMMVMState -VM $VM -Server $VMMServer -State Running -TimeoutSeconds 300 -IntervalSeconds 10
$VM = $w.VM
if (-not $w.Reached) {
$startOk = $false
Write-Host "VM did not report Running within five minutes. Check SCVMM manually." -ForegroundColor Yellow
}
}
catch {
$startOk = $false
Write-Host "Failed to start the VM. $($_.Exception.Message)" -ForegroundColor Red
}
}
try { $VM = Get-VMMRefreshedVM -VM $VM -Server $VMMServer } catch { }
$FinalCloud = if ($null -ne $VM.Cloud) { $VM.Cloud.Name } else { 'Not assigned' }
Write-Host ""
Write-Host "Completed:" -ForegroundColor Green
Write-Host " - VM: $($VM.Name)"
Write-Host " - Dynamic Memory: $($VM.DynamicMemoryEnabled)"
Write-Host " - Static Memory: $($VM.Memory) MB"
Write-Host " - Description: $($VM.Description)"
Write-Host " - Cost Center: $($VM.CostCenter)"
Write-Host " - Tag: $($VM.Tag)"
Write-Host " - Cloud: $FinalCloud"
Write-Host " - Current status: $($VM.StatusString)"
$result.Success = $startOk
$result.MemoryConverted = $MemoryUpdateRequired
$result.GeneralUpdated = $GeneralUpdateRequired
$result.Status = $VM.StatusString
$result.Message = if ($startOk) { 'OK' } else { 'Updated, but VM did not restart cleanly' }
[pscustomobject]$result
}
Write-Host ""
Write-Host "Finished processing $total VM name(s) entered." -ForegroundColor Cyan
}
}
Export-ModuleMember -Function Set-VMMStaticMemory
Questions or improvements?
Get the code on GitHub, send me a note, or browse the other VMware and automation posts.
View the code on GitHub → Browse all blogs Email Ravi