diff --git a/docs/src/content/docs/guides/win11creator.mdx b/docs/src/content/docs/guides/win11creator.mdx index 150354e1..9139f240 100644 --- a/docs/src/content/docs/guides/win11creator.mdx +++ b/docs/src/content/docs/guides/win11creator.mdx @@ -144,6 +144,7 @@ When you install Windows 11 from your modified ISO: | Driver injection seems stuck | WIM servicing can pause at a mount or commit. Allow 10–20 minutes depending on hardware, and check the live log before closing WinUtil | | "Access Denied" error | Make sure WinUtil is running as Administrator | | Driver injection warning | Review the live log for the named package. Other compatible packages continue; if none succeed, the ISO is created with the original `install.wim` | +| DISM error with an explanation in parentheses, e.g. "exit code 112 (Disk is full)" | WinUtil now explains common DISM failures in plain language. Free up disk space for a "Disk is full" error, or run WinUtil as Administrator for an "Access denied" error. If the code has no explanation, search the number on Microsoft's [DISM error code reference](https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes) | | "Setup has failed to validate the product key" | Recreate the ISO with the latest WinUtil. The creator now removes stale `PID.txt`, writes `sources\ei.cfg`, and pins the selected image in `autounattend.xml` so setup does not use an embedded OEM key for a different edition | --- diff --git a/functions/private/Invoke-WinUtilISOScript.ps1 b/functions/private/Invoke-WinUtilISOScript.ps1 index b4abe884..2dc46d56 100644 --- a/functions/private/Invoke-WinUtilISOScript.ps1 +++ b/functions/private/Invoke-WinUtilISOScript.ps1 @@ -185,6 +185,21 @@ function Invoke-WinUtilISOScript { return @($survivingFolders) } + $knownExitCode = @{ + 112 = "Disk is full" + 5 = "Access denied" + 2 = "File not found" + 3 = "Path not found" + 87 = "Invalid parameter" + 1168 = "Element not found" + 1392 = "File or directory is corrupted" + 32 = "File in use / sharing violation" + 21 = "Device not ready" + 1460 = "Operation timed out" + 1223 = "Operation cancelled by user" + 50 = "Request not supported" + } + function Invoke-WinUtilISODism { param ( [Parameter(Mandatory)][string[]]$Arguments, @@ -199,7 +214,11 @@ function Invoke-WinUtilISOScript { & $Logger " dism[$Operation]: $line" } } - throw "DISM $Operation failed with exit code $exitCode." + if ($knownExitCode.ContainsKey($exitCode)) { + throw "DISM $Operation failed with exit code $exitCode ($($knownExitCode[$exitCode]))." + } else { + throw "DISM $Operation failed with exit code $exitCode." + } } if ($Operation -ne 'metadata') { & $Logger "DISM $Operation completed." diff --git a/pester/win11creator.Tests.ps1 b/pester/win11creator.Tests.ps1 index 350aa9a3..baf666bb 100644 --- a/pester/win11creator.Tests.ps1 +++ b/pester/win11creator.Tests.ps1 @@ -950,6 +950,109 @@ Describe "Win11 Creator setup media" { } } + It "reports a friendly explanation when DISM fails with a known exit code" { + $contentRoot = Join-Path ([IO.Path]::GetTempPath()) "WinUtilIsoMountFailureKnownCode_$([guid]::NewGuid())" + $installWim = Join-Path $contentRoot 'sources\install.wim' + $template = Get-Content -Path $script:autoUnattendPath -Raw + $script:dismCalls = [System.Collections.Generic.List[string]]::new() + + function dism.exe { + param([Parameter(ValueFromRemainingArguments)][string[]]$Arguments) + + $script:dismCalls.Add(($Arguments -join '|')) + if ($Arguments -contains '/Get-WimInfo') { + $global:LASTEXITCODE = 0 + 'Languages : en-US' + 'Installation : Client' + 'Edition : Professional' + 'ProductSuite : Terminal Server' + 'ProductType : WinNT' + } elseif ($Arguments -contains '/Mount-Image') { + $global:LASTEXITCODE = 112 + 'Mount failed' + } elseif ($Arguments -contains '/Get-MountedImageInfo') { + $global:LASTEXITCODE = 0 + "Mount Dir : $(Join-Path (Split-Path -Path $contentRoot -Parent) 'wim_mount')" + } elseif ($Arguments -contains '/Export-Driver') { + $global:LASTEXITCODE = 0 + Export-WinUtilTestDriverPackage -Arguments $Arguments -Fixtures @( + @{ Path = 'storage_pkg'; Name = 'iaStorAC.inf'; Class = 'System' } + ) + } else { + $global:LASTEXITCODE = 0 + } + } + + try { + New-Item -Path (Split-Path $installWim -Parent) -ItemType Directory -Force | Out-Null + Set-Content -Path $installWim -Value 'mock-wim' + . $script:isoScriptPath + + { Invoke-WinUtilISOScript -ISOContentsDir $contentRoot -AutoUnattendXml $template -InjectCurrentSystemDrivers $true -InstallImagePath $installWim -InstallImageIndex 6 -InstallEditionId 'Professional' } | + Should -Throw '*112*Disk is full*' + + @($script:dismCalls | Where-Object { $_ -match '/Get-MountedImageInfo' }).Count | Should -Be 1 + @($script:dismCalls | Where-Object { $_ -match '/Unmount-Image\|.*\|/Discard' }).Count | Should -Be 1 + } finally { + Remove-Item Function:\dism.exe -ErrorAction SilentlyContinue + Remove-Item -Path $contentRoot -Recurse -Force -ErrorAction SilentlyContinue + } + } + + It "falls back to the plain exit code for an unmapped DISM failure" { + $contentRoot = Join-Path ([IO.Path]::GetTempPath()) "WinUtilIsoMountFailureUnknownCode_$([guid]::NewGuid())" + $installWim = Join-Path $contentRoot 'sources\install.wim' + $template = Get-Content -Path $script:autoUnattendPath -Raw + $script:dismCalls = [System.Collections.Generic.List[string]]::new() + + function dism.exe { + param([Parameter(ValueFromRemainingArguments)][string[]]$Arguments) + + $script:dismCalls.Add(($Arguments -join '|')) + if ($Arguments -contains '/Get-WimInfo') { + $global:LASTEXITCODE = 0 + 'Languages : en-US' + 'Installation : Client' + 'Edition : Professional' + 'ProductSuite : Terminal Server' + 'ProductType : WinNT' + } elseif ($Arguments -contains '/Mount-Image') { + $global:LASTEXITCODE = 999 + 'Mount failed' + } elseif ($Arguments -contains '/Get-MountedImageInfo') { + $global:LASTEXITCODE = 0 + "Mount Dir : $(Join-Path (Split-Path -Path $contentRoot -Parent) 'wim_mount')" + } elseif ($Arguments -contains '/Export-Driver') { + $global:LASTEXITCODE = 0 + Export-WinUtilTestDriverPackage -Arguments $Arguments -Fixtures @( + @{ Path = 'storage_pkg'; Name = 'iaStorAC.inf'; Class = 'System' } + ) + } else { + $global:LASTEXITCODE = 0 + } + } + + try { + New-Item -Path (Split-Path $installWim -Parent) -ItemType Directory -Force | Out-Null + Set-Content -Path $installWim -Value 'mock-wim' + . $script:isoScriptPath + + $thrown = $null + try { + Invoke-WinUtilISOScript -ISOContentsDir $contentRoot -AutoUnattendXml $template -InjectCurrentSystemDrivers $true -InstallImagePath $installWim -InstallImageIndex 6 -InstallEditionId 'Professional' + } catch { + $thrown = $_.Exception.Message + } + $thrown | Should -Be 'DISM mount failed with exit code 999.' + + @($script:dismCalls | Where-Object { $_ -match '/Get-MountedImageInfo' }).Count | Should -Be 1 + @($script:dismCalls | Where-Object { $_ -match '/Unmount-Image\|.*\|/Discard' }).Count | Should -Be 1 + } finally { + Remove-Item Function:\dism.exe -ErrorAction SilentlyContinue + Remove-Item -Path $contentRoot -Recurse -Force -ErrorAction SilentlyContinue + } + } + It "does not add driver setup artifacts when injection is disabled" { $contentRoot = Join-Path ([IO.Path]::GetTempPath()) "WinUtilIsoNoDrivers_$([guid]::NewGuid())"