diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/MonitoringService.EventUpload.cs b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/MonitoringService.EventUpload.cs
index 5b93e57f..85e78b91 100644
--- a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/MonitoringService.EventUpload.cs
+++ b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/MonitoringService.EventUpload.cs
@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Security.AccessControl;
+using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using AutopilotMonitor.Agent.Core.Configuration;
@@ -608,15 +610,20 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
return;
}
- // Create temp folder for the dialog (survives agent self-destruct).
- // Use ProgramData instead of Path.GetTempPath() because the agent runs as SYSTEM
- // and SYSTEM's temp (C:\WINDOWS\SystemTemp) is not accessible to the user session.
+ // Create temp folder for the dialog OUTSIDE the AutopilotMonitor folder.
+ // The agent's self-destruct deletes ProgramData\AutopilotMonitor entirely,
+ // so the dialog files must live elsewhere to survive.
var tempDir = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
- "AutopilotMonitor",
- "Summary-" + Guid.NewGuid().ToString("N").Substring(0, 8));
+ "AutopilotMonitor-Summary");
+
+ // Clean up any leftover folder from a previous run, then create fresh
+ try { if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } catch { }
Directory.CreateDirectory(tempDir);
+ // Grant the interactive user delete permissions so the dialog can self-cleanup
+ GrantUserDeletePermission(tempDir);
+
// Copy dialog EXE + Newtonsoft.Json.dll + final-status.json to temp
var tempDialogExe = Path.Combine(tempDir, "AutopilotMonitor.SummaryDialog.exe");
File.Copy(dialogExe, tempDialogExe);
@@ -625,6 +632,10 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
if (File.Exists(jsonDll))
File.Copy(jsonDll, Path.Combine(tempDir, "Newtonsoft.Json.dll"));
+ var exeConfig = dialogExe + ".config";
+ if (File.Exists(exeConfig))
+ File.Copy(exeConfig, tempDialogExe + ".config");
+
var tempStatusFile = Path.Combine(tempDir, "final-status.json");
File.Copy(statusFile, tempStatusFile);
@@ -667,6 +678,31 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
}
}
+ ///
+ /// Grants the built-in Users group full control on a directory so the user-session
+ /// SummaryDialog process can delete it during self-cleanup.
+ ///
+ private void GrantUserDeletePermission(string directoryPath)
+ {
+ try
+ {
+ var dirInfo = new DirectoryInfo(directoryPath);
+ var security = dirInfo.GetAccessControl();
+ var usersIdentity = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
+ security.AddAccessRule(new FileSystemAccessRule(
+ usersIdentity,
+ FileSystemRights.FullControl,
+ InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
+ PropagationFlags.None,
+ AccessControlType.Allow));
+ dirInfo.SetAccessControl(security);
+ }
+ catch (Exception ex)
+ {
+ _logger.Debug($"Could not set ACL on summary folder: {ex.Message}");
+ }
+ }
+
public void Dispose()
{
_uploadTimer?.Dispose();
diff --git a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt
index cd307095..33a21f83 100644
--- a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt
+++ b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt
@@ -1 +1 @@
-306
+308
diff --git a/src/Agent/AutopilotMonitor.SummaryDialog/App.xaml.cs b/src/Agent/AutopilotMonitor.SummaryDialog/App.xaml.cs
index 73edefa3..be2cc212 100644
--- a/src/Agent/AutopilotMonitor.SummaryDialog/App.xaml.cs
+++ b/src/Agent/AutopilotMonitor.SummaryDialog/App.xaml.cs
@@ -12,14 +12,42 @@ namespace AutopilotMonitor.SummaryDialog
internal static string BrandingImageUrl { get; private set; }
internal static bool? ForceTheme { get; private set; } // true=dark, false=light, null=auto
+ private static string _logFile;
+
+ public App()
+ {
+ // Set up crash logging as early as possible to diagnose launch failures
+ // (e.g. when launched via CreateProcessAsUser from SYSTEM service)
+ try
+ {
+ var exeDir = Path.GetDirectoryName(
+ System.Reflection.Assembly.GetExecutingAssembly().Location) ?? ".";
+ _logFile = Path.Combine(exeDir, "SummaryDialog.log");
+ Log("App constructor — process started");
+ }
+ catch { }
+
+ AppDomain.CurrentDomain.UnhandledException += (s, e) =>
+ {
+ Log($"UNHANDLED EXCEPTION: {e.ExceptionObject}");
+ };
+
+ DispatcherUnhandledException += (s, e) =>
+ {
+ Log($"DISPATCHER EXCEPTION: {e.Exception}");
+ };
+ }
+
protected override void OnStartup(StartupEventArgs e)
{
+ Log($"OnStartup — args: {string.Join(" ", e.Args)}");
base.OnStartup(e);
ParseArguments(e.Args);
}
protected override void OnExit(ExitEventArgs e)
{
+ Log("OnExit — shutting down");
base.OnExit(e);
SelfCleanup();
}
@@ -67,15 +95,16 @@ namespace AutopilotMonitor.SummaryDialog
var exeDir = Path.GetDirectoryName(exePath);
// Only self-cleanup if running from a known staging folder
- // (agent copies the dialog to ProgramData\AutopilotMonitor\Summary-* or legacy TEMP)
if (exeDir == null)
return;
var programData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
- var agentStagingRoot = Path.Combine(programData, "AutopilotMonitor");
+ var summaryStaging = Path.Combine(programData, "AutopilotMonitor-Summary");
+ var legacyStaging = Path.Combine(programData, "AutopilotMonitor");
var tempRoot = Path.GetTempPath().TrimEnd('\\');
- var isStagedCopy = exeDir.StartsWith(agentStagingRoot, StringComparison.OrdinalIgnoreCase)
+ var isStagedCopy = exeDir.StartsWith(summaryStaging, StringComparison.OrdinalIgnoreCase)
+ || exeDir.StartsWith(legacyStaging, StringComparison.OrdinalIgnoreCase)
|| exeDir.StartsWith(tempRoot, StringComparison.OrdinalIgnoreCase);
if (!isStagedCopy)
return;
@@ -99,5 +128,16 @@ namespace AutopilotMonitor.SummaryDialog
// Best-effort cleanup, ignore errors
}
}
+
+ internal static void Log(string message)
+ {
+ try
+ {
+ if (_logFile != null)
+ File.AppendAllText(_logFile,
+ $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}] {message}{Environment.NewLine}");
+ }
+ catch { }
+ }
}
}
diff --git a/src/Agent/AutopilotMonitor.SummaryDialog/AutopilotMonitor.SummaryDialog.csproj b/src/Agent/AutopilotMonitor.SummaryDialog/AutopilotMonitor.SummaryDialog.csproj
index f3facfc2..e592db77 100644
--- a/src/Agent/AutopilotMonitor.SummaryDialog/AutopilotMonitor.SummaryDialog.csproj
+++ b/src/Agent/AutopilotMonitor.SummaryDialog/AutopilotMonitor.SummaryDialog.csproj
@@ -7,6 +7,7 @@
true
AutopilotMonitor.SummaryDialog
AutopilotMonitor.SummaryDialog
+ app.ico
diff --git a/src/Agent/AutopilotMonitor.SummaryDialog/app.ico b/src/Agent/AutopilotMonitor.SummaryDialog/app.ico
new file mode 100644
index 00000000..51f34476
Binary files /dev/null and b/src/Agent/AutopilotMonitor.SummaryDialog/app.ico differ