From 9956bf403a607ca8a428487ec83298244fc1f573 Mon Sep 17 00:00:00 2001 From: Sensei002 Date: Tue, 27 Oct 2026 01:47:58 +0600 Subject: [PATCH] Fix Ventoy installs with UDF 1.50 and a media-locating Setup launch ISOs built here stopped on Ventoy with "A media driver your computer needs is missing". The message is not about a driver: Ventoy boots the ISO's own boot.wim and injects vtoyjump.exe to mount the ISO as a virtual drive, so Setup only sees sources\install.wim after that mount has happened. - Build with -udfver150 instead of -udfver102. UDF 1.02 cannot describe a file larger than 4 GB, which a trimmed install.wim exceeds. - Core starts the boot image's own setup.exe bootstrap instead of a fixed X:/sources/setup.exe, so Setup locates the real media before running that media's classic installer. The scripts no longer overwrite Setup\CmdLine. - Refuse to create an ISO whose sources\ has no usable install.wim/esd, remove stray install2 images, and never delete install.wim after a failed DISM export. Each of those failures looks identical to the Ventoy one. - Fix tiny11Coremaker.ps1 referencing an undefined $ScratchDisk, including in the oscdimg command, which built the ISO from the wrong directory. --- README.md | 29 ++++++++ docs/usage.md | 49 ++++++++++++- src/Tiny11Builder.Core/BuildEngine.Tail.cs | 43 ++++++++++- src/Tiny11Builder.Core/SetupInstaller.cs | 25 ++++++- .../SetupInstallerTests.cs | 17 +++++ tiny11Coremaker.ps1 | 73 +++++++++++++++++-- tiny11maker.ps1 | 40 +++++++++- 7 files changed, 261 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index e3f13795..597aa288 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,35 @@ Windows. Test your ISO in a VM before installing it.** ![Build settings with Turbo mode and optional image cleanup](docs/images/ui-build-settings.png) +## Booting from Ventoy + +ISOs built here boot from Ventoy in its default (non-wimboot) mode โ€” no Rufus, +no extracted USB stick, no manual patching. If an older build stops at +**"A media driver your computer needs is missing"**, the wording is misleading: +it is not a missing disk driver, it means Setup never saw its installation +media. Rebuild the ISO; nothing about the Ventoy stick needs to change. + +Ventoy does not extract a Windows ISO. It boots the ISO's own `boot.wim` and +injects `vtoyjump.exe` so the ISO can be mounted as a virtual drive before +`setup.exe` runs. Two builder behaviours broke that: + +| Change | Why it broke Ventoy | +| --- | --- | +| The WinPE launch chain forced to a fixed `X:\sources\setup.exe` (`Setup\CmdLine` in the scripts, `winpeshl.ini` in the app) | Setup takes the boot RAM disk as its media root, finds no `install.wim` beside it and stops. Core only. | +| `oscdimg -udfver102` | UDF 1.02 cannot describe a file larger than 4 GB, so a trimmed `install.wim` is written with metadata that lean readers mis-parse. | + +Both are fixed: the stock WinPE launch chain is left alone, so the boot image's +own `setup.exe` bootstrap locates the media before running that media's +`sources\setup.exe`, and ISOs are built with `-udfver150`. A build also refuses +to emit an ISO whose `sources\` has no usable `install.wim`/`install.esd`, +because that failure looks identical to this one. + +If a rebuilt ISO still fails, press `Shift+F10` at the error and run `diskpart`, +then `list volume`. No virtual DVD drive means `vtoyjump` never ran; +`X:\Windows\System32\ventoy\vtoype.log` records why a mount failed. `Ctrl+W` at +the Ventoy menu switches to wimboot mode, a different boot path. Also check that +Ventoy is 1.1.07 or newer and that its data partition is NTFS rather than exFAT. + ## More information [Usage, compatibility and build instructions](docs/usage.md) ยท diff --git a/docs/usage.md b/docs/usage.md index 7676c46a..62d1d63a 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -63,9 +63,14 @@ The six-step wizard provides: acknowledgement before a build. - Core and Custom WinSxS/component-payload removals always use the legacy Windows installer, including on 24H2 and newer media. The private Setup - boot image starts `wpeinit.exe`, then its matching `sources\setup.exe` - (`setup_old.exe` when present), instead of the newer SetupHost workflow. - This is automatic, not a hardware-bypass option; no installer from a + boot image starts `wpeinit.exe`, then the boot image's own `setup.exe` + bootstrap, which locates the installation media and runs that media's + matching `sources\setup.exe` (`setup_old.exe` when present) instead of the + newer SetupHost workflow. Starting a fixed `X:\sources\setup.exe` instead + would make Setup treat the boot RAM disk as the media root and stop with + "A media driver your computer needs is missing" โ€” including under Ventoy, + which mounts the ISO as a virtual drive only after its own WinPE hook has + run. This is automatic, not a hardware-bypass option; no installer from a different Windows release is downloaded. Media without the classic executable fails explicitly. A resumed Core job also receives this change even if its final installation image was already compressed. @@ -94,6 +99,44 @@ The six-step wizard provides: state, verified saved progress for **Resume build**, an output SHA-256, a JSON report, and separate interrupted-workspace discard/recovery. +## Booting from Ventoy + +Output ISOs boot from Ventoy in its default (non-wimboot) mode, for both Tiny11 +and Tiny11 Core. Ventoy does not extract a Windows ISO: it boots the ISO's own +`boot.wim` and injects `vtoyjump.exe`, which mounts the ISO as a virtual drive +before `setup.exe` runs. Setup only reads `sources\install.wim` after that +mount, so anything that stops the hook from running, or that hides the media +from Setup, surfaces as the same misleading message: + +> A media driver your computer needs is missing. This could be a DVD, USB, or Hard disk driver. + +That message is not about a driver. It means Setup never saw its installation +media. Three build-time causes are handled: + +| Cause | Handling | +| --- | --- | +| The WinPE launch chain forced to a fixed `X:\sources\setup.exe` (`Setup\CmdLine` in the scripts, `winpeshl.ini` in the app) | The stock launch chain is left alone. The boot image's own `setup.exe` bootstrap locates the media and runs that media's `sources\setup.exe`. | +| `oscdimg -udfver102` | ISOs are built with `-udfver150`. UDF 1.02 cannot describe a file larger than 4 GB, which a trimmed `install.wim` exceeds. | +| A failed image export that still deleted the old image | The build refuses to create an ISO whose `sources\` has no usable `install.wim`/`install.esd`, and removes leftover `install2.wim`/`install2.esd`. | + +If a current ISO still stops at that message, press `Shift+F10` and run: + +``` +diskpart +list volume +type X:\Windows\System32\ventoy\vtoype.log +``` + +- **No virtual DVD drive in `list volume`** โ€” `vtoyjump` never ran, so the + problem is the boot image patch rather than the ISO contents. +- **`vtoype.log`** records what `vtoyjump` did and why a mount failed. +- **`Ctrl+W`** at the Ventoy menu switches to wimboot mode, a different boot + path worth trying as a workaround. + +Also check that Ventoy is **1.1.07 or newer**, that its data partition is +**NTFS** rather than exFAT, and that the ISO sits on the first, large Ventoy +partition rather than the 32 MB `VTOYEFI` one. + ## Turbo mode and smaller images The settings page has independent **Turbo mode**, **Remove redundant image diff --git a/src/Tiny11Builder.Core/BuildEngine.Tail.cs b/src/Tiny11Builder.Core/BuildEngine.Tail.cs index 8c6c800a..3a6f2ffd 100644 --- a/src/Tiny11Builder.Core/BuildEngine.Tail.cs +++ b/src/Tiny11Builder.Core/BuildEngine.Tail.cs @@ -304,6 +304,7 @@ await s.Tracking.RunAsync("checkpoint:compressed", "Compress", "Save verified co private async Task CreateIsoAsync(BuildSession s, CancellationToken token) { s.Stage("Create ISO", "Creating a UEFI-bootable ISO using Microsoft's oscdimg.", token); + RequireInstallableMedia(s); if (File.Exists(s.TemporaryIso)) { PathSafety.RejectReparsePoints(s.TemporaryIso); @@ -313,12 +314,52 @@ private async Task CreateIsoAsync(BuildSession s, CancellationToken token) var efi = Path.Combine(s.Staging, s.EfiRelative); var boot = s.Image.Architecture == "x64" && File.Exists(bios) ? $"-bootdata:2#p0,e,b{bios}#pEF,e,b{efi}" : $"-bootdata:1#pEF,e,b{efi}"; + // -u2 is the ISO9660 + Joliet + UDF layout Microsoft's own retail media uses; ISO9660 alone cannot hold a + // file larger than 4 GB, so the UDF layer is what carries install.wim. -udfver150 rather than the default + // UDF 1.02: 1.02 cannot describe a single file larger than 4 GB, and a trimmed install.wim routinely exceeds + // that. oscdimg still writes such a file, but the on-disc metadata ends up in a form lenient readers accept + // and lean ones mis-parse. This is a large-file robustness fix; it is not what stopped Ventoy booting. var isoResult = await runner.RunAsync(s.Recipe.OscdimgPath, - ["-m", "-o", "-u2", "-udfver102", boot, s.Staging, s.TemporaryIso], s.Log, token, finishOnCancellation: false).ConfigureAwait(false); + ["-m", "-o", "-u2", "-udfver150", boot, s.Staging, s.TemporaryIso], s.Log, token, finishOnCancellation: false).ConfigureAwait(false); if (isoResult.ExitCode != 0 || !File.Exists(s.TemporaryIso) || new FileInfo(s.TemporaryIso).Length < 34816) throw new IOException($"oscdimg did not produce an ISO (exit {isoResult.ExitCode}). {isoResult.Output}"); } + /// + /// Refuses to publish media that Windows Setup cannot install from. Every failure below looks identical to the + /// user - "A media driver your computer needs is missing" - while the real cause is an ISO with no installation + /// image, so the build has to stop here and name it instead of shipping a disc that cannot install. + /// + private static void RequireInstallableMedia(BuildSession s) + { + var sources = Path.Combine(s.Staging, "sources"); + // An export interrupted between writing and renaming can leave a second image behind. Setup picks the first + // image it finds in sources\, so a stale partial one would silently win over the finished one. + foreach (var stray in new[] { "install2.wim", "install2.esd" }) + { + var file = Path.Combine(sources, stray); + if (!File.Exists(file)) continue; + PathSafety.RejectReparsePoints(file); + File.Delete(file); + s.Warn($"Removed a leftover {stray} from the sources folder; Setup would have detected it as the installation image."); + } + var images = new[] { "install.wim", "install.esd" }.Select(name => Path.Combine(sources, name)) + .Where(File.Exists).ToArray(); + if (images.Length == 0) + throw new IOException("The media has no sources\\install.wim or install.esd, so Windows Setup would stop " + + "with \"A media driver your computer needs is missing\" even though no driver is involved. The image " + + "export step failed; no ISO was created."); + // The export stage already proved each image is complete (DISM lists its single edition and its WIM header and + // footer parse), so only the size is worth reporting here. + foreach (var image in images) + s.Log($"Install image {Path.GetFileName(image)}: {new FileInfo(image).Length / (1024d * 1024d):0} MB"); + // Ventoy only applies its Windows boot patch to an ISO it recognises as standard installation media; without + // these it is treated as a generic image and never mounts itself as the drive Setup reads from. + foreach (var required in new[] { @"setup.exe", @"sources\boot.wim", @"boot\bcd", @"boot\boot.sdi" }) + if (!File.Exists(Path.Combine(s.Staging, required))) + s.Log($"Ventoy compatibility: the staged media has no {required}, so Ventoy will not recognise this ISO as Windows media."); + } + private static async Task VerifyAndPublishAsync(BuildSession s, CancellationToken token) { s.Stage("Verify output", "Hashing the completed ISO and publishing it without overwriting existing files.", token); diff --git a/src/Tiny11Builder.Core/SetupInstaller.cs b/src/Tiny11Builder.Core/SetupInstaller.cs index f5dc24a5..ab5a91a6 100644 --- a/src/Tiny11Builder.Core/SetupInstaller.cs +++ b/src/Tiny11Builder.Core/SetupInstaller.cs @@ -24,6 +24,27 @@ internal static void ApplyLegacy(string mount, Action log) ImageFileAccess.ValidateFile(program); if (new FileInfo(program).Length == 0) throw new InvalidDataException("The legacy installer in the Windows Setup image is empty."); + + // Start the boot image's own setup bootstrap, and let it find the media, instead of starting a fixed path + // inside the boot RAM disk. + // + // boot.wim is loaded to X:, but the installation media is not there. From a USB stick it is another drive; + // under Ventoy it is not a drive at all until vtoyjump.exe has mounted the ISO as a virtual one, and Ventoy + // runs that hook from the same WinPE launch chain this file configures. Setup started as + // X:\sources\setup.exe takes the directory it was loaded from as the media root, finds no install.wim beside + // it, and stops with "A media driver your computer needs is missing" - which reads like a missing storage + // driver but only means Setup never saw its media. setup.exe at the root of the boot image is the stock + // bootstrap: it locates the media and then runs that media's matching sources\setup.exe, which is the classic + // installer this method exists to select. Only when the image ships no bootstrap does the fixed path remain. + var launch = "%SYSTEMDRIVE%\\" + executable; + var bootstrap = Path.Combine(mount, "setup.exe"); + if (File.Exists(bootstrap)) + { + ImageFileAccess.ValidateFile(bootstrap); + if (new FileInfo(bootstrap).Length == 0) + throw new InvalidDataException("The Setup bootstrap in the Windows Setup image is empty."); + launch = @"%SYSTEMDRIVE%\setup.exe"; + } var path = Path.Combine(mount, @"Windows\System32\winpeshl.ini"); PathSafety.RejectReparsePoints(Path.GetDirectoryName(path)!); if (File.Exists(path)) @@ -31,9 +52,9 @@ internal static void ApplyLegacy(string mount, Action log) using var privileges = new ImageOperations.PrivilegeScope(); ImageFileAccess.DeleteEntry(path); } - var content = "[LaunchApps]\r\n%SYSTEMROOT%\\System32\\wpeinit.exe\r\n%SYSTEMDRIVE%\\" + executable + "\r\n"; + var content = "[LaunchApps]\r\n%SYSTEMROOT%\\System32\\wpeinit.exe\r\n" + launch + "\r\n"; File.WriteAllText(path, content, Encoding.ASCII); if (File.ReadAllText(path) != content) throw new IOException("The legacy Windows Setup startup configuration could not be verified."); - log($"Legacy installer enabled: %SYSTEMDRIVE%\\{executable}. Required by Core/component-store removals."); + log($"Legacy installer enabled: {launch}. Required by Core/component-store removals."); } } diff --git a/tests/Tiny11Builder.Core.Tests/SetupInstallerTests.cs b/tests/Tiny11Builder.Core.Tests/SetupInstallerTests.cs index f1936f75..b1bc2f1e 100644 --- a/tests/Tiny11Builder.Core.Tests/SetupInstallerTests.cs +++ b/tests/Tiny11Builder.Core.Tests/SetupInstallerTests.cs @@ -47,6 +47,23 @@ public void Legacy_startup_initializes_PE_then_runs_the_matching_classic_install Assert.Throws(() => SetupInstaller.ApplyLegacy(fixture.Root, messages.Add)); } + [Fact] + public void Legacy_startup_uses_the_media_bootstrap_so_Setup_finds_its_media_outside_the_ram_disk() + { + using var fixture = new BuildFixture(); + var mount = NativeTests.CreateOwnedMount(fixture.Root); + Directory.CreateDirectory(Path.Combine(mount, "sources")); + File.WriteAllText(Path.Combine(mount, @"sources\setup.exe"), "same-build classic binary"); + File.WriteAllText(Path.Combine(mount, "setup.exe"), "stock media bootstrap"); + var messages = new List(); + SetupInstaller.ApplyLegacy(mount, messages.Add); + var ini = File.ReadAllText(Path.Combine(mount, @"Windows\System32\winpeshl.ini")); + // A fixed X:\sources\setup.exe makes Setup treat the RAM disk as the media root, which is empty under + // Ventoy until vtoyjump mounts the ISO. The bootstrap locates the real media first. + Assert.Equal("[LaunchApps]\r\n%SYSTEMROOT%\\System32\\wpeinit.exe\r\n%SYSTEMDRIVE%\\setup.exe\r\n", ini); + Assert.Single(messages); + } + [Fact] public void Missing_classic_installer_is_a_loud_failure_not_a_new_installer_fallback() { diff --git a/tiny11Coremaker.ps1 b/tiny11Coremaker.ps1 index 439768ab..aa816b0b 100644 --- a/tiny11Coremaker.ps1 +++ b/tiny11Coremaker.ps1 @@ -332,11 +332,11 @@ Rename-Item -Path $mainOSDrive\scratchdir\Windows\WinSxS_edit -NewName $mainOSDr Write-Host "Complete!" Write-Host "Loading registry..." -reg load HKLM\zCOMPONENTS $ScratchDisk\scratchdir\Windows\System32\config\COMPONENTS | Out-Null -reg load HKLM\zDEFAULT $ScratchDisk\scratchdir\Windows\System32\config\default | Out-Null -reg load HKLM\zNTUSER $ScratchDisk\scratchdir\Users\Default\ntuser.dat | Out-Null -reg load HKLM\zSOFTWARE $ScratchDisk\scratchdir\Windows\System32\config\SOFTWARE | Out-Null -reg load HKLM\zSYSTEM $ScratchDisk\scratchdir\Windows\System32\config\SYSTEM | Out-Null +reg load HKLM\zCOMPONENTS $mainOSDrive\scratchdir\Windows\System32\config\COMPONENTS | Out-Null +reg load HKLM\zDEFAULT $mainOSDrive\scratchdir\Windows\System32\config\default | Out-Null +reg load HKLM\zNTUSER $mainOSDrive\scratchdir\Users\Default\ntuser.dat | Out-Null +reg load HKLM\zSOFTWARE $mainOSDrive\scratchdir\Windows\System32\config\SOFTWARE | Out-Null +reg load HKLM\zSYSTEM $mainOSDrive\scratchdir\Windows\System32\config\SYSTEM | Out-Null Write-Host "Bypassing system requirements(on the system image):" & 'reg' 'add' 'HKLM\zDEFAULT\Control Panel\UnsupportedHardwareNotificationCache' '/v' 'SV1' '/t' 'REG_DWORD' '/d' '0' '/f' | Out-Null & 'reg' 'add' 'HKLM\zDEFAULT\Control Panel\UnsupportedHardwareNotificationCache' '/v' 'SV2' '/t' 'REG_DWORD' '/d' '0' '/f' | Out-Null @@ -380,7 +380,7 @@ Write-Host "Disabling Sponsored Apps:" & 'reg' 'add' 'HKLM\zSOFTWARE\Policies\Microsoft\Windows\CloudContent' '/v' 'DisableCloudOptimizedContent' '/t' 'REG_DWORD' '/d' '1' '/f' | Out-Null Write-Host "Enabling Local Accounts on OOBE:" & 'reg' 'add' 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\OOBE' '/v' 'BypassNRO' '/t' 'REG_DWORD' '/d' '1' '/f' | Out-Null -Copy-Item -Path "$PSScriptRoot\autounattend.xml" -Destination "$ScratchDisk\scratchdir\Windows\System32\Sysprep\autounattend.xml" -Force | Out-Null +Copy-Item -Path "$PSScriptRoot\autounattend.xml" -Destination "$mainOSDrive\scratchdir\Windows\System32\Sysprep\autounattend.xml" -Force | Out-Null Write-Host "Disabling Reserved Storage:" & 'reg' 'add' 'HKLM\zSOFTWARE\Microsoft\Windows\CurrentVersion\ReserveManager' '/v' 'ShippedWithReserves' '/t' 'REG_DWORD' '/d' '0' '/f' | Out-Null Write-Host "Disabling BitLocker Device Encryption" @@ -483,6 +483,14 @@ Write-Host "Unmounting image..." & 'dism' '/English' '/unmount-image' "/mountdir:$mainOSDrive\scratchdir" '/commit' Write-Host "Exporting image..." & 'dism' '/English' '/Export-Image' "/SourceImageFile:$mainOSDrive\tiny11\sources\install.wim" "/SourceIndex:$index" "/DestinationImageFile:$mainOSDrive\tiny11\sources\install2.wim" '/compress:max' + +# Never delete the source image unless the export actually produced one. A failed export that still deletes +# install.wim leaves the ISO with no installation image at all, and Windows Setup reports that as +# "A media driver your computer needs is missing" - which looks like a USB or driver fault and hides the cause. +if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath "$mainOSDrive\tiny11\sources\install2.wim")) { + throw "DISM /Export-Image failed (exit code $LASTEXITCODE). Refusing to delete install.wim." +} + Remove-Item -Path "$mainOSDrive\tiny11\sources\install.wim" -Force >null Rename-Item -Path "$mainOSDrive\tiny11\sources\install2.wim" -NewName "install.wim" >null Write-Host "Windows image completed. Continuing with boot.wim." @@ -511,7 +519,20 @@ Write-Host "Bypassing system requirements(on the setup image):" & 'reg' 'add' 'HKLM\zSYSTEM\Setup\LabConfig' '/v' 'BypassStorageCheck' '/t' 'REG_DWORD' '/d' '1' '/f' >null & 'reg' 'add' 'HKLM\zSYSTEM\Setup\LabConfig' '/v' 'BypassTPMCheck' '/t' 'REG_DWORD' '/d' '1' '/f' >null & 'reg' 'add' 'HKLM\zSYSTEM\Setup\MoSetup' '/v' 'AllowUpgradesWithUnsupportedTPMOrCPU' '/t' 'REG_DWORD' '/d' '1' '/f' >null -& 'reg' 'add' 'HKEY_LOCAL_MACHINE\zSYSTEM\Setup' '/v' 'CmdLine' '/t' 'REG_SZ' '/d' 'X:\sources\setup.exe' '/f' >null + +# NOTE: this build deliberately does NOT rewrite HKLM\SYSTEM\Setup\CmdLine. +# +# It used to be forced to a bare 'X:\sources\setup.exe'. That value is the WinPE setup launch chain a third-party +# boot manager has to hook in order to work: +# +# Ventoy does not extract an ISO. It boots the ISO's own boot.wim and injects vtoyjump.exe so that vtoyjump can +# mount the ISO as a virtual drive before setup.exe runs. Setup started as X:\sources\setup.exe takes the +# directory it was loaded from - the boot RAM disk - as the media root, finds no install.wim beside it, and stops +# with "A media driver your computer needs is missing". That reads like a missing storage driver but only means +# Setup never saw its media. +# +# The stock value launches the boot image's own setup bootstrap, which locates the real media (the USB stick, or +# the drive Ventoy mounted) and then runs that media's matching sources\setup.exe. Leave it alone. Write-Host "Tweaking complete!" Write-Host "Unmounting Registry..." reg unload HKLM\zCOMPONENTS >null @@ -524,9 +545,40 @@ Write-Host "Unmounting image..." Clear-Host Write-Host "Exporting ESD. This may take a while..." & dism /Export-Image /SourceImageFile:"$mainOSDrive\tiny11\sources\install.wim" /SourceIndex:1 /DestinationImageFile:"$mainOSDrive\tiny11\sources\install.esd" /Compress:recovery + +# Same guard as the WIM export: never delete the source WIM unless the ESD export actually succeeded. +if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath "$mainOSDrive\tiny11\sources\install.esd")) { + throw "DISM /Export-Image to install.esd failed (exit code $LASTEXITCODE). Refusing to delete install.wim." +} + Remove-Item "$mainOSDrive\tiny11\sources\install.wim" > $null 2>&1 Write-Host "The tiny11 image is now completed. Proceeding with the making of the ISO..." Write-Host "Creating ISO image..." + +# Guard: an ISO with no usable installation image produces exactly the symptom "A media driver your computer needs +# is missing" when Setup looks for its media, which is easy to misread as a USB or driver fault. Fail here instead. +$installImages = @("$mainOSDrive\tiny11\sources\install.wim", "$mainOSDrive\tiny11\sources\install.esd") | + Where-Object { Test-Path -LiteralPath $_ } +if (-not $installImages) { + throw "No install.wim or install.esd in $mainOSDrive\tiny11\sources - the image export step failed. Refusing to build an ISO that Setup cannot install from." +} +foreach ($img in $installImages) { + $imgMB = [math]::Round((Get-Item -LiteralPath $img).Length / 1MB, 1) + Write-Host "Install image $([System.IO.Path]::GetFileName($img)): ${imgMB}MB" + if ($imgMB -lt 500) { + throw "Install image $img is only ${imgMB}MB - it looks truncated. Refusing to build the ISO." + } +} + +# A leftover export would confuse Setup's media detection. +foreach ($stray in @('install2.wim', 'install2.esd')) { + $strayPath = Join-Path "$mainOSDrive\tiny11\sources" $stray + if (Test-Path -LiteralPath $strayPath) { + Write-Host "Removing leftover $stray from sources\" + Remove-Item -Path $strayPath -Force -ErrorAction SilentlyContinue + } +} + $ADKDepTools = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\$hostarchitecture\Oscdimg" $localOSCDIMGPath = "$PSScriptRoot\oscdimg.exe" @@ -556,7 +608,12 @@ if ([System.IO.Directory]::Exists($ADKDepTools)) { $OSCDIMG = $localOSCDIMGPath } -& "$OSCDIMG" '-m' '-o' '-u2' '-udfver102' "-bootdata:2#p0,e,b$ScratchDisk\tiny11\boot\etfsboot.com#pEF,e,b$ScratchDisk\tiny11\efi\microsoft\boot\efisys.bin" "$ScratchDisk\tiny11" "$PSScriptRoot\tiny11.iso" +# ---- ISO container -------------------------------------------------------- +# -u2 ISO9660 + Joliet + UDF, the layout Microsoft's own retail media uses. ISO9660 alone cannot hold a +# >4 GB file, so the UDF layer is what carries install.esd. +# -udfver150 UDF 1.50 rather than 1.02. UDF 1.02 cannot describe a single file larger than 4 GB and a trimmed +# install.esd can exceed that. This is a large-file robustness fix; it is not what stopped Ventoy booting. +& "$OSCDIMG" '-m' '-o' '-u2' '-udfver150' "-bootdata:2#p0,e,b$mainOSDrive\tiny11\boot\etfsboot.com#pEF,e,b$mainOSDrive\tiny11\efi\microsoft\boot\efisys.bin" "$mainOSDrive\tiny11" "$PSScriptRoot\tiny11.iso" # Finishing up Write-Host "Creation completed! Press any key to exit the script..." diff --git a/tiny11maker.ps1 b/tiny11maker.ps1 index c24757f2..cb5cd456 100644 --- a/tiny11maker.ps1 +++ b/tiny11maker.ps1 @@ -394,6 +394,14 @@ Write-Output "Unmounting image..." Dismount-WindowsImage -Path $ScratchDisk\scratchdir -Save Write-Host "Exporting image..." Dism.exe /Export-Image /SourceImageFile:"$ScratchDisk\tiny11\sources\install.wim" /SourceIndex:$index /DestinationImageFile:"$ScratchDisk\tiny11\sources\install2.wim" /Compress:recovery + +# Never delete the source image unless the export actually produced one. A failed export that still deletes +# install.wim leaves the ISO with no installation image at all, and Windows Setup reports that as +# "A media driver your computer needs is missing" - which looks like a USB or driver fault and hides the cause. +if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath "$ScratchDisk\tiny11\sources\install2.wim")) { + throw "DISM /Export-Image failed (exit code $LASTEXITCODE). Refusing to delete install.wim." +} + Remove-Item -Path "$ScratchDisk\tiny11\sources\install.wim" -Force | Out-Null Rename-Item -Path "$ScratchDisk\tiny11\sources\install2.wim" -NewName "install.wim" | Out-Null Write-Output "Windows image completed. Continuing with boot.wim." @@ -439,6 +447,31 @@ Write-Output "The tiny11 image is now completed. Proceeding with the making of t Write-Output "Copying unattended file for bypassing MS account on OOBE..." Copy-Item -Path "$PSScriptRoot\autounattend.xml" -Destination "$ScratchDisk\tiny11\autounattend.xml" -Force | Out-Null Write-Output "Creating ISO image..." + +# Guard: an ISO with no usable installation image produces exactly the symptom "A media driver your computer needs +# is missing" when Setup looks for its media, which is easy to misread as a USB or driver fault. Fail here instead. +$installImages = @("$ScratchDisk\tiny11\sources\install.wim", "$ScratchDisk\tiny11\sources\install.esd") | + Where-Object { Test-Path -LiteralPath $_ } +if (-not $installImages) { + throw "No install.wim or install.esd in $ScratchDisk\tiny11\sources - the image export step failed. Refusing to build an ISO that Setup cannot install from." +} +foreach ($img in $installImages) { + $imgMB = [math]::Round((Get-Item -LiteralPath $img).Length / 1MB, 1) + Write-Output "Install image $([System.IO.Path]::GetFileName($img)): ${imgMB}MB" + if ($imgMB -lt 500) { + throw "Install image $img is only ${imgMB}MB - it looks truncated. Refusing to build the ISO." + } +} + +# A leftover export would confuse Setup's media detection. +foreach ($stray in @('install2.wim', 'install2.esd')) { + $strayPath = Join-Path "$ScratchDisk\tiny11\sources" $stray + if (Test-Path -LiteralPath $strayPath) { + Write-Output "Removing leftover $stray from sources\" + Remove-Item -Path $strayPath -Force -ErrorAction SilentlyContinue + } +} + $ADKDepTools = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\$hostarchitecture\Oscdimg" $localOSCDIMGPath = "$PSScriptRoot\oscdimg.exe" @@ -466,7 +499,12 @@ if ([System.IO.Directory]::Exists($ADKDepTools)) { $OSCDIMG = $localOSCDIMGPath } -& "$OSCDIMG" '-m' '-o' '-u2' '-udfver102' "-bootdata:2#p0,e,b$ScratchDisk\tiny11\boot\etfsboot.com#pEF,e,b$ScratchDisk\tiny11\efi\microsoft\boot\efisys.bin" "$ScratchDisk\tiny11" "$PSScriptRoot\tiny11.iso" +# ---- ISO container -------------------------------------------------------- +# -u2 ISO9660 + Joliet + UDF, the layout Microsoft's own retail media uses. ISO9660 alone cannot hold a +# >4 GB file, so the UDF layer is what carries install.wim. +# -udfver150 UDF 1.50 rather than 1.02. UDF 1.02 cannot describe a single file larger than 4 GB and a trimmed +# install.wim can exceed that. This is a large-file robustness fix; it is not what stopped Ventoy booting. +& "$OSCDIMG" '-m' '-o' '-u2' '-udfver150' "-bootdata:2#p0,e,b$ScratchDisk\tiny11\boot\etfsboot.com#pEF,e,b$ScratchDisk\tiny11\efi\microsoft\boot\efisys.bin" "$ScratchDisk\tiny11" "$PSScriptRoot\tiny11.iso" # Finishing up Write-Output "Creation completed! Press any key to exit the script..."