Improves Windows image index selection

Updates selection to match images by language-independent edition metadata instead of localized names, reducing failures across ISO/ESD sources and languages.

Adds server Desktop Experience vs Core handling via installation type and prefers the best match deterministically, falling back to a user prompt only when needed with better logging.
This commit is contained in:
rbalsleyMSFT
2026-01-29 16:44:46 -08:00
parent 65e5ce0c63
commit b2a7ef5f41
+119 -27
View File
@@ -2386,43 +2386,135 @@ function Get-Index {
[string]$WindowsSKU [string]$WindowsSKU
) )
# Get the available indexes in the WIM/ESD
# Get the available indexes using Get-WindowsImage
$imageIndexes = Get-WindowsImage -ImagePath $WindowsImagePath $imageIndexes = Get-WindowsImage -ImagePath $WindowsImagePath
# Get the ImageName of ImageIndex 1 if an ISO was specified, else use ImageIndex 4 - this is usually Home or Education SKU on ESD MCT media # Normalize SKU and determine if Desktop Experience is explicitly requested (Server only)
if ($ISOPath) { $normalizedWindowsSKU = $WindowsSKU.Trim()
if ($WindowsSKU -notmatch "Standard|Datacenter") { $isDesktopExperienceRequested = $normalizedWindowsSKU -match '\(Desktop Experience\)'
$imageIndex = $imageIndexes | Where-Object ImageIndex -eq 1 $normalizedWindowsSKU = $normalizedWindowsSKU -replace '\s*\(Desktop Experience\)\s*', ''
$WindowsImage = $imageIndex.ImageName.Substring(0, 10)
} # Map user-selected SKU to language-independent EditionId values
else { # Notes:
$imageIndex = $imageIndexes | Where-Object ImageIndex -eq 1 # - Client: EditionId values are stable across languages (e.g. Professional, Core, Education)
$WindowsImage = $imageIndex.ImageName.Substring(0, 19) # - Server: Desktop Experience vs Core is differentiated by InstallationType (EditionId is the same)
} $editionIdCandidates = switch ($normalizedWindowsSKU) {
} 'Home' { @('Core') }
else { 'Core' { @('Core') }
$imageIndex = $imageIndexes | Where-Object ImageIndex -eq 4
$WindowsImage = $imageIndex.ImageName.Substring(0, 10) 'Home N' { @('CoreN') }
'CoreN' { @('CoreN') }
'Home Single Language' { @('CoreSingleLanguage') }
'CoreSingleLanguage' { @('CoreSingleLanguage') }
'Education' { @('Education') }
'Education N' { @('EducationN') }
'EducationN' { @('EducationN') }
'Pro' { @('Professional') }
'Professional' { @('Professional') }
'Pro N' { @('ProfessionalN') }
'ProfessionalN' { @('ProfessionalN') }
'Pro Education' { @('ProfessionalEducation') }
'ProfessionalEducation' { @('ProfessionalEducation') }
'Pro Education N' { @('ProfessionalEducationN') }
'ProfessionalEducationN' { @('ProfessionalEducationN') }
'Pro for Workstations' { @('ProfessionalWorkstation') }
'ProfessionalWorkstation' { @('ProfessionalWorkstation') }
'Pro N for Workstations' { @('ProfessionalWorkstationN') }
'ProfessionalWorkstationN' { @('ProfessionalWorkstationN') }
'Enterprise' { @('Enterprise') }
'Enterprise N' { @('EnterpriseN') }
'EnterpriseN' { @('EnterpriseN') }
'Enterprise LTSC' { @('EnterpriseS') }
'Enterprise 2016 LTSB' { @('EnterpriseS') }
'EnterpriseS' { @('EnterpriseS') }
'Enterprise N LTSC' { @('EnterpriseSN') }
'Enterprise N 2016 LTSB' { @('EnterpriseSN') }
'EnterpriseSN' { @('EnterpriseSN') }
'IoT Enterprise LTSC' { @('IoTEnterpriseS') }
'IoTEnterpriseS' { @('IoTEnterpriseS') }
'IoT Enterprise N LTSC' { @('IoTEnterpriseSN') }
'IoTEnterpriseSN' { @('IoTEnterpriseSN') }
'Standard' { @('ServerStandard') }
'ServerStandard' { @('ServerStandard') }
'Datacenter' { @('ServerDatacenter') }
'ServerDatacenter' { @('ServerDatacenter') }
default { @() }
} }
# Concatenate $WindowsImage and $WindowsSKU (E.g. Windows 11 Pro) # Determine preferred InstallationType for Server images
$ImageNameToFind = "$WindowsImage $WindowsSKU" $preferredInstallationType = $null
if ($normalizedWindowsSKU -in @('Standard', 'Datacenter', 'ServerStandard', 'ServerDatacenter')) {
# Find the ImageName in all of the indexes in the image if ($isDesktopExperienceRequested) {
$matchingImageIndex = $imageIndexes | Where-Object ImageName -eq $ImageNameToFind $preferredInstallationType = 'Server'
# Return the index that matches exactly
if ($matchingImageIndex) {
return $matchingImageIndex.ImageIndex
} }
else { else {
$preferredInstallationType = 'Server Core'
}
}
# If we can map SKU -> EditionId, attempt a non-interactive match
if ($editionIdCandidates.Count -gt 0) {
# Build per-index metadata (EditionId, InstallationType) to match deterministically
$imageMetadata = @(foreach ($imageIndex in $imageIndexes) {
try {
$details = Get-WindowsImage -ImagePath $WindowsImagePath -Index $imageIndex.ImageIndex
[pscustomobject]@{
ImageIndex = $details.ImageIndex
ImageName = $details.ImageName
ImageSize = $details.ImageSize
EditionId = $details.EditionId
InstallationType = $details.InstallationType
}
}
catch {
$null
}
}) | Where-Object { $null -ne $_ }
# Match by EditionId first
$imageMatches = $imageMetadata | Where-Object { $_.EditionId -in $editionIdCandidates }
# If this is a Server SKU, prefer the requested InstallationType (Server vs Server Core)
if ($null -ne $preferredInstallationType -and $imageMatches.Count -gt 0) {
$preferredImageMatches = $imageMatches | Where-Object { $_.InstallationType -eq $preferredInstallationType }
if ($preferredImageMatches.Count -gt 0) {
$imageMatches = $preferredImageMatches
}
}
# If multiple matches remain, pick the largest image (Desktop Experience tends to be larger)
if ($imageMatches.Count -gt 0) {
$bestMatch = $imageMatches | Sort-Object -Property ImageSize -Descending | Select-Object -First 1
WriteLog "Selected Windows image index $($bestMatch.ImageIndex) (SKU='$WindowsSKU', EditionId='$($bestMatch.EditionId)', InstallationType='$($bestMatch.InstallationType)'): $($bestMatch.ImageName)"
return $bestMatch.ImageIndex
}
}
# Final fallback: prompt the user to select an ImageName
# Look for the numbers 10, 11, 2016, 2019, 2022+ in the ImageName # Look for the numbers 10, 11, 2016, 2019, 2022+ in the ImageName
$relevantImageIndexes = $imageIndexes | Where-Object { ($_.ImageName -match "(10|11|2016|2019|202\d)") } $relevantImageIndexes = $imageIndexes | Where-Object { ($_.ImageName -match "(10|11|2016|2019|202\d)") }
WriteLog "No matching image index found for SKU '$WindowsSKU' in '$WindowsImagePath'. Prompting user to select an ImageName."
while ($true) { while ($true) {
# Present list of ImageNames to the end user if no matching ImageIndex is found # Present list of ImageNames to the end user if no matching ImageIndex is found
Write-Host "No matching ImageIndex found for $ImageNameToFind. Please select an ImageName from the list below:" Write-Host "No matching ImageIndex found for Windows SKU '$WindowsSKU'. Please select an ImageName from the list below:"
$i = 1 $i = 1
$relevantImageIndexes | ForEach-Object { $relevantImageIndexes | ForEach-Object {
@@ -2437,13 +2529,13 @@ function Get-Index {
$selectedImage = $relevantImageIndexes[$inputValue - 1] $selectedImage = $relevantImageIndexes[$inputValue - 1]
if ($selectedImage) { if ($selectedImage) {
WriteLog "User selected Windows image index $($selectedImage.ImageIndex) (SKU='$WindowsSKU'): $($selectedImage.ImageName)"
return $selectedImage.ImageIndex return $selectedImage.ImageIndex
} }
else { else {
Write-Host "Invalid selection, please try again." Write-Host "Invalid selection, please try again."
} }
} }
}
} }
#Create VHDX #Create VHDX