From d8499215fbccfee6ef87c7dc27f1550dbc50bbdb Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Thu, 12 Feb 2026 16:45:05 +0100 Subject: [PATCH] added process guard added enrollment complete marker --- .../Monitoring/Core/MonitoringService.cs | 22 ++++ .../Monitoring/Tracking/EnrollmentTracker.cs | 29 +++++ src/Agent/AutopilotMonitor.Agent/Program.cs | 112 ++++++++++++++++++ 3 files changed, 163 insertions(+) diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/MonitoringService.cs b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/MonitoringService.cs index 95ba6138..32aeb973 100644 --- a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/MonitoringService.cs +++ b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Core/MonitoringService.cs @@ -474,6 +474,28 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core _completionEvent.Wait(); } + /// + /// Triggers cleanup without running enrollment monitoring. + /// Used when enrollment complete marker is detected on startup (cleanup retry). + /// + public void TriggerCleanup() + { + _logger.Info("TriggerCleanup invoked - executing cleanup without enrollment monitoring"); + + if (_configuration.SelfDestructOnComplete) + { + ExecuteSelfDestruct(); + } + else if (_configuration.CleanupOnExit) + { + ExecuteCleanup(); + } + else + { + _logger.Info("No cleanup configured - nothing to do"); + } + } + /// /// Stops all event collection components /// diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentTracker.cs b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentTracker.cs index 8db53a60..fd0dd2b4 100644 --- a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentTracker.cs +++ b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentTracker.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Management; using System.Threading; @@ -823,6 +824,9 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking Message = "Autopilot enrollment completed successfully (user session completed)" }); + // Write enrollment complete marker for cleanup retry detection + WriteEnrollmentCompleteMarker(); + // Collect final device info (BitLocker may have been enabled during enrollment) CollectDeviceInfoAtEnd(); @@ -855,6 +859,9 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking Message = "Autopilot enrollment completed successfully (Hello provisioning completed)" }); + // Write enrollment complete marker for cleanup retry detection + WriteEnrollmentCompleteMarker(); + // Clean up persisted tracker state so next enrollment starts fresh _imeLogTracker?.DeleteState(); } @@ -904,5 +911,27 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking _summaryTimer?.Dispose(); _imeLogTracker?.Dispose(); } + + /// + /// Writes an enrollment complete marker to the state directory. + /// This marker is checked on agent restart to handle cleanup retry if scheduled task fails. + /// + private void WriteEnrollmentCompleteMarker() + { + try + { + var stateDirectory = Environment.ExpandEnvironmentVariables(@"%ProgramData%\AutopilotMonitor\State"); + Directory.CreateDirectory(stateDirectory); + + var markerPath = Path.Combine(stateDirectory, "enrollment-complete.marker"); + File.WriteAllText(markerPath, $"Enrollment completed at {DateTime.UtcNow:O}"); + + _logger.Info($"Enrollment complete marker written: {markerPath}"); + } + catch (Exception ex) + { + _logger.Warning($"Failed to write enrollment complete marker: {ex.Message}"); + } + } } } diff --git a/src/Agent/AutopilotMonitor.Agent/Program.cs b/src/Agent/AutopilotMonitor.Agent/Program.cs index 28f6244c..45fd8079 100644 --- a/src/Agent/AutopilotMonitor.Agent/Program.cs +++ b/src/Agent/AutopilotMonitor.Agent/Program.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using AutopilotMonitor.Agent.Core.Configuration; @@ -31,6 +32,26 @@ namespace AutopilotMonitor.Agent { var consoleMode = args.Contains("--console") || Environment.UserInteractive; + // Process guard: prevent multiple agent instances from running + if (IsAnotherAgentInstanceRunning()) + { + var message = "Another agent process is already running. This instance will exit."; + if (consoleMode) + Console.WriteLine($"ERROR: {message}"); + + // Try to log if possible, but this might fail if logger isn't initialized yet + try + { + var logDir = Environment.ExpandEnvironmentVariables(@"%ProgramData%\AutopilotMonitor\Logs"); + var logger = new AgentLogger(logDir, enableDebug: false); + logger.Warning(message); + } + catch { } + + Environment.Exit(1); + return; + } + if (consoleMode) { Console.WriteLine("Autopilot Monitor Agent"); @@ -49,6 +70,13 @@ namespace AutopilotMonitor.Agent logger.Info($"============================== Agent starting ({(consoleMode ? "console" : "background/SYSTEM")}) =============================="); logger.Info("============================================================================================================"); + // Check for enrollment complete marker (handles scheduled task cleanup retry) + if (CheckEnrollmentCompleteMarker(config, logger, consoleMode)) + { + // Marker was found and handled (cleanup executed or skipped) - exit + return; + } + if (consoleMode) { Console.WriteLine($"Session ID: {config.SessionId}"); @@ -399,5 +427,89 @@ namespace AutopilotMonitor.Agent KeepLogFile = keepLogFile }; } + + /// + /// Checks if another AutopilotMonitor.Agent.exe process is already running. + /// + static bool IsAnotherAgentInstanceRunning() + { + try + { + var currentProcess = Process.GetCurrentProcess(); + var processes = Process.GetProcessesByName(currentProcess.ProcessName); + + // If there's more than one process with our name, another instance is running + return processes.Length > 1; + } + catch + { + // If we can't check, assume no conflict + return false; + } + } + + /// + /// Checks for enrollment complete marker and handles cleanup retry if needed. + /// Returns true if marker was found and agent should exit. + /// + static bool CheckEnrollmentCompleteMarker(AgentConfiguration config, AgentLogger logger, bool consoleMode) + { + var stateDirectory = Environment.ExpandEnvironmentVariables(@"%ProgramData%\AutopilotMonitor\State"); + var markerPath = Path.Combine(stateDirectory, "enrollment-complete.marker"); + + if (!File.Exists(markerPath)) + { + // No marker - proceed with normal enrollment + return false; + } + + logger.Info("Enrollment complete marker detected from previous session"); + + if (!config.CleanupOnExit && !config.SelfDestructOnComplete) + { + // No cleanup configured - just exit + logger.Info("Enrollment already completed (no cleanup is configured). Agent will exit."); + if (consoleMode) + Console.WriteLine("Enrollment already completed (no cleanup is configured). Agent will exit."); + return true; + } + + // Cleanup is configured - attempt it now in case scheduled task failed + logger.Info("Enrollment already completed. Attempting cleanup retry (scheduled task may have failed)..."); + if (consoleMode) + Console.WriteLine("Enrollment already completed. Attempting cleanup retry..."); + + try + { + using (var service = new MonitoringService(config, logger)) + { + // Trigger cleanup directly without running enrollment + if (config.SelfDestructOnComplete) + { + logger.Info("Executing self-destruct cleanup..."); + // Call the internal cleanup method via reflection or expose it + // For now, we'll create a minimal service and let it clean up on dispose + service.TriggerCleanup(); + } + else if (config.CleanupOnExit) + { + logger.Info("Executing standard cleanup..."); + service.TriggerCleanup(); + } + } + + logger.Info("Cleanup retry completed. Agent will exit."); + if (consoleMode) + Console.WriteLine("Cleanup retry completed. Agent will exit."); + } + catch (Exception ex) + { + logger.Warning($"Cleanup retry failed: {ex.Message}"); + if (consoleMode) + Console.WriteLine($"WARNING: Cleanup retry failed: {ex.Message}"); + } + + return true; + } } }