Skip to content

Commit abf031c

Browse files
feat(tests): full VHD source intelligence — gallery, ISO, eval download, Gen1/Gen2
Test VM creation now supports four VHD sources in priority order: 1. GalleryImageName — marketplace image already on the cluster (recommended) 2. IsoPath — local ISO converted to VHDX via Convert-WindowsImage.ps1 3. DownloadEvalIso — downloads WS2022 eval ISO from Microsoft then converts 4. SourceVhdPath — explicit VHDX/VHD path (sideloaded image) 5. (none) — empty VHD, Azure resource layer testing only New Test-Common.ps1 helpers: Get-ConvertWindowsImageScript downloads Convert-WindowsImage.ps1 from MSLab if not already cached in $env:TEMP Convert-IsoToVhdx wraps Convert-WindowsImage with Gen1 (MBR/BIOS) and Gen2 (GPT/UEFI) output; reuses existing VHDX Invoke-EvalIsoDownload downloads WS2022 eval ISO from Microsoft Eval Center; reuses existing file; accepts -DownloadUrl override Invoke-HydrationTest.ps1 new params: -IsoPath, -DownloadEvalIso, -EvalIsoUrl, -IsoEdition -Generation controls both the Hyper-V VM generation AND ISO conversion format New-ReconnectTestScenario.ps1 new params: -IsoPath, -DownloadEvalIso, -EvalIsoUrl, -IsoEdition (Reconnect scenario always uses Gen2 for the initial hydration phase) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent d4c52d3 commit abf031c

3 files changed

Lines changed: 358 additions & 63 deletions

File tree

tests/helpers/Test-Common.ps1

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,173 @@ function Wait-ForAzureResource {
158158

159159
#endregion
160160

161+
#region ── ISO Download and Conversion ───────────────────────────────────────
162+
163+
function Get-ConvertWindowsImageScript {
164+
<#
165+
.SYNOPSIS
166+
Ensures Convert-WindowsImage.ps1 is available locally, downloading it if needed.
167+
.DESCRIPTION
168+
Convert-WindowsImage.ps1 is a Microsoft tool (maintained in MSLab) that converts
169+
a Windows ISO to a bootable VHD/VHDX. This function caches it to avoid repeat downloads.
170+
.OUTPUTS
171+
[string] Path to Convert-WindowsImage.ps1, or $null on failure.
172+
#>
173+
param(
174+
[string]$LocalPath = "$env:TEMP\Convert-WindowsImage.ps1"
175+
)
176+
177+
if (Test-Path $LocalPath) {
178+
Write-TestInfo "Convert-WindowsImage.ps1 cached at: $LocalPath"
179+
return $LocalPath
180+
}
181+
182+
$url = 'https://raw.githubusercontent.com/microsoft/MSLab/master/Tools/Convert-WindowsImage.ps1'
183+
Write-TestInfo "Downloading Convert-WindowsImage.ps1 from MSLab..."
184+
try {
185+
$prev = $ProgressPreference; $ProgressPreference = 'SilentlyContinue'
186+
Invoke-WebRequest -Uri $url -OutFile $LocalPath -UseBasicParsing -ErrorAction Stop
187+
$ProgressPreference = $prev
188+
Write-TestInfo "Downloaded to: $LocalPath"
189+
return $LocalPath
190+
} catch {
191+
$ProgressPreference = $prev
192+
Write-TestFail "Could not download Convert-WindowsImage.ps1: $_"
193+
Write-TestFail "Download manually from: $url"
194+
return $null
195+
}
196+
}
197+
198+
function Convert-IsoToVhdx {
199+
<#
200+
.SYNOPSIS
201+
Converts a Windows Server ISO to a bootable VHDX using Convert-WindowsImage.ps1.
202+
.DESCRIPTION
203+
Gen2 output: GPT/UEFI partition scheme — attach to Hyper-V Generation 2 VM.
204+
Gen1 output: MBR/BIOS partition scheme — attach to Hyper-V Generation 1 VM.
205+
Both produce a Dynamic VHDX. Reuses an existing output file if already present.
206+
.OUTPUTS
207+
[string] Path to the created VHDX, or $null on failure.
208+
#>
209+
param(
210+
[Parameter(Mandatory)] [string]$IsoPath,
211+
[Parameter(Mandatory)] [string]$OutputPath,
212+
[ValidateSet(1, 2)] [int]$Generation = 2,
213+
[string]$Edition = 'Windows Server 2022 Datacenter',
214+
[int64]$SizeBytes = 60GB,
215+
[string]$ConvertScriptPath
216+
)
217+
218+
if (-not (Test-Path $IsoPath)) {
219+
Write-TestFail "ISO not found: $IsoPath"
220+
return $null
221+
}
222+
223+
if (Test-Path $OutputPath) {
224+
$mb = [math]::Round((Get-Item $OutputPath).Length / 1MB)
225+
Write-TestInfo "VHDX already exists (${mb} MB) — reusing: $OutputPath"
226+
return $OutputPath
227+
}
228+
229+
if (-not $ConvertScriptPath) {
230+
$ConvertScriptPath = Get-ConvertWindowsImageScript
231+
}
232+
if (-not $ConvertScriptPath) { return $null }
233+
234+
Write-TestInfo "Converting ISO → VHDX (Generation $Generation, $([math]::Round($SizeBytes/1GB)) GB)"
235+
Write-TestInfo " ISO : $IsoPath"
236+
Write-TestInfo " Output : $OutputPath"
237+
Write-TestInfo " Edition : $Edition"
238+
Write-TestWarn "This may take 5–15 minutes depending on disk speed."
239+
240+
try {
241+
# Dot-source to bring Convert-WindowsImage into scope
242+
. $ConvertScriptPath
243+
244+
$cwParams = @{
245+
SourcePath = $IsoPath
246+
VHDPath = $OutputPath
247+
VHDFormat = 'VHDX'
248+
Edition = $Edition
249+
SizeBytes = $SizeBytes
250+
VHDType = 'Dynamic'
251+
}
252+
253+
# Gen1 → MBR/BIOS partition scheme; Gen2 → GPT/UEFI (default)
254+
if ($Generation -eq 1) {
255+
$cwParams['BCDinVHD'] = 'NativeBoot'
256+
}
257+
258+
Convert-WindowsImage @cwParams
259+
260+
if (Test-Path $OutputPath) {
261+
$mb = [math]::Round((Get-Item $OutputPath).Length / 1MB)
262+
Write-TestInfo "VHDX created (${mb} MB): $OutputPath"
263+
return $OutputPath
264+
}
265+
266+
Write-TestFail "Convert-WindowsImage completed but output file not found: $OutputPath"
267+
return $null
268+
} catch {
269+
Write-TestFail "ISO-to-VHDX conversion failed: $_"
270+
return $null
271+
}
272+
}
273+
274+
function Invoke-EvalIsoDownload {
275+
<#
276+
.SYNOPSIS
277+
Downloads the Windows Server evaluation ISO from Microsoft.
278+
.DESCRIPTION
279+
Uses the Microsoft Evaluation Center redirect URL. The file is ~5 GB.
280+
If the ISO already exists at DestinationPath it is reused without re-downloading.
281+
282+
Note: Microsoft periodically changes evaluation download URLs. If the default URL
283+
fails, visit https://www.microsoft.com/en-us/evalcenter/download-windows-server-2022
284+
to get a current direct-download link and pass it via -DownloadUrl.
285+
.OUTPUTS
286+
[string] Path to the downloaded ISO, or $null on failure.
287+
#>
288+
param(
289+
[string]$DestinationPath = "$env:TEMP\WS2022_eval.iso",
290+
# Default URL = Microsoft Eval Center redirect for WS2022 ISO (en-us, x64)
291+
[string]$DownloadUrl = 'https://go.microsoft.com/fwlink/p/?LinkID=2195280&clcid=0x409&culture=en-us&country=US'
292+
)
293+
294+
if (Test-Path $DestinationPath) {
295+
$mb = [math]::Round((Get-Item $DestinationPath).Length / 1MB)
296+
Write-TestInfo "Eval ISO already present (${mb} MB) — reusing: $DestinationPath"
297+
return $DestinationPath
298+
}
299+
300+
$dir = Split-Path $DestinationPath
301+
if ($dir -and -not (Test-Path $dir)) { New-Item -Path $dir -ItemType Directory -Force | Out-Null }
302+
303+
Write-TestInfo "Downloading Windows Server 2022 Evaluation ISO (~5 GB)..."
304+
Write-TestInfo "URL : $DownloadUrl"
305+
Write-TestInfo "Destination : $DestinationPath"
306+
Write-TestWarn "This download may take 10–30 minutes on a typical connection."
307+
308+
try {
309+
$prev = $ProgressPreference; $ProgressPreference = 'SilentlyContinue'
310+
Invoke-WebRequest -Uri $DownloadUrl -OutFile $DestinationPath -UseBasicParsing -ErrorAction Stop
311+
$ProgressPreference = $prev
312+
313+
$mb = [math]::Round((Get-Item $DestinationPath).Length / 1MB)
314+
Write-TestInfo "Download complete (${mb} MB): $DestinationPath"
315+
return $DestinationPath
316+
} catch {
317+
$ProgressPreference = $prev
318+
Write-TestFail "Eval ISO download failed: $_"
319+
Write-TestFail "Download manually from:"
320+
Write-TestFail " https://www.microsoft.com/en-us/evalcenter/download-windows-server-2022"
321+
Write-TestFail "Then pass the path with: -IsoPath '<path-to-iso>'"
322+
return $null
323+
}
324+
}
325+
326+
#endregion
327+
161328
#region ── Gallery Image Resolution ──────────────────────────────────────────
162329

163330
function Get-GalleryImagePath {

tests/hydration/Invoke-HydrationTest.ps1

Lines changed: 115 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -46,44 +46,66 @@
4646
4747
.PARAMETER GalleryImageName
4848
Name of an Azure Local gallery (marketplace) image already downloaded to this cluster.
49-
The script resolves its local VHDX path automatically and uses it as the test VM disk.
50-
Takes precedence over -SourceVhdPath when both are provided.
51-
52-
List available images:
53-
az stack-hci-vm image list --resource-group <rg> --output table
49+
Resolves to the local VHDX automatically — recommended for realistic full-OS testing.
50+
List available images: az stack-hci-vm image list -g <rg> --output table
5451
5552
.PARAMETER SourceVhdPath
56-
Full path to an existing Windows Server VHDX to use as the test VM disk.
57-
Use this if -GalleryImageName resolution fails, or to specify an exact file.
58-
If neither -GalleryImageName nor -SourceVhdPath is set, an empty VHD is created.
53+
Explicit local path to a VHDX/VHD to copy into the test VM folder.
54+
Use when -GalleryImageName resolution fails or you have a custom template.
55+
56+
.PARAMETER IsoPath
57+
Path to a Windows Server ISO on the cluster node. The script converts it to a
58+
bootable VHDX using Convert-WindowsImage.ps1 (downloaded automatically from MSLab).
59+
Use with -Generation to produce Gen1 (MBR/BIOS) or Gen2 (GPT/UEFI) output.
60+
61+
.PARAMETER DownloadEvalIso
62+
Download the Windows Server 2022 Evaluation ISO from Microsoft automatically,
63+
then convert it to VHDX. Requires ~5 GB download + conversion time.
64+
Override the download URL with -EvalIsoUrl if the default link has changed.
65+
66+
.PARAMETER EvalIsoUrl
67+
Override the default Microsoft evaluation ISO download URL.
68+
Visit https://www.microsoft.com/en-us/evalcenter/download-windows-server-2022
69+
to get a current direct-download link if the default has expired.
70+
71+
.PARAMETER IsoEdition
72+
Windows Server edition to install when converting from ISO.
73+
Default: 'Windows Server 2022 Datacenter'
74+
Common values:
75+
'Windows Server 2022 Standard'
76+
'Windows Server 2022 Standard (Desktop Experience)'
77+
'Windows Server 2022 Datacenter'
78+
'Windows Server 2022 Datacenter (Desktop Experience)'
5979
6080
.PARAMETER SkipSetup
6181
Skip the New-HydrationTestVM step. Use when the VM already exists.
6282
6383
.EXAMPLE
64-
# Azure resource layer test (empty VHD):
65-
.\Invoke-HydrationTest.ps1 `
66-
-ResourceGroup 'rg-azlocal-test' `
67-
-CustomLocation '/subscriptions/.../customlocations/cl-test' `
68-
-StoragePathId '/subscriptions/.../storageContainers/UserStorage1' `
69-
-StorageRootPath 'C:\ClusterStorage\Volume1' `
70-
-SubnetId 'lnet-test-vlan10' `
71-
-Location 'eastus'
84+
# Azure resource layer test only (empty VHD, no OS needed):
85+
.\Invoke-HydrationTest.ps1 -ResourceGroup 'rg-test' -CustomLocation '...' `
86+
-StoragePathId '...' -StorageRootPath 'C:\ClusterStorage\csv-01' `
87+
-SubnetId 'lnet-test' -Location 'eastus'
88+
89+
.EXAMPLE
90+
# Use a marketplace image already on the cluster (recommended):
91+
.\Invoke-HydrationTest.ps1 ... -GalleryImageName 'windows-server-2022-datacenter'
7292
7393
.EXAMPLE
74-
# Full end-to-end test with real Windows Server VHD:
75-
.\Invoke-HydrationTest.ps1 `
76-
-ResourceGroup 'rg-azlocal-test' `
77-
-CustomLocation '/subscriptions/.../customlocations/cl-test' `
78-
-StoragePathId '/subscriptions/.../storageContainers/UserStorage1' `
79-
-StorageRootPath 'C:\ClusterStorage\Volume1' `
80-
-SubnetId 'lnet-test-vlan10' `
81-
-Location 'eastus' `
82-
-SourceVhdPath 'C:\ClusterStorage\csv-01\ISOs\WS2022_template.vhdx'
94+
# Use an ISO already on cluster storage:
95+
.\Invoke-HydrationTest.ps1 ... -IsoPath 'C:\ClusterStorage\csv-01\ISOs\WS2022.iso'
96+
97+
.EXAMPLE
98+
# Download eval ISO automatically and convert (Gen2):
99+
.\Invoke-HydrationTest.ps1 ... -DownloadEvalIso
100+
101+
.EXAMPLE
102+
# Download eval ISO and test Gen1 hydration path:
103+
.\Invoke-HydrationTest.ps1 ... -DownloadEvalIso -Generation 1
83104
84105
.NOTES
85-
Run on one of the Azure Local cluster nodes.
106+
Run on one of the Azure Local cluster nodes as Administrator.
86107
Azure CLI must be authenticated (az login).
108+
VHD source priority: GalleryImageName > IsoPath/DownloadEvalIso > SourceVhdPath > empty VHD.
87109
#>
88110
[CmdletBinding(SupportsShouldProcess)]
89111
param(
@@ -121,6 +143,18 @@ param(
121143
[Parameter()]
122144
[string]$SourceVhdPath,
123145

146+
[Parameter()]
147+
[string]$IsoPath,
148+
149+
[Parameter()]
150+
[switch]$DownloadEvalIso,
151+
152+
[Parameter()]
153+
[string]$EvalIsoUrl,
154+
155+
[Parameter()]
156+
[string]$IsoEdition = 'Windows Server 2022 Datacenter',
157+
124158
[Parameter()]
125159
[switch]$SkipClusterCheck,
126160

@@ -144,19 +178,63 @@ function Record-Pass([string]$msg) { $script:passed++; Write-TestPass $msg }
144178
function Record-Fail([string]$msg) { $script:failed++; $script:failures.Add($msg); Write-TestFail $msg }
145179

146180
#region ── Resolve VHD Source ─────────────────────────────────────────────────
181+
# Priority: GalleryImageName > IsoPath/DownloadEvalIso > SourceVhdPath > empty VHD
182+
183+
if (-not $SkipSetup) {
184+
185+
if ($GalleryImageName) {
186+
# ── Option 1: Marketplace / gallery image already on the cluster
187+
$resolvedPath = Get-GalleryImagePath `
188+
-ImageName $GalleryImageName `
189+
-ResourceGroup $ResourceGroup `
190+
-StorageRootPath $StorageRootPath
191+
if ($resolvedPath) {
192+
$SourceVhdPath = $resolvedPath
193+
Write-Host " [VHD] Gallery image : $GalleryImageName" -ForegroundColor Cyan
194+
Write-Host " Local path : $SourceVhdPath" -ForegroundColor Cyan
195+
} else {
196+
Write-Host " [WARN] Gallery image '$GalleryImageName' not resolved — check next option or use -SourceVhdPath." -ForegroundColor Yellow
197+
}
198+
}
199+
200+
if (-not $SourceVhdPath -and ($IsoPath -or $DownloadEvalIso)) {
201+
# ── Option 2: ISO → convert to VHDX
202+
$iso = $IsoPath
203+
204+
if ($DownloadEvalIso -and -not $iso) {
205+
$isoFile = Join-Path $env:TEMP 'WS2022_eval.iso'
206+
$dlParams = @{ DestinationPath = $isoFile }
207+
if ($EvalIsoUrl) { $dlParams['DownloadUrl'] = $EvalIsoUrl }
208+
$iso = Invoke-EvalIsoDownload @dlParams
209+
}
210+
211+
if ($iso) {
212+
$genTag = "gen$Generation"
213+
$vhdxOut = Join-Path $env:TEMP "ws2022-test-${genTag}.vhdx"
214+
$cvParams = @{
215+
IsoPath = $iso
216+
OutputPath = $vhdxOut
217+
Generation = $Generation
218+
Edition = $IsoEdition
219+
}
220+
$converted = Convert-IsoToVhdx @cvParams
221+
if ($converted) {
222+
$SourceVhdPath = $converted
223+
Write-Host " [VHD] Converted ISO : Gen$Generation VHDX" -ForegroundColor Cyan
224+
Write-Host " Path : $SourceVhdPath" -ForegroundColor Cyan
225+
} else {
226+
Write-Host " [WARN] ISO conversion failed — falling back to empty VHD." -ForegroundColor Yellow
227+
}
228+
}
229+
}
147230

148-
# Gallery image takes precedence; fall back to explicit SourceVhdPath; then empty VHD
149-
if ($GalleryImageName -and -not $SkipSetup) {
150-
$resolvedPath = Get-GalleryImagePath `
151-
-ImageName $GalleryImageName `
152-
-ResourceGroup $ResourceGroup `
153-
-StorageRootPath $StorageRootPath
154-
if ($resolvedPath) {
155-
$SourceVhdPath = $resolvedPath
156-
Write-Host " Using gallery image: $GalleryImageName" -ForegroundColor Cyan
157-
Write-Host " Local path : $SourceVhdPath" -ForegroundColor Cyan
231+
if ($SourceVhdPath) {
232+
# ── Option 3: Explicit path already set (from above or passed directly)
233+
Write-Host " [VHD] Source VHDX : $SourceVhdPath" -ForegroundColor Cyan
158234
} else {
159-
Write-Host " [WARN] Could not resolve gallery image '$GalleryImageName' — falling back to empty VHD." -ForegroundColor Yellow
235+
# ── Option 4: Empty VHD — Azure resource layer testing only
236+
Write-Host " [VHD] Mode: empty VHD (no OS) — Azure resource registration only." -ForegroundColor Yellow
237+
Write-Host " Supply -GalleryImageName, -IsoPath, -DownloadEvalIso, or -SourceVhdPath for full OS testing." -ForegroundColor Yellow
160238
}
161239
}
162240

0 commit comments

Comments
 (0)