From 159cea001905bbca1c4158962eb6f259ba0bd478 Mon Sep 17 00:00:00 2001 From: sysadmin Date: Tue, 9 Jun 2026 18:09:24 +0100 Subject: [PATCH 1/2] fix(welcome): harden kiosk chrome + add startup/WebView2 diagnostics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 4th VM e2e: kiosk shell engages + the app launches fullscreen, but the Blazor wizard renders BLANK and the kiosk chrome didn't apply (title bar present) — the app didn't crash, so there's no log to read. Two changes: 1) ApplyKioskChrome made defensive (null-guard HWND/AppWindow, FullScreen presenter only, returns bool) and wrapped in try/catch at the call site, so a chrome failure can never stall app/WebView startup (the likely cause of the blank). 2) Always-on file log at C:\ProgramData\SilverMetal\welcome.log: app ctor, window create, chrome result, unhandled exceptions, and the BlazorWebView/WebView2 lifecycle (Initialized, NavigationCompleted, ProcessFailed). If the wizard is still blank next run, this pinpoints whether WebView2 env creation failed. Co-Authored-By: Claude Opus 4.8 --- .../src/SilverOS.Welcome.App/App.xaml.cs | 26 +++++++++++++++--- .../src/SilverOS.Welcome.App/Diagnostics.cs | 24 +++++++++++++++++ .../src/SilverOS.Welcome.App/MainPage.xaml | 6 +++-- .../src/SilverOS.Welcome.App/MainPage.xaml.cs | 27 ++++++++++++++++++- .../Platforms/Windows/WindowExtensions.cs | 17 ++++++------ 5 files changed, 86 insertions(+), 14 deletions(-) create mode 100644 windows/welcome/src/SilverOS.Welcome.App/Diagnostics.cs diff --git a/windows/welcome/src/SilverOS.Welcome.App/App.xaml.cs b/windows/welcome/src/SilverOS.Welcome.App/App.xaml.cs index 1991936..0b313e2 100644 --- a/windows/welcome/src/SilverOS.Welcome.App/App.xaml.cs +++ b/windows/welcome/src/SilverOS.Welcome.App/App.xaml.cs @@ -1,20 +1,40 @@ -namespace SilverOS.Welcome.App; +namespace SilverOS.Welcome.App; public partial class App : Application { public App() { + Diag.Log("App ctor"); + AppDomain.CurrentDomain.UnhandledException += (s, e) => + Diag.Log("UNHANDLED: " + (e.ExceptionObject as Exception)?.ToString()); + TaskScheduler.UnobservedTaskException += (s, e) => + { + Diag.Log("UNOBSERVED TASK: " + e.Exception); + e.SetObserved(); + }; InitializeComponent(); } protected override Window CreateWindow(IActivationState? activationState) { + Diag.Log("CreateWindow"); var window = new Window(new MainPage()) { Title = "SilverMetal Windows" }; #if WINDOWS + // Apply kiosk chrome once the native window handler exists. Wrapped so a + // chrome failure can NEVER break app/WebView startup (a previous version + // threw here and left the wizard blank). HandlerChanged can fire before the + // HWND is ready, so ApplyKioskChrome null-guards and re-applies safely. window.HandlerChanged += (s, e) => { - if (window.Handler?.PlatformView is Microsoft.UI.Xaml.Window native) - native.ApplyKioskChrome(); + try + { + if (window.Handler?.PlatformView is Microsoft.UI.Xaml.Window native) + { + var ok = native.ApplyKioskChrome(); + Diag.Log($"ApplyKioskChrome applied={ok}"); + } + } + catch (Exception ex) { Diag.Log("ApplyKioskChrome FAILED: " + ex); } }; #endif return window; diff --git a/windows/welcome/src/SilverOS.Welcome.App/Diagnostics.cs b/windows/welcome/src/SilverOS.Welcome.App/Diagnostics.cs new file mode 100644 index 0000000..4fbfc65 --- /dev/null +++ b/windows/welcome/src/SilverOS.Welcome.App/Diagnostics.cs @@ -0,0 +1,24 @@ +namespace SilverOS.Welcome.App; + +// Lightweight always-on file logger for first-boot diagnosis. The Welcome app +// runs as the kiosk shell with no console and (in Release) no debugger, so when +// something fails silently (blank WebView, chrome not applied) there is nowhere +// to look. This writes to a world-writable ProgramData path that survives the +// session and can be read off the disk image. +public static class Diag +{ + const string Dir = @"C:\ProgramData\SilverMetal"; + const string Path = Dir + @"\welcome.log"; + static readonly object _lock = new(); + + public static void Log(string msg) + { + try + { + System.IO.Directory.CreateDirectory(Dir); + lock (_lock) + System.IO.File.AppendAllText(Path, $"{DateTime.Now:HH:mm:ss.fff} {msg}{Environment.NewLine}"); + } + catch { /* logging must never throw */ } + } +} diff --git a/windows/welcome/src/SilverOS.Welcome.App/MainPage.xaml b/windows/welcome/src/SilverOS.Welcome.App/MainPage.xaml index 078f543..2343cb6 100644 --- a/windows/welcome/src/SilverOS.Welcome.App/MainPage.xaml +++ b/windows/welcome/src/SilverOS.Welcome.App/MainPage.xaml @@ -1,11 +1,13 @@ - + - + diff --git a/windows/welcome/src/SilverOS.Welcome.App/MainPage.xaml.cs b/windows/welcome/src/SilverOS.Welcome.App/MainPage.xaml.cs index 55e4c7c..c0a0877 100644 --- a/windows/welcome/src/SilverOS.Welcome.App/MainPage.xaml.cs +++ b/windows/welcome/src/SilverOS.Welcome.App/MainPage.xaml.cs @@ -1,9 +1,34 @@ -namespace SilverOS.Welcome.App; +using Microsoft.AspNetCore.Components.WebView; + +namespace SilverOS.Welcome.App; public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); + Diag.Log("MainPage ctor"); } + + // Fires once the platform WebView2 is created. If this never appears in the log, + // WebView2 environment creation failed (the real cause of a blank wizard). + void OnBlazorInitialized(object? sender, BlazorWebViewInitializedEventArgs e) + { + Diag.Log("BlazorWebViewInitialized"); +#if WINDOWS + try + { + var wv = e.WebView; // Microsoft.UI.Xaml.Controls.WebView2 + wv.NavigationCompleted += (a, b) => + Diag.Log($"WV2 NavigationCompleted ok={b.IsSuccess} status={b.WebErrorStatus}"); + if (wv.CoreWebView2 is not null) + wv.CoreWebView2.ProcessFailed += (a, b) => + Diag.Log("WV2 ProcessFailed: " + b.ProcessFailedKind); + } + catch (Exception ex) { Diag.Log("WV2 hook failed: " + ex.Message); } +#endif + } + + void OnUrlLoading(object? sender, UrlLoadingEventArgs e) + => Diag.Log("UrlLoading: " + e.Url); } diff --git a/windows/welcome/src/SilverOS.Welcome.App/Platforms/Windows/WindowExtensions.cs b/windows/welcome/src/SilverOS.Welcome.App/Platforms/Windows/WindowExtensions.cs index 6241ba7..3bbc16a 100644 --- a/windows/welcome/src/SilverOS.Welcome.App/Platforms/Windows/WindowExtensions.cs +++ b/windows/welcome/src/SilverOS.Welcome.App/Platforms/Windows/WindowExtensions.cs @@ -7,20 +7,21 @@ namespace SilverOS.Welcome.App; public static class WindowExtensions { - // Borderless, fullscreen, non-closable kiosk window. - public static void ApplyKioskChrome(this Microsoft.UI.Xaml.Window winuiWindow) + // Borderless, fullscreen, non-closable kiosk window. Returns true when applied. + // Defensive: HandlerChanged can fire before the HWND/AppWindow is ready, so we + // bail out cleanly and rely on a later HandlerChanged to apply it. + public static bool ApplyKioskChrome(this Microsoft.UI.Xaml.Window winuiWindow) { var hwnd = WindowNative.GetWindowHandle(winuiWindow); + if (hwnd == IntPtr.Zero) return false; var id = Win32Interop.GetWindowIdFromWindow(hwnd); var appWindow = AppWindow.GetFromWindowId(id); - if (appWindow.Presenter is OverlappedPresenter p) - { - p.SetBorderAndTitleBar(false, false); - p.IsResizable = false; p.IsMaximizable = false; p.IsMinimizable = false; - } + if (appWindow is null) return false; + // FullScreen presenter is borderless (no title bar) by nature — simpler and + // more reliable than toggling OverlappedPresenter border/title-bar flags. appWindow.SetPresenter(AppWindowPresenterKind.FullScreen); - // Block the close box; the wizard exits by rebooting, not by closing. appWindow.Closing += (s, e) => e.Cancel = true; + return true; } } #endif -- 2.39.5 From d54a5cb8db5c554d17fa686734978ffd701def7e Mon Sep 17 00:00:00 2001 From: sysadmin Date: Tue, 9 Jun 2026 18:12:57 +0100 Subject: [PATCH 2/2] fix(kiosk): re-assert UAC auto-approve online (OOBE resets the offline bake) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 4th e2e showed a UAC consent prompt for the unsigned Welcome app — the offline-baked ConsentPromptBehaviorAdmin=0 is reset by Windows during OOBE. Re-assert it (and PromptOnSecureDesktop=0) ONLINE in Configure-Kiosk.ps1, which runs right before the sm-bootstrap autologon, so 'Start-Process -Verb RunAs' elevates silently. RevertKioskAsync restores SECURE UAC (ConsentPromptBehaviorAdmin=2, PromptOnSecureDesktop=1) for the real user. Co-Authored-By: Claude Opus 4.8 --- windows/installer/oem/Configure-Kiosk.ps1 | 10 +++++++++- .../SilverOS.Welcome.Core/Apply/BootstrapService.cs | 5 ++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/windows/installer/oem/Configure-Kiosk.ps1 b/windows/installer/oem/Configure-Kiosk.ps1 index cc2d360..a22ec38 100644 --- a/windows/installer/oem/Configure-Kiosk.ps1 +++ b/windows/installer/oem/Configure-Kiosk.ps1 @@ -77,4 +77,12 @@ New-Item $sys -Force | Out-Null Set-ItemProperty $sys -Name DisableTaskMgr -Value 1 -Type DWord Set-ItemProperty $sys -Name DisableLockWorkstation -Value 1 -Type DWord Set-ItemProperty $sys -Name HideFastUserSwitching -Value 1 -Type DWord -Log 'escape policies set; kiosk ready' + +# Silent elevation for the sm-bootstrap launcher's 'Start-Process -Verb RunAs': +# the offline-baked UAC auto-approve (build.ps1) is RESET by Windows during OOBE, +# so re-assert it online here (runs before the autologon shell). Otherwise the +# kiosk shows a UAC consent prompt for the (unsigned) Welcome app. Reverted at +# teardown so the real end-user keeps normal UAC. +Set-ItemProperty $sys -Name ConsentPromptBehaviorAdmin -Value 0 -Type DWord +Set-ItemProperty $sys -Name PromptOnSecureDesktop -Value 0 -Type DWord +Log 'escape policies + UAC auto-approve set; kiosk ready' diff --git a/windows/welcome/src/SilverOS.Welcome.Core/Apply/BootstrapService.cs b/windows/welcome/src/SilverOS.Welcome.Core/Apply/BootstrapService.cs index e0fca60..6f63f33 100644 --- a/windows/welcome/src/SilverOS.Welcome.Core/Apply/BootstrapService.cs +++ b/windows/welcome/src/SilverOS.Welcome.Core/Apply/BootstrapService.cs @@ -19,7 +19,10 @@ public sealed class BootstrapService(IProcessRunner runner) : IBootstrapService // Revert escape policies set by Configure-Kiosk.ps1. await Ps( "$s='HKLM:\\\\SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Policies\\\\System';" + - "Remove-ItemProperty $s -Name DisableTaskMgr,DisableLockWorkstation,HideFastUserSwitching -EA SilentlyContinue", + "Remove-ItemProperty $s -Name DisableTaskMgr,DisableLockWorkstation,HideFastUserSwitching -EA SilentlyContinue;" + + // Restore SECURE UAC for the real end-user (the kiosk auto-approved unsigned elevation). + "Set-ItemProperty $s -Name ConsentPromptBehaviorAdmin -Value 2 -Type DWord -EA SilentlyContinue;" + + "Set-ItemProperty $s -Name PromptOnSecureDesktop -Value 1 -Type DWord -EA SilentlyContinue", ct); } -- 2.39.5