mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-23 20:26:10 +00:00
fixes for Summary Dialog
This commit is contained in:
@@ -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/*
|
||||
|
||||
@@ -1 +1 @@
|
||||
316
|
||||
317
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete the temp error log if no errors occurred (no traces on success).
|
||||
/// </summary>
|
||||
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 { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log an error to both the main log and the temp error log.
|
||||
/// The temp error log survives self-cleanup for post-mortem troubleshooting.
|
||||
/// </summary>
|
||||
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 { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<FinalStatus>(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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user