Skip to content

Commit eb4c794

Browse files
committed
Fix download resets, localized output and pending servicing in 0.1.2
1 parent d060813 commit eb4c794

31 files changed

Lines changed: 2875 additions & 125 deletions

‎docs/usage.md‎

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ The six-step wizard provides:
3636
English with an explicit status message. No URL or save dialog is required;
3737
downloads go into the selected workspace's `downloads` directory. Microsoft
3838
availability/region blocks are reported rather than bypassed.
39+
Transient connection resets and busy-server responses are retried a bounded
40+
number of times with visible status. After a secure-handshake reset the app
41+
can retry with TLS 1.2, still using Windows certificate validation. Certificate,
42+
DNS, proxy and permission failures are not bypassed. The failure notice links
43+
to Microsoft's download page; a local ISO remains an option. Interrupted
44+
ISO body transfers retain their partial file for retry/resume.
3945
- Official Microsoft ISO downloads with speed/progress, strong-ETag resumable
4046
partial files, ISO structure checks, and optional verification against a
4147
published SHA-256. Only Microsoft's ISO hosts are accepted, including every
@@ -122,6 +128,22 @@ transactions into the image. Other I/O/access errors remain failures.
122128
Unknown symlinks and junctions are refused before a cleanup
123129
target is deleted. It does not scan the running PC for things to delete.
124130

131+
Component cleanup runs after removals but before enabling .NET Framework 3.5:
132+
offline feature changes can leave work that completes only when Windows first
133+
boots. If cleanup returns `0x800F0806` (pending operations), a serviceable
134+
Tiny11/Custom build keeps that work and reports cleanup as **Skipped** with a
135+
warning; the image may be larger. Other cleanup errors remain fatal. Pending
136+
actions are never deleted or reverted to get past this error.
137+
When that exact pending-state error occurs, selected Edge/WebView2 file removal
138+
is also skipped for the run, because its WinSxS files may be referenced by
139+
pending servicing. The report records that those files were kept; it does not
140+
claim the requested removal succeeded. These warnings survive resume.
141+
Core and explicit Custom component-payload removal also check pending files,
142+
package states and feature states immediately before deleting component files,
143+
even when cleanup is off. If enabling .NET 3.5 leaves pending work, those
144+
destructive builds stop with an explanation rather than removing files needed
145+
at first boot. Build without .NET 3.5 or use a serviceable image in that case.
146+
125147
Both Standard and Turbo delete offline entries through backup/restore handles without changing
126148
their ACLs or clearing read-only attributes shared by remaining hard links.
127149
Both handle long image paths
@@ -181,6 +203,12 @@ hosts can reject it even through the native API. Host protection services are
181203
not disabled. The policy applies to Widgets and its taskbar content; support
182204
still varies by Windows edition.
183205

206+
Search highlights use the machine policy
207+
`SOFTWARE\Policies\Microsoft\Windows\Windows Search\EnableDynamicContentInWSB=0`.
208+
The protected default-user `SearchSettings\IsDynamicSearchBoxEnabled`
209+
preference is not edited. Required policy writes are still verified; access
210+
errors are not silently ignored and no host registry protection is disabled.
211+
184212
Core payload deletion uses extended-length Windows paths so long
185213
WinSxS filenames do not fail at the legacy 260-character limit. Symbolic-link
186214
and junction restrictions are unchanged.
@@ -205,6 +233,10 @@ hard-coded maximum build number. Image architecture, edition and version are
205233
read from DISM, not inferred from the ISO filename. Package/capability/feature
206234
identities come from the mounted image, so missing items are logged as *not
207235
present*, rather than guessed.
236+
Edition names are decoded using the OEM code page Windows image tools write,
237+
with UTF byte-order marks recognized explicitly. Cyrillic text is not repaired
238+
by replacing fonts or guessing edition names. Invalid encoded output fails
239+
explicitly rather than silently substituting characters.
208240

209241
Acceptance is **not a certification that every build and every custom removal
210242
combination boots**. This new implementation needs VM validation against
@@ -228,6 +260,38 @@ Unknown newer/Canary builds are not folded into these families and may still
228260
require a preview ADK or newer tool providers. The app stops before
229261
modifications when its tools do not meet that servicing base. It detects
230262
installed tools automatically; there is no manual tool picker.
263+
264+
### Windows 10 reports image tools 10.0.19041
265+
266+
For image build **26300**, tools **10.0.19041.3636** are not the same case as
267+
the earlier false rejection of 26100 tools. Windows 10 can stay installed,
268+
but its inbox servicing tools are older than this image's servicing base.
269+
270+
1. Open the [Microsoft ADK page](https://learn.microsoft.com/windows-hardware/get-started/adk-install)
271+
using **Get image tools** in the app's error notice.
272+
2. Install the ADK that Microsoft lists for Windows 11 24H2/25H2/26H2
273+
(26100 family), selecting **Deployment Tools**, and apply its servicing
274+
updates. The separate Windows PE add-on is not required to modify the
275+
`boot.wim` already present in a complete ISO.
276+
3. Close and reopen Tiny11 Builder so it detects the installed tools, then
277+
start the build again with your original ISO.
278+
279+
Do not replace files in `Windows\System32`, download a standalone DISM EXE,
280+
or remove pending-operation files. The app needs a complete compatible
281+
toolset, not a renamed launcher.
282+
283+
Advanced component inventory is read-only and checks the actual listing
284+
operations instead of imposing the writable-build version gate or output
285+
space requirements. If the installed tools cannot query a newer image,
286+
the app stops the inventory, discards its owned mount and offers **Get image
287+
tools**, linking to Microsoft's ADK page. Listing an image successfully is
288+
not permission to modify it with older tools: build and resume preflight still
289+
require compatible servicing providers.
290+
291+
A saved image's hash, edition, architecture, installation type and complete
292+
version remain verified on resume. Its localized display name is not treated
293+
as an identity: fixing incorrectly decoded text does not invalidate unchanged
294+
saved image bytes.
231295
Microsoft also decides which stable/Insider ISOs are available: the app does not
232296
claim to download unpublished Canary releases or use unofficial UUP mirrors.
233297
The main Download button resolves the current public release automatically.
@@ -312,7 +376,7 @@ runtime, and includes dependency license notices. It refuses to overwrite an
312376
existing archive for that version. Extract the entire ZIP into a folder and
313377
open `Tiny11Builder.exe`; copying just the EXE is not sufficient.
314378

315-
The current app version is **0.1.1**. Packages are unsigned,
379+
The current app version is **0.1.2**. Packages are unsigned,
316380
not Microsoft-certified Windows images. Review [NOTICE.md](../NOTICE.md) before
317381
public redistribution: this upstream checkout has no standalone license file,
318382
and this rewrite does not assign a license to someone else's source.

‎src/Tiny11Builder.App/MainWindow.xaml.cs‎

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ public sealed partial class MainWindow : Window
3535
private bool completed;
3636
private bool coreAcknowledged;
3737
private bool modalOpen;
38-
private enum NoticeAction { None, OpenLogs, CopyDetails }
38+
private enum NoticeAction { None, OpenLogs, CopyDetails, OpenTools, OpenOfficialDownload }
39+
private string officialDownloadPage = OfficialImageDownloader.DownloadPage;
3940
// The run RunOperation is executing and the cancel prompt open for it; both are null when nothing is happening.
4041
private OperationRun? active;
4142
private CancellationFlow? cancelFlow;
@@ -356,7 +357,18 @@ private async Task RunOperation(OperationKind kind, Func<CancellationToken, Task
356357
{
357358
var outcome = Describe(kind, error);
358359
operation.Record(OperationEnd.From(outcome));
359-
try { ShowStopped(kind, outcome); }
360+
try
361+
{
362+
ShowStopped(kind, outcome);
363+
if (OperationOutcomes.RequiresServicingTools(error))
364+
ShowNotice(outcome.Title, outcome.FullMessage,
365+
InfoBarSeverity.Error, NoticeAction.OpenTools, outcome.Details);
366+
else if (OperationOutcomes.DownloadServiceProblem(error) is { } download)
367+
{
368+
officialDownloadPage = download.OfficialPage;
369+
ShowNotice(outcome.Title, download.Message, InfoBarSeverity.Error, NoticeAction.OpenOfficialDownload, outcome.Details);
370+
}
371+
}
360372
catch (Exception display)
361373
{
362374
try { ShowNotice("The operation did not complete.", ErrorText.Reason(error) + " " + display.Message, InfoBarSeverity.Error); }
@@ -464,14 +476,36 @@ private void ShowNotice(string title, string message, InfoBarSeverity severity =
464476
noticeAction = action;
465477
noticeDetails = details ?? "";
466478
NoticeButton.Visibility = action == NoticeAction.None ? Visibility.Collapsed : Visibility.Visible;
467-
NoticeButton.Content = action == NoticeAction.OpenLogs ? "Open logs" : "Copy details";
468-
Microsoft.UI.Xaml.Automation.AutomationProperties.SetName(NoticeButton, action == NoticeAction.OpenLogs ? "Open logs folder" : "Copy error details");
479+
var label = action switch
480+
{
481+
NoticeAction.OpenLogs => "Open logs", NoticeAction.OpenTools => "Get image tools",
482+
NoticeAction.OpenOfficialDownload => "Open Microsoft download page", _ => "Copy details"
483+
};
484+
NoticeButton.Content = label;
485+
Microsoft.UI.Xaml.Automation.AutomationProperties.SetName(NoticeButton, label);
469486
Notice.IsOpen = true;
470487
}
471488

472489
private void OnNoticeAction(object sender, RoutedEventArgs args)
473490
{
474491
if (noticeAction == NoticeAction.OpenLogs) { OnOpenLogs(sender, args); return; }
492+
if (noticeAction is NoticeAction.OpenTools or NoticeAction.OpenOfficialDownload)
493+
{
494+
var page = noticeAction == NoticeAction.OpenTools ? ServicingToolsRequiredException.DownloadPage : officialDownloadPage;
495+
try
496+
{
497+
if (page is not (ServicingToolsRequiredException.DownloadPage or OfficialImageDownloader.DownloadPage or OfficialImageDownloader.ArmDownloadPage))
498+
throw new InvalidDataException("The suggested download page is not an approved Microsoft page.");
499+
using var browser = Process.Start(new ProcessStartInfo(page) { UseShellExecute = true });
500+
if (browser is null) throw new IOException("The browser could not open Microsoft's image-tools page.");
501+
}
502+
catch (Exception error) when (error is IOException or InvalidDataException or System.ComponentModel.Win32Exception or InvalidOperationException)
503+
{
504+
ShowNotice("Could not open the download page.", page + "\n" +
505+
ErrorText.Reason(error), InfoBarSeverity.Error, NoticeAction.CopyDetails, error.ToString());
506+
}
507+
return;
508+
}
475509
if (noticeAction != NoticeAction.CopyDetails || noticeDetails.Length == 0) return;
476510
NoticeButton.Content = CopyToClipboard(noticeDetails) ? "Copied" : "Could not copy";
477511
}
@@ -680,7 +714,8 @@ private async void OnDownload(object sender, RoutedEventArgs args) => await RunO
680714
BeginDownloadText(p => FormatDownload(p, ""));
681715
try
682716
{
683-
var downloaded = await new OfficialImageDownloader(client).DownloadAsync(uri.AbsoluteUri, path, hash, downloadUpdates, token);
717+
var downloaded = await new OfficialImageDownloader(client).DownloadAsync(uri.AbsoluteUri, path, hash, downloadUpdates, token,
718+
status: new UiProgress<string>(text => DispatcherQueue.TryEnqueue(() => DownloadStatus.Text = text)));
684719
DrainDownload();
685720
SourcePathBox.Text = downloaded.Path;
686721
DownloadStatus.Text = $"SHA-256: {downloaded.Sha256}";
@@ -725,7 +760,7 @@ private async void OnDownloadLatest(object sender, RoutedEventArgs args) => awai
725760
using var downloadClient = OfficialImageDownloader.CreateClient();
726761
BeginDownloadText(p => FormatDownload(p, $"{media.Language} · "));
727762
var download = await new OfficialImageDownloader(downloadClient).DownloadAsync(media.Url.AbsoluteUri,
728-
path, media.Sha256, downloadUpdates, token);
763+
path, media.Sha256, downloadUpdates, token, status: catalogProgress);
729764
DrainDownload();
730765
await File.WriteAllTextAsync(path + ".sha256", download.Sha256, token);
731766
DownloadStatus.Text = $"Downloaded {media.Description}. SHA-256: {download.Sha256}" +
@@ -741,6 +776,7 @@ private async Task EnsureToolsAsync(CancellationToken token)
741776
{
742777
servicingTool = ToolLocator.FindDism();
743778
using var client = OfficialImageDownloader.CreateClient();
779+
client.Timeout = TimeSpan.FromSeconds(60);
744780
isoTool = await new OscdimgAcquirer(client).EnsureAsync(Path.Combine(dataDirectory, "tools"),
745781
new UiProgress<string>(text => DispatcherQueue.TryEnqueue(() => ToolStatus.Text = text)), token);
746782
ToolStatus.Text = "Image and ISO tools ready.";
@@ -842,7 +878,12 @@ await RunOperation(OperationKind.Inventory, async token =>
842878
ExpertConsent.IsChecked = true;
843879
FilterInventory();
844880
UpdateCounts();
845-
ShowNotice("Component list loaded.", "Existing selections were preserved. Check the individual items you want to remove or disable.");
881+
if (!ToolLocator.SupportsServicing(recipe.DismPath, image!))
882+
ShowNotice("Component list loaded read-only.", "The installed tools could list this image, but building it requires newer " +
883+
"Windows ADK Deployment Tools. Install them and reopen the app before building.",
884+
InfoBarSeverity.Warning, NoticeAction.OpenTools);
885+
else
886+
ShowNotice("Component list loaded.", "Existing selections were preserved. Check the individual items you want to remove or disable.");
846887
});
847888
}
848889

@@ -1008,7 +1049,7 @@ private void ShowBuildResult(BuildResult built, bool resumed, bool turbo)
10081049
var warnings = built.Warnings;
10091050
// A cancel prompt open for this run reports the warning too, instead of calling the end clean.
10101051
if (warnings.Count > 0)
1011-
active?.NoteWarning("The ISO was saved, but some follow-up steps reported problems. See the warnings on the page.");
1052+
active?.NoteWarning("The ISO was saved with warnings. See the warnings on the page.");
10121053
ResultWarnings.IsOpen = warnings.Count > 0;
10131054
if (warnings.Count > 0)
10141055
ResultWarnings.Message = string.Join("\n", warnings.Take(4).Select(w => "• " + ErrorText.Summary(w, 200))) +
@@ -1028,7 +1069,7 @@ private void ShowBuildResult(BuildResult built, bool resumed, bool turbo)
10281069
BuildBar.IsIndeterminate = false;
10291070
BuildBar.Value = 100;
10301071
CurrentMessage.Text = warnings.Count > 0 ?
1031-
"The ISO was saved, but some follow-up steps reported problems. See the warnings below." :
1072+
"The ISO was saved with warnings. See the skipped work and other details below." :
10321073
"The ISO and build report have been saved. Test the ISO in a VM before use.";
10331074
stoppedRun = null;
10341075
stoppedHasCheckpoint = false;

‎src/Tiny11Builder.App/OperationFlow.cs‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,22 @@ internal sealed record OperationOutcome(OperationKind Kind, bool Cancelled, stri
2424

2525
internal static class OperationOutcomes
2626
{
27+
public static DownloadServiceException? DownloadServiceProblem(Exception error) => error switch
28+
{
29+
DownloadServiceException download => download,
30+
AggregateException { InnerExceptions.Count: > 0 } aggregate => DownloadServiceProblem(aggregate.InnerExceptions[0]),
31+
BuildFailureException { InnerException: { } inner } => DownloadServiceProblem(inner),
32+
_ => null
33+
};
34+
35+
public static bool RequiresServicingTools(Exception error) => error switch
36+
{
37+
ServicingToolsRequiredException => true,
38+
AggregateException { InnerExceptions.Count: > 0 } aggregate => RequiresServicingTools(aggregate.InnerExceptions[0]),
39+
BuildFailureException { InnerException: { } inner } => RequiresServicingTools(inner),
40+
_ => false
41+
};
42+
2743
// The Win32 code for "the user declined the elevation prompt".
2844
private const int UserDeclinedElevation = 1223;
2945

‎src/Tiny11Builder.App/Tiny11Builder.App.csproj‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<TargetFramework>net8.0-windows10.0.26100.0</TargetFramework>
55
<TargetPlatformMinVersion>10.0.19041.0</TargetPlatformMinVersion>
66
<AssemblyName>Tiny11Builder</AssemblyName>
7-
<Version>0.1.1</Version>
7+
<Version>0.1.2</Version>
88
<RootNamespace>Tiny11Builder.App</RootNamespace>
99
<ApplicationManifest>app.manifest</ApplicationManifest>
1010
<Platforms>x64;ARM64</Platforms>

0 commit comments

Comments
 (0)