@@ -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 ;
0 commit comments