From 17b16891d15e5574e1d09e1ef198bffc790d99e5 Mon Sep 17 00:00:00 2001 From: maged909 Date: Wed, 19 Aug 2026 21:02:53 +0300 Subject: [PATCH] Restore image edition metadata lost during export On some source images the exported install.wim loses the EDITIONID, INSTALLATIONTYPE, PRODUCTTYPE, PRODUCTSUITE and LANGUAGES elements from its XML metadata block. Windows Setup matches ei.cfg against EDITIONID, so without it the install fails with "Setup has failed to validate the product key" - both when a product key is supplied and when the user picks "I don't have a product key". Reported in #583. Read the edition metadata off the source image before servicing, and restore it after the final export if it went missing. The XML block is written last in the WIM, so it can be rewritten in place and the file retruncated; the header's XML resource entry is repointed and the integrity table cleared. No-op when the metadata survived the export. Co-Authored-By: Claude Opus 5 --- tiny11maker.ps1 | 115 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/tiny11maker.ps1 b/tiny11maker.ps1 index c24757f2..77242ed4 100644 --- a/tiny11maker.ps1 +++ b/tiny11maker.ps1 @@ -67,6 +67,103 @@ function Remove-RegistryValue { } } +function Repair-WimMetadata { + # On some images (reported on 25H2, see issue #583) the exported WIM loses the + # EDITIONID/INSTALLATIONTYPE/PRODUCTTYPE/PRODUCTSUITE/LANGUAGES elements from its + # XML. Setup matches ei.cfg against EDITIONID, so without it the install dies with + # "Setup has failed to validate the product key" - both when a key is supplied and + # when the user picks "I don't have a product key". + # + # This restores those elements from the values read off the source image before + # servicing. It is a no-op when the metadata survived the export. + param ( + [string]$wimPath, + [hashtable]$source + ) + try { + $stream = [System.IO.File]::Open($wimPath, 'Open', 'ReadWrite') + } catch { + Write-Output "Could not open $wimPath to check metadata: $_" + return + } + try { + $header = New-Object byte[] 208 + $null = $stream.Read($header, 0, 208) + if ([System.Text.Encoding]::ASCII.GetString($header, 0, 5) -ne 'MSWIM') { + Write-Output "Not a WIM file, skipping metadata check." + return + } + + # rhXmlData lives at offset 72: size (7 bytes) + flags (1 byte), offset, uncompressed size + $xmlSize = [int]([BitConverter]::ToUInt64($header, 72) -band 0x00FFFFFFFFFFFFFF) + $xmlFlags = $header[79] + $xmlOffset = [long][BitConverter]::ToUInt64($header, 80) + + $buffer = New-Object byte[] $xmlSize + $null = $stream.Seek($xmlOffset, 'Begin') + $null = $stream.Read($buffer, 0, $xmlSize) + $xml = [System.Text.Encoding]::Unicode.GetString($buffer).TrimStart([char]0xFEFF) + + if ($xml -match '') { + Write-Output "Image metadata is intact." + return + } + + # the XML blob is written last, so it can be rewritten in place and the file retruncated + if ($xmlOffset + $xmlSize -ne $stream.Length) { + Write-Output "WARNING: image metadata is missing but the XML block is not at the end of the WIM." + Write-Output "Skipping repair - the resulting ISO may fail to install." + return + } + + Write-Output "Image metadata was lost during export. Restoring it..." + $languages = '' + if ($source.Languages) { + $languages = '' + $source.Languages + '' + $source.Languages + '' + } + $restore = '' + $source.EditionId + '' + + '' + $source.InstallationType + '' + + '' + $source.ProductType + '' + + '' + $source.ProductSuite + '' + + $languages + $xml = $xml -replace '()', "`$1$restore" + if ($xml -notmatch '') { + Write-Output "WARNING: could not restore metadata, PRODUCTNAME element not found." + return + } + + # TOTALBYTES has to match the new file size, and the number's own length feeds + # back into that size, so settle it by iterating + for ($i = 0; $i -lt 6; $i++) { + $bytes = [byte[]](0xFF, 0xFE) + [System.Text.Encoding]::Unicode.GetBytes($xml) + $updated = $xml -replace '^\d+', ('' + ($xmlOffset + $bytes.Length) + '') + if ($updated -eq $xml) { break } + $xml = $updated + } + $bytes = [byte[]](0xFF, 0xFE) + [System.Text.Encoding]::Unicode.GetBytes($xml) + + $null = $stream.Seek($xmlOffset, 'Begin') + $stream.Write($bytes, 0, $bytes.Length) + $stream.SetLength($xmlOffset + $bytes.Length) + + $sizeField = [BitConverter]::GetBytes([uint64]$bytes.Length) + $sizeField[7] = $xmlFlags + [Array]::Copy($sizeField, 0, $header, 72, 8) + [Array]::Copy([BitConverter]::GetBytes([uint64]$bytes.Length), 0, $header, 88, 8) + + # the edit invalidates any integrity table, so clear it + for ($i = 124; $i -lt 148; $i++) { $header[$i] = 0 } + + $null = $stream.Seek(0, 'Begin') + $stream.Write($header, 0, 208) + Write-Output "Restored image metadata (EditionId: $($source.EditionId))." + } catch { + Write-Output "Error while repairing image metadata: $_" + } finally { + $stream.Close() + } +} + #---------[ Execution ]---------# # Check if PowerShell execution is restricted if ((Get-ExecutionPolicy) -eq 'Restricted') { @@ -193,6 +290,21 @@ if (-not $architecture) { Write-Output "Architecture information not found." } +# Remember the edition metadata so it can be restored if the export drops it (see Repair-WimMetadata) +$sourceMetadata = @{} +try { + $sourceImage = Get-WindowsImage -ImagePath "$ScratchDisk\tiny11\sources\install.wim" -Index $index + $sourceMetadata = @{ + EditionId = $sourceImage.EditionId + InstallationType = $sourceImage.InstallationType + ProductType = $sourceImage.ProductType + ProductSuite = $sourceImage.ProductSuite + Languages = ($sourceImage.Languages | Select-Object -First 1) + } +} catch { + Write-Output "Could not read source image metadata: $_" +} + Write-Output "Mounting complete! Performing removal of applications..." $packages = & 'dism' '/English' "/image:$($ScratchDisk)\scratchdir" '/Get-ProvisionedAppxPackages' | @@ -396,6 +508,9 @@ Write-Host "Exporting image..." Dism.exe /Export-Image /SourceImageFile:"$ScratchDisk\tiny11\sources\install.wim" /SourceIndex:$index /DestinationImageFile:"$ScratchDisk\tiny11\sources\install2.wim" /Compress:recovery Remove-Item -Path "$ScratchDisk\tiny11\sources\install.wim" -Force | Out-Null Rename-Item -Path "$ScratchDisk\tiny11\sources\install2.wim" -NewName "install.wim" | Out-Null +if ($sourceMetadata.EditionId) { + Repair-WimMetadata -wimPath "$ScratchDisk\tiny11\sources\install.wim" -source $sourceMetadata +} Write-Output "Windows image completed. Continuing with boot.wim." Start-Sleep -Seconds 2 Clear-Host