From fc03af421bb3d07f6eb6bc34108d5ac543cbe126 Mon Sep 17 00:00:00 2001 From: Kristian Date: Tue, 29 Sep 2026 18:17:51 +0200 Subject: [PATCH] Add friendly explanations for common DISM exit codes (#5087) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add friendly explanations for common DISM exit codes Invoke-WinUtilISODism threw only a raw exit code on DISM failure (e.g. "DISM add-driver failed with exit code 112"), leaving users to look up what the number means themselves. Adds a $knownExitCode lookup table mapping common Windows/DISM exit codes (disk full, access denied, file/path not found, file in use, timeout, etc.) to plain-English explanations. When a failure's exit code is recognized, the thrown message now includes the explanation in parentheses; unrecognized codes fall back to the original plain-number message. Verified the script still parses correctly after the change. * Add missing period to fallback DISM error message * Add tests for DISM known/unknown exit code error messages Adds a test verifying a known exit code (112) produces the friendly explanation in the thrown message, alongside the existing test verifying an unrecognized code still falls back to the plain numeric message. Verified: all 35 tests in win11creator.Tests.ps1 pass. * Document DISM friendly error messages in Win11 Creator troubleshooting Adds a Troubleshooting table row explaining the new DISM error message format (exit code + explanation in parentheses), with guidance for the most common cases and a link to Microsoft's full error code reference for anything not explained. * Fix duplicate DISM calls in unmapped exit code test The unmapped-code test called Invoke-WinUtilISOScript twice — once via Should -Throw, once to capture the message for the no-parens check — causing $script:dismCalls to double-count. Consolidated to a single call, checking both the exit code and the absence of a parenthesized explanation against one captured exception message. Verified: all 36 tests in win11creator.Tests.ps1 pass. * Fix broken DISM error code reference link in docs Replaced the dead windows-hardware/manufacture link with Microsoft's actual System Error Codes reference page, which DISM exit codes correspond to. * Strengthen DISM fallback regression coverage --------- Co-authored-by: Chris Titus --- docs/src/content/docs/guides/win11creator.mdx | 1 + functions/private/Invoke-WinUtilISOScript.ps1 | 21 +++- pester/win11creator.Tests.ps1 | 103 ++++++++++++++++++ 3 files changed, 124 insertions(+), 1 deletion(-) 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())"