mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
agent bug fixes, trace events, and important IME events emitted now (user_session_completed)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Management;
|
||||
using System.Threading;
|
||||
@@ -17,6 +18,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
|
||||
private readonly AgentLogger _logger;
|
||||
private Timer _pollingTimer;
|
||||
private bool _desktopArrived;
|
||||
private bool _excludedUserTraced; // Emit trace event only once for excluded-user skips
|
||||
private const int PollingIntervalSeconds = 30;
|
||||
|
||||
/// <summary>
|
||||
@@ -38,6 +40,12 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
|
||||
/// </summary>
|
||||
public event EventHandler DesktopArrived;
|
||||
|
||||
/// <summary>
|
||||
/// Optional callback for trace events (decision, reason, context).
|
||||
/// Wired by MonitoringService to emit agent_trace events to the backend.
|
||||
/// </summary>
|
||||
public Action<string, string, Dictionary<string, object>> OnTraceEvent { get; set; }
|
||||
|
||||
public DesktopArrivalDetector(AgentLogger logger)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
@@ -83,6 +91,20 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
|
||||
if (IsExcludedUser(owner))
|
||||
{
|
||||
_logger.Debug($"DesktopArrivalDetector: explorer.exe PID {proc.Id} owned by excluded user '{owner}' — skipping");
|
||||
|
||||
// Trace this decision once so it's visible in the backend
|
||||
if (!_excludedUserTraced)
|
||||
{
|
||||
_excludedUserTraced = true;
|
||||
try
|
||||
{
|
||||
OnTraceEvent?.Invoke(
|
||||
"desktop_excluded_user",
|
||||
$"explorer.exe found but owned by excluded user '{owner}' — not a real user desktop",
|
||||
new Dictionary<string, object> { { "pid", proc.Id }, { "session", proc.SessionId }, { "owner", owner } });
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -90,6 +112,15 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
|
||||
_desktopArrived = true;
|
||||
_logger.Info($"DesktopArrivalDetector: real user desktop detected (explorer.exe PID {proc.Id}, session {proc.SessionId}, user '{owner}')");
|
||||
|
||||
try
|
||||
{
|
||||
OnTraceEvent?.Invoke(
|
||||
"desktop_real_user_detected",
|
||||
$"Real user desktop detected (explorer.exe PID {proc.Id}, user '{owner}')",
|
||||
new Dictionary<string, object> { { "pid", proc.Id }, { "session", proc.SessionId }, { "owner", owner } });
|
||||
}
|
||||
catch { }
|
||||
|
||||
// Stop polling
|
||||
_pollingTimer?.Dispose();
|
||||
_pollingTimer = null;
|
||||
|
||||
+2
@@ -106,6 +106,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
|
||||
// Desktop arrival detector — detects real user desktop for no-ESP completion
|
||||
_desktopArrivalDetector = new DesktopArrivalDetector(_logger);
|
||||
_desktopArrivalDetector.DesktopArrived += OnDesktopArrived;
|
||||
_desktopArrivalDetector.OnTraceEvent = (decision, reason, context) =>
|
||||
EmitTraceEvent("DesktopArrivalDetector", decision, reason, context);
|
||||
_desktopArrivalDetector.Start();
|
||||
|
||||
// Agent max lifetime safety net — prevents zombie agents
|
||||
|
||||
+48
-19
@@ -129,20 +129,10 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
|
||||
}
|
||||
});
|
||||
|
||||
// Trigger self-destruct or reboot if configured
|
||||
if (_configuration.SelfDestructOnComplete || _configuration.RebootOnComplete)
|
||||
{
|
||||
Task.Run(() => HandleEnrollmentComplete(enrollmentSucceeded));
|
||||
return; // Don't continue with normal event processing
|
||||
}
|
||||
|
||||
// No self-destruct/reboot — still upload diagnostics and optionally show summary
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await UploadDiagnosticsWithTimelineEvents(enrollmentSucceeded);
|
||||
if (_configuration.ShowEnrollmentSummary)
|
||||
LaunchEnrollmentSummaryDialog();
|
||||
});
|
||||
// Always run the full completion sequence (analyzers, diagnostics, summary, exit).
|
||||
// HandleEnrollmentComplete conditionally self-destructs or reboots based on config.
|
||||
Task.Run(() => HandleEnrollmentComplete(enrollmentSucceeded));
|
||||
return; // Don't continue with normal event processing
|
||||
}
|
||||
|
||||
// Immediate upload for:
|
||||
@@ -162,6 +152,41 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits a Trace-severity event from any component for agent decision auditing.
|
||||
/// Always logged locally; only sent to the backend when SendTraceEvents is enabled.
|
||||
/// </summary>
|
||||
private void EmitTraceEvent(string source, string decision, string reason, Dictionary<string, object> context = null)
|
||||
{
|
||||
_logger.Info($"[TRACE] {source}: {decision} — {reason}");
|
||||
|
||||
if (!_configuration.SendTraceEvents)
|
||||
return;
|
||||
|
||||
var data = new Dictionary<string, object>
|
||||
{
|
||||
{ "decision", decision },
|
||||
{ "reason", reason }
|
||||
};
|
||||
if (context != null)
|
||||
{
|
||||
foreach (var kvp in context)
|
||||
data[kvp.Key] = kvp.Value;
|
||||
}
|
||||
|
||||
EmitEvent(new EnrollmentEvent
|
||||
{
|
||||
SessionId = _configuration.SessionId,
|
||||
TenantId = _configuration.TenantId,
|
||||
EventType = "agent_trace",
|
||||
Severity = EventSeverity.Trace,
|
||||
Source = source,
|
||||
Phase = EnrollmentPhase.Unknown,
|
||||
Message = $"{decision}: {reason}",
|
||||
Data = data
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when new events are available in the spool (FileSystemWatcher detected new files)
|
||||
/// Uses debouncing to batch events before uploading
|
||||
@@ -424,7 +449,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("===== ENROLLMENT COMPLETE - Starting Self-Destruct Sequence =====");
|
||||
_logger.Info("===== ENROLLMENT COMPLETE - Starting shutdown sequence =====");
|
||||
|
||||
// Step 1: Stop all event collectors
|
||||
_logger.Info("Stopping event collectors...");
|
||||
@@ -487,7 +512,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
|
||||
Process.Start(psi);
|
||||
}
|
||||
|
||||
_logger.Info("Self-destruct sequence initiated. Agent will now exit.");
|
||||
_logger.Info("Shutdown sequence complete. Agent will now exit.");
|
||||
|
||||
// Exit the application
|
||||
Environment.Exit(0);
|
||||
@@ -583,9 +608,13 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
|
||||
return;
|
||||
}
|
||||
|
||||
// Create temp folder for the dialog (survives agent self-destruct)
|
||||
var tempDir = Path.Combine(Path.GetTempPath(),
|
||||
"AutopilotMonitor-Summary-" + Guid.NewGuid().ToString("N").Substring(0, 8));
|
||||
// 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.
|
||||
var tempDir = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
||||
"AutopilotMonitor",
|
||||
"Summary-" + Guid.NewGuid().ToString("N").Substring(0, 8));
|
||||
Directory.CreateDirectory(tempDir);
|
||||
|
||||
// Copy dialog EXE + Newtonsoft.Json.dll + final-status.json to temp
|
||||
|
||||
+28
@@ -432,12 +432,40 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
|
||||
_logger.Info($"EnrollmentTracker: {message}");
|
||||
}
|
||||
|
||||
private void HandleImeSessionChange(string changeType)
|
||||
{
|
||||
_emitEvent(new EnrollmentEvent
|
||||
{
|
||||
SessionId = _sessionId,
|
||||
TenantId = _tenantId,
|
||||
EventType = "ime_session_change",
|
||||
Severity = EventSeverity.Debug,
|
||||
Source = "ImeLogTracker",
|
||||
Phase = EnrollmentPhase.Unknown,
|
||||
Message = $"IME session change: {changeType}",
|
||||
Data = new Dictionary<string, object> { { "changeType", changeType ?? "" } }
|
||||
});
|
||||
}
|
||||
|
||||
private void HandleUserSessionCompleted()
|
||||
{
|
||||
_logger.Info("EnrollmentTracker: User session completed (detected from IME log)");
|
||||
_stateData.ImePatternSeenUtc = DateTime.UtcNow;
|
||||
RecordSignal("ime_pattern");
|
||||
|
||||
// Emit timeline event so admins can see exactly when IME finished its user part
|
||||
EmitImeTrackerEvent(new EnrollmentEvent
|
||||
{
|
||||
SessionId = _sessionId,
|
||||
TenantId = _tenantId,
|
||||
EventType = "ime_user_session_completed",
|
||||
Severity = EventSeverity.Info,
|
||||
Source = "ImeLogTracker",
|
||||
Phase = EnrollmentPhase.Unknown,
|
||||
Message = "IME user session completed",
|
||||
Data = new Dictionary<string, object> { { "detectedAt", DateTime.UtcNow.ToString("o") } }
|
||||
});
|
||||
|
||||
// User session completed successfully — cancel any pending ESP failure
|
||||
CancelPendingEspFailure();
|
||||
|
||||
|
||||
@@ -179,6 +179,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
|
||||
_imeLogTracker.OnPoliciesDiscovered = HandlePoliciesDiscovered;
|
||||
_imeLogTracker.OnAllAppsCompleted = HandleAllAppsCompleted;
|
||||
_imeLogTracker.OnUserSessionCompleted = HandleUserSessionCompleted;
|
||||
_imeLogTracker.OnImeSessionChange = HandleImeSessionChange;
|
||||
_imeLogTracker.OnDoTelemetryReceived = HandleDoTelemetryReceived;
|
||||
_imeLogTracker.OnScriptCompleted = HandleScriptCompleted;
|
||||
|
||||
|
||||
+3
-1
@@ -138,7 +138,9 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
|
||||
break;
|
||||
|
||||
case "imesessionchange":
|
||||
_logger.Debug($"IME session change: {match.Groups["change"]?.Value}");
|
||||
var sessionChange = match.Groups["change"]?.Value;
|
||||
_logger.Debug($"IME session change: {sessionChange}");
|
||||
OnImeSessionChange?.Invoke(sessionChange);
|
||||
break;
|
||||
|
||||
case "espphasedetected":
|
||||
|
||||
@@ -112,6 +112,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
|
||||
public Action<string> OnPoliciesDiscovered { get; set; }
|
||||
public Action OnAllAppsCompleted { get; set; }
|
||||
public Action OnUserSessionCompleted { get; set; }
|
||||
public Action<string> OnImeSessionChange { get; set; }
|
||||
public Action<AppPackageState> OnDoTelemetryReceived { get; set; }
|
||||
public Action<ScriptExecutionState> OnScriptCompleted { get; set; }
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
289
|
||||
292
|
||||
|
||||
@@ -66,9 +66,18 @@ namespace AutopilotMonitor.SummaryDialog
|
||||
var exePath = System.Reflection.Assembly.GetExecutingAssembly().Location;
|
||||
var exeDir = Path.GetDirectoryName(exePath);
|
||||
|
||||
// Only self-cleanup if running from a temp folder
|
||||
// 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 tempRoot = Path.GetTempPath().TrimEnd('\\');
|
||||
if (exeDir == null || !exeDir.StartsWith(tempRoot, StringComparison.OrdinalIgnoreCase))
|
||||
|
||||
var isStagedCopy = exeDir.StartsWith(agentStagingRoot, StringComparison.OrdinalIgnoreCase)
|
||||
|| exeDir.StartsWith(tempRoot, StringComparison.OrdinalIgnoreCase);
|
||||
if (!isStagedCopy)
|
||||
return;
|
||||
|
||||
var pid = Process.GetCurrentProcess().Id;
|
||||
|
||||
Reference in New Issue
Block a user