Simon Vedder

Cloud Engineer · Zürich

Start-InPlaceUpgrade

Snapshot one Azure VM, attach the upgrade media and start Windows Setup unattended

The Start half of the state machine (ADR 0003). Runs the preflight, creates an incremental snapshot of the OS disk as the rollback point, builds a managed disk from Microsoft’s hidden upgrade image, attaches it, locates setup.exe inside the guest and starts it through a scheduled task running as SYSTEM (ADR 0001). Returns within minutes; the upgrade itself takes 30 to 120 minutes and several reboots and is finished by Complete-InPlaceUpgrade.

Start does not wait. Use it when something else has to survive the gap - an Azure Automation job is cancelled after three hours, so the runbook starts in one job and checks in later ones. At a console, where nothing cancels you, Invoke-InPlaceUpgrade does Start, the waiting and Complete in a single call.

Writes nothing to the VM’s metadata. Everything Complete-InPlaceUpgrade needs comes back in the result object - Snapshot, Engine, MediaDisk, StartedAt and a ready-to-run ResumeCommand - so the caller decides where that state lives. The runbook keeps it in tags; a script may keep it in a variable. On failure the media disk is removed unless -KeepMediaDisk is set, and the snapshot always stays.

Idempotent, and it asks the guest rather than a tag: a VM already on the target build is skipped, a Setup that is already running is not started again, an existing media disk is reused, an attached disk is not attached twice, and a snapshot from an earlier attempt is reused when the caller passes -ReuseSnapshot. The install.wim image is always passed explicitly (/installfrom, /imageindex), detected from the media’s WIM metadata, because unattended Setup cannot choose between the Core and Desktop Experience images itself.

This changes the VM. ConfirmImpact is High, so it prompts unless -Confirm:$false or -Force is given; -WhatIf runs the preflight and stops before the snapshot.

Syntax

Start-InPlaceUpgrade -VM <Object> -Target <string> [-ReuseSnapshot <string>] [-Engine <string>] [-MediaDiskResourceGroupName <string>] [-MediaDiskSkuName <string>] [-ProductKey <string>] [-UseMatrixProductKey] [-TargetImageIndex <int>] [-MinimumFreeSpaceGB <int>] [-Force] [-KeepMediaDisk] [-LogIngestionEndpoint <string>] [-DataCollectionRuleId <string>] [-WhatIf] [-Confirm] [<CommonParameters>]

Start-InPlaceUpgrade -ResourceGroupName <string> -Name <string> -Target <string> [-ReuseSnapshot <string>] [-Engine <string>] [-MediaDiskResourceGroupName <string>] [-MediaDiskSkuName <string>] [-ProductKey <string>] [-UseMatrixProductKey] [-TargetImageIndex <int>] [-MinimumFreeSpaceGB <int>] [-Force] [-KeepMediaDisk] [-LogIngestionEndpoint <string>] [-DataCollectionRuleId <string>] [-WhatIf] [-Confirm] [<CommonParameters>]

Requirements

PermissionsMicrosoft.Compute/virtualMachines/read, write, instanceView/read, runCommand/action; Microsoft.Compute/disks/read, write, delete; Microsoft.Compute/snapshots/read, write; Microsoft.Compute/locations/publishers/artifacttypes/offers/skus/versions/read; Microsoft.Resources/subscriptions/resourceGroups/read (the Bicep deployment ships a custom role with these actions plus Microsoft.Resources/tags/write, which the runbook - not this function - needs)
PrerequisitesPowerShell 7.2+, Az.Compute, Az.Resources; a healthy Azure Guest Agent in the VM; an established Azure context

Parameters

NameTypeRequiredPipelineDefaultDescription
-VMObjectyesyesThe VM object from Get-AzVM. Accepts pipeline input.
-ResourceGroupNameStringyesnoResource group of the VM when -Name is used instead of -VM.
-NameStringyesnoName of the VM when -ResourceGroupName is used instead of -VM.
-TargetStringyesnoRequired. Target key from the matrix, for example WS2025; Get-InPlaceUpgradeTarget lists them. The module never reads it from a tag - the caller states the target.
-ReuseSnapshotStringnonoName of a snapshot from a previous attempt. Start used to find this in a tag; the caller passes it now, so the module never reads resource metadata.
-EngineStringnonoMediaDiskMediaDisk (default): Microsoft’s upgrade media as a managed disk, no network needed, every documented source version. FeatureUpdate (experimental): the Windows Server 2025 feature update through the Windows Update Agent, WS2019/WS2022 only, needs Windows Update reachability; no media disk is created (ADR 0006).
-MediaDiskResourceGroupNameStringnonoResource group for the media disk. Defaults to the VM’s resource group.
-MediaDiskSkuNameStringnonoStandard_LRSStorage SKU of the media disk. Standard_LRS is plenty; Setup reads it once.
-ProductKeyStringnonoExplicit key for setup.exe /pkey. Use a public KMS client setup key (GVLK) only; the value ends up in the scheduled task definition, visible to every local administrator.
-UseMatrixProductKeySwitchParameternonoPass the matrix’s public KMS client setup key for the guest’s edition as /pkey. Rarely needed: the 0xC1900215 failure is caused by image selection, not by the key (see KNOWN-ISSUES), and is solved by the explicit image index below. Kept for guests whose own key does not validate.
-TargetImageIndexInt32nono0Override the install.wim image index for setup.exe /installfrom and /imageindex. By default the index is detected from the WIM metadata by matching the guest’s edition and installation type; an override is refused when the index does not exist on the media.
-MinimumFreeSpaceGBInt32nono30Free space required on C: by the preflight.
-ForceSwitchParameternonoProceed although the preflight reports NotEligible, and suppress the confirmation prompt. AlreadyAtTarget is never overridden.
-KeepMediaDiskSwitchParameternonoKeep the media disk when Start fails. Useful when debugging a Setup that dies immediately.
-LogIngestionEndpointStringnonoLogs ingestion endpoint of a data collection endpoint (https://…ingest.monitor.azure.com). Together with -DataCollectionRuleId, every state transition is written to the InPlaceUpgrade_CL table. Empty means no telemetry.
-DataCollectionRuleIdStringnonoImmutable id (dcr-…) of the data collection rule that routes Custom-InPlaceUpgrade_CL.

Supports -WhatIf and -Confirm.

Examples

Example 1

# Upgrade one VM by name (prompts before the snapshot). No tags needed.
Start-InPlaceUpgrade -ResourceGroupName rg-apps-prod-weu -Name vm-app-prod-weu-01 -Target WS2025

Example 2

# A fleet: the caller selects the VMs however it likes and passes the target per machine
Get-AzVM -ResourceGroupName rg-apps-prod-weu | Where-Object { $_.Tags.UpgradeState -eq 'Pending' } |
    ForEach-Object { Start-InPlaceUpgrade -VM $_ -Target $_.Tags.UpgradeTarget -UseMatrixProductKey -Confirm:$false }

Example 3

# Preflight and plan only
Start-InPlaceUpgrade -ResourceGroupName rg-apps-prod-weu -Name vm-app-prod-weu-01 -Target WS2025 -WhatIf

Output

  • AzureInPlaceUpgrade.StartResult

Generated from the comment-based help in the module. Same text as Get-Help Start-InPlaceUpgrade -Full, and as the copy on GitHub.