diff --git a/.gitignore b/.gitignore
index 96f9b072..878e17cc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -104,3 +104,6 @@ tmpclaude-*-cwd
# Deployment scripts (local only)
scripts/Deployment/build_and_upload_debug_agent_build.ps1
scripts/Deployment/build_and_upload_release_agent_build.ps1
+
+
+.dotnet-home/.dotnet/*
diff --git a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt
index 47eb669b..4dab36bb 100644
--- a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt
+++ b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt
@@ -1 +1 @@
-316
+317
diff --git a/src/Agent/AutopilotMonitor.SummaryDialog/App.xaml.cs b/src/Agent/AutopilotMonitor.SummaryDialog/App.xaml.cs
index be2cc212..5cbe9491 100644
--- a/src/Agent/AutopilotMonitor.SummaryDialog/App.xaml.cs
+++ b/src/Agent/AutopilotMonitor.SummaryDialog/App.xaml.cs
@@ -1,6 +1,7 @@
using System;
using System.Diagnostics;
using System.IO;
+using System.Text;
using System.Windows;
namespace AutopilotMonitor.SummaryDialog
@@ -13,6 +14,8 @@ namespace AutopilotMonitor.SummaryDialog
internal static bool? ForceTheme { get; private set; } // true=dark, false=light, null=auto
private static string _logFile;
+ private static string _tempLogFile; // error log in %TEMP% (survives self-cleanup)
+ private static bool _hadErrors;
public App()
{
@@ -23,21 +26,61 @@ namespace AutopilotMonitor.SummaryDialog
var exeDir = Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().Location) ?? ".";
_logFile = Path.Combine(exeDir, "SummaryDialog.log");
+
+ // Error log in user's temp folder — survives self-cleanup for troubleshooting
+ try { _tempLogFile = Path.Combine(Path.GetTempPath(), "SummaryDialog-error.log"); }
+ catch { /* temp path unavailable */ }
+
Log("App constructor — process started");
+ LogDiagnostics();
+ }
+ catch (Exception ex)
+ {
+ LogError($"App constructor failed: {ex}");
}
- catch { }
AppDomain.CurrentDomain.UnhandledException += (s, e) =>
{
- Log($"UNHANDLED EXCEPTION: {e.ExceptionObject}");
+ LogError($"UNHANDLED EXCEPTION: {e.ExceptionObject}");
};
DispatcherUnhandledException += (s, e) =>
{
- Log($"DISPATCHER EXCEPTION: {e.Exception}");
+ LogError($"DISPATCHER EXCEPTION: {e.Exception}");
+ e.Handled = true; // prevent uncontrolled crash, ensure OnExit runs
+ try { Shutdown(1); } catch { }
};
}
+ private void LogDiagnostics()
+ {
+ try
+ {
+ var proc = Process.GetCurrentProcess();
+ Log($"PID={proc.Id} SessionId={proc.SessionId}");
+
+ // Session 0 = SYSTEM session — window will NOT be visible to the user
+ if (proc.SessionId == 0)
+ LogError("CRITICAL: Process is in Session 0 (SYSTEM) — window will NOT be visible!");
+
+ Log($"User: {Environment.UserDomainName}\\{Environment.UserName}");
+ Log($"CommandLine: {Environment.CommandLine}");
+ Log($"ExeDir: {Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)}");
+ Log($"Temp: {Path.GetTempPath()}");
+ Log($"Is64Bit: {Environment.Is64BitProcess} OS: {Environment.OSVersion}");
+
+ // Check if critical dependency exists
+ var exeDir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
+ var jsonDll = Path.Combine(exeDir ?? ".", "Newtonsoft.Json.dll");
+ if (!File.Exists(jsonDll))
+ LogError($"Newtonsoft.Json.dll NOT FOUND at {jsonDll}");
+ }
+ catch (Exception ex)
+ {
+ LogError($"LogDiagnostics failed: {ex.Message}");
+ }
+ }
+
protected override void OnStartup(StartupEventArgs e)
{
Log($"OnStartup — args: {string.Join(" ", e.Args)}");
@@ -49,6 +92,7 @@ namespace AutopilotMonitor.SummaryDialog
{
Log("OnExit — shutting down");
base.OnExit(e);
+ CleanupTempErrorLog();
SelfCleanup();
}
@@ -84,8 +128,9 @@ namespace AutopilotMonitor.SummaryDialog
}
///
- /// Self-cleanup: delete the temp folder this EXE was launched from.
- /// Uses a cmd.exe script that waits for our process to exit, then deletes the folder.
+ /// Self-cleanup: delete the staging folder this EXE was launched from.
+ /// Uses PowerShell Wait-Process to cleanly wait for our process to exit,
+ /// then deletes the folder. No polling loops or race conditions.
///
private void SelfCleanup()
{
@@ -110,25 +155,46 @@ namespace AutopilotMonitor.SummaryDialog
return;
var pid = Process.GetCurrentProcess().Id;
- // Wait for our process to exit, then delete the temp folder
- var script = $@"/c tasklist /fi ""PID eq {pid}"" 2>nul | find ""{pid}"" >nul && (ping 127.0.0.1 -n 3 >nul) & rd /s /q ""{exeDir}""";
+
+ // PowerShell Wait-Process: waits for our process to actually exit (no polling),
+ // then deletes the staging folder. 60s timeout as safety net.
+ var psScript = $"Wait-Process -Id {pid} -Timeout 60 -ErrorAction SilentlyContinue; " +
+ $"Start-Sleep -Seconds 1; " +
+ $"Remove-Item -LiteralPath '{exeDir}' -Recurse -Force -ErrorAction SilentlyContinue";
+ var encoded = Convert.ToBase64String(Encoding.Unicode.GetBytes(psScript));
+
+ Log($"SelfCleanup: spawning PowerShell to delete '{exeDir}' after PID {pid} exits");
var psi = new ProcessStartInfo
{
- FileName = "cmd.exe",
- Arguments = script,
+ FileName = "powershell.exe",
+ Arguments = $"-NoProfile -NonInteractive -EncodedCommand {encoded}",
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
UseShellExecute = false
};
Process.Start(psi);
}
- catch
+ catch (Exception ex)
{
+ Log($"SelfCleanup failed: {ex.Message}");
// Best-effort cleanup, ignore errors
}
}
+ ///
+ /// Delete the temp error log if no errors occurred (no traces on success).
+ ///
+ private void CleanupTempErrorLog()
+ {
+ try
+ {
+ if (!_hadErrors && _tempLogFile != null && File.Exists(_tempLogFile))
+ File.Delete(_tempLogFile);
+ }
+ catch { }
+ }
+
internal static void Log(string message)
{
try
@@ -139,5 +205,22 @@ namespace AutopilotMonitor.SummaryDialog
}
catch { }
}
+
+ ///
+ /// Log an error to both the main log and the temp error log.
+ /// The temp error log survives self-cleanup for post-mortem troubleshooting.
+ ///
+ internal static void LogError(string message)
+ {
+ _hadErrors = true;
+ Log(message);
+ try
+ {
+ if (_tempLogFile != null)
+ File.AppendAllText(_tempLogFile,
+ $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {message}{Environment.NewLine}");
+ }
+ catch { }
+ }
}
}
diff --git a/src/Agent/AutopilotMonitor.SummaryDialog/MainWindow.xaml.cs b/src/Agent/AutopilotMonitor.SummaryDialog/MainWindow.xaml.cs
index 5abd109d..7692a579 100644
--- a/src/Agent/AutopilotMonitor.SummaryDialog/MainWindow.xaml.cs
+++ b/src/Agent/AutopilotMonitor.SummaryDialog/MainWindow.xaml.cs
@@ -26,28 +26,41 @@ namespace AutopilotMonitor.SummaryDialog
public MainWindow()
{
+ App.Log("MainWindow constructor — InitializeComponent");
InitializeComponent();
+ App.Log("MainWindow constructor — done");
}
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
- _isDarkTheme = App.ForceTheme ?? DetectDarkTheme();
- if (_isDarkTheme)
- ApplyDarkTheme();
-
- ApplyScrollBarStyle();
- ApplyContentClip();
- ApplyTaskbarIcon();
-
- LoadStatusData();
- RenderOutcome();
- RenderAppList();
- StartCountdown();
-
- // Load branding image (async, best-effort)
- if (!string.IsNullOrEmpty(App.BrandingImageUrl))
+ App.Log("Window_Loaded — start");
+ try
{
- await LoadBrandingImageAsync();
+ _isDarkTheme = App.ForceTheme ?? DetectDarkTheme();
+ App.Log($"Theme: isDark={_isDarkTheme} (forced={App.ForceTheme})");
+ if (_isDarkTheme)
+ ApplyDarkTheme();
+
+ ApplyScrollBarStyle();
+ ApplyContentClip();
+ ApplyTaskbarIcon();
+
+ LoadStatusData();
+ RenderOutcome();
+ RenderAppList();
+ StartCountdown();
+
+ // Load branding image (async, best-effort)
+ if (!string.IsNullOrEmpty(App.BrandingImageUrl))
+ {
+ await LoadBrandingImageAsync();
+ }
+
+ App.Log("Window_Loaded — complete, window should be visible");
+ }
+ catch (Exception ex)
+ {
+ App.LogError($"Window_Loaded FAILED: {ex}");
}
}
@@ -59,10 +72,16 @@ namespace AutopilotMonitor.SummaryDialog
{
var json = File.ReadAllText(App.StatusFilePath);
_status = JsonConvert.DeserializeObject(json);
+ App.Log($"LoadStatusData: outcome={_status?.Outcome} apps={_status?.AppSummary?.TotalApps}");
+ }
+ else
+ {
+ App.Log($"LoadStatusData: status file missing or empty (path={App.StatusFilePath})");
}
}
- catch
+ catch (Exception ex)
{
+ App.LogError($"LoadStatusData FAILED: {ex.Message}");
_status = null;
}
}
@@ -277,9 +296,9 @@ namespace AutopilotMonitor.SummaryDialog
BrandingBanner.Visibility = Visibility.Visible;
}
}
- catch
+ catch (Exception ex)
{
- // Best-effort: skip banner on any error
+ App.Log($"LoadBrandingImage: skipped ({ex.Message})");
BrandingBanner.Visibility = Visibility.Collapsed;
}
}
@@ -479,11 +498,14 @@ namespace AutopilotMonitor.SummaryDialog
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize"))
{
var value = key?.GetValue("AppsUseLightTheme");
- return value is int i && i == 0;
+ var isDark = value is int i && i == 0;
+ App.Log($"DetectDarkTheme: AppsUseLightTheme={value} → isDark={isDark}");
+ return isDark;
}
}
- catch
+ catch (Exception ex)
{
+ App.Log($"DetectDarkTheme: registry read failed ({ex.Message}), defaulting to light");
return false;
}
}