diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Collectors/DeviceInfoCollector.cs b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Collectors/DeviceInfoCollector.cs
index eff122ee..266e6e10 100644
--- a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Collectors/DeviceInfoCollector.cs
+++ b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Collectors/DeviceInfoCollector.cs
@@ -38,9 +38,9 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
///
/// Runs all device info collectors at agent startup.
- /// Returns the detected enrollment type ("v1" or "v2") and whether the profile indicates Hybrid Join.
+ /// Returns the detected enrollment type, Hybrid Join flag, and ESP skip configuration from registry.
///
- public (string enrollmentType, bool isHybridJoin) CollectAll()
+ public (string enrollmentType, bool isHybridJoin, bool? skipUserStatusPage, bool? skipDeviceStatusPage) CollectAll()
{
_logger.Info("EnrollmentTracker: collecting device info (at start)");
@@ -48,12 +48,13 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
CollectNetworkAndDnsConfiguration();
CollectProxyConfiguration();
var profileResult = CollectAutopilotProfile();
+ var espConfig = CollectEspConfiguration();
CollectSecureBootStatus();
CollectBitLockerStatus();
CollectTpmStatus();
CollectAadJoinStatus();
- return profileResult;
+ return (profileResult.enrollmentType, profileResult.isHybridJoin, espConfig.skipUserStatusPage, espConfig.skipDeviceStatusPage);
}
///
@@ -581,6 +582,86 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
}
}
+ ///
+ /// Reads ESP skip configuration from the enrollment FirstSync registry key.
+ /// CSP SkipUserStatusPage/SkipDeviceStatusPage are written as DWORD:
+ /// 0xFFFFFFFF = skip (ESP page not shown), 0 = show, key missing = unknown.
+ /// We use defensive interpretation: non-null AND non-zero = skip.
+ ///
+ private (bool? skipUserStatusPage, bool? skipDeviceStatusPage) CollectEspConfiguration()
+ {
+ bool? skipUser = null;
+ bool? skipDevice = null;
+ string enrollmentGuid = null;
+
+ try
+ {
+ const string enrollmentsKeyPath = @"SOFTWARE\Microsoft\Enrollments";
+
+ using (var enrollmentsKey = Registry.LocalMachine.OpenSubKey(enrollmentsKeyPath))
+ {
+ if (enrollmentsKey == null)
+ {
+ _logger.Debug("EnrollmentTracker: Enrollments registry key not found — ESP config unknown");
+ return (null, null);
+ }
+
+ // Find the MDM enrollment entry (EnrollmentType == 6)
+ foreach (var guid in enrollmentsKey.GetSubKeyNames())
+ {
+ using (var enrollmentKey = enrollmentsKey.OpenSubKey(guid))
+ {
+ if (enrollmentKey == null) continue;
+
+ var enrollmentType = enrollmentKey.GetValue("EnrollmentType");
+ if (enrollmentType == null || Convert.ToInt32(enrollmentType) != 6)
+ continue;
+
+ enrollmentGuid = guid;
+
+ // Read FirstSync subkey
+ using (var firstSyncKey = enrollmentKey.OpenSubKey("FirstSync"))
+ {
+ if (firstSyncKey == null)
+ {
+ _logger.Debug($"EnrollmentTracker: FirstSync subkey not found for enrollment {guid}");
+ break;
+ }
+
+ var rawSkipUser = firstSyncKey.GetValue("SkipUserStatusPage");
+ if (rawSkipUser != null)
+ skipUser = Convert.ToInt32(rawSkipUser) != 0;
+
+ var rawSkipDevice = firstSyncKey.GetValue("SkipDeviceStatusPage");
+ if (rawSkipDevice != null)
+ skipDevice = Convert.ToInt32(rawSkipDevice) != 0;
+ }
+
+ break; // Found the MDM enrollment entry
+ }
+ }
+ }
+
+ var data = new Dictionary
+ {
+ { "source", "registry_firstsync" }
+ };
+ if (enrollmentGuid != null) data["enrollmentGuid"] = enrollmentGuid;
+ if (skipUser.HasValue) data["skipUserStatusPage"] = skipUser.Value;
+ if (skipDevice.HasValue) data["skipDeviceStatusPage"] = skipDevice.Value;
+
+ var summary = $"SkipUser={skipUser?.ToString() ?? "unknown"}, SkipDevice={skipDevice?.ToString() ?? "unknown"}";
+ _logger.Info($"EnrollmentTracker: ESP configuration detected — {summary}");
+ EmitDeviceInfoEvent("esp_config_detected", $"ESP configuration: {summary}", data);
+ }
+ catch (Exception ex)
+ {
+ _logger.Warning($"EnrollmentTracker: failed to collect ESP configuration: {ex.Message}");
+ }
+
+ return (skipUser, skipDevice);
+ }
+
private void EmitDeviceInfoEvent(string eventType, string message, Dictionary data)
{
_emitEvent(new EnrollmentEvent
diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentStatePersistence.cs b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentStatePersistence.cs
index f7891e4c..d75ef15f 100644
--- a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentStatePersistence.cs
+++ b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentStatePersistence.cs
@@ -107,6 +107,10 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
public bool EnrollmentCompleteEmitted { get; set; }
public string EnrollmentType { get; set; }
+ // ESP configuration from registry (FirstSync\SkipUserStatusPage / SkipDeviceStatusPage)
+ public bool? SkipUserStatusPage { get; set; }
+ public bool? SkipDeviceStatusPage { get; set; }
+
// Signal timestamps for audit trail
public DateTime? EspFirstSeenUtc { get; set; }
public DateTime? EspFinalExitUtc { get; set; }
diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentTracker.cs b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentTracker.cs
index e76e6af3..4ae80e88 100644
--- a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentTracker.cs
+++ b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentTracker.cs
@@ -57,6 +57,11 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
private Timer _deviceOnlyEspTimer;
private const int DeviceOnlyEspTimerMinutes = 5;
+ // ESP configuration from registry (FirstSync\SkipUserStatusPage / SkipDeviceStatusPage)
+ // null = unknown (registry keys not present), true = skip, false = show
+ private bool? _skipUserStatusPage;
+ private bool? _skipDeviceStatusPage;
+
// Safety-net timer for waiting_for_hello state
private Timer _waitingForHelloSafetyTimer;
private const int WaitingForHelloSafetyTimeoutSeconds = 420; // 7 min — longer than Hello chain (330s)
@@ -241,7 +246,11 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
{
var result = _deviceInfoCollector.CollectAll();
_enrollmentType = result.enrollmentType;
+ _skipUserStatusPage = result.skipUserStatusPage;
+ _skipDeviceStatusPage = result.skipDeviceStatusPage;
_stateData.EnrollmentType = _enrollmentType;
+ _stateData.SkipUserStatusPage = _skipUserStatusPage;
+ _stateData.SkipDeviceStatusPage = _skipDeviceStatusPage;
_stateDirty = true;
}
@@ -873,10 +882,44 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
// during AccountSetup), the composite signal can fire immediately.
TryEmitEnrollmentComplete("esp_hello_composite");
}
+ else if (_skipUserStatusPage == true)
+ {
+ // Registry definitively says no AccountSetup expected → immediate device-only classification
+ _logger.Info($"EnrollmentTracker: ESP phase exiting from '{_lastEspPhase ?? "unknown"}' — SkipUserStatusPage=true, classified as device-only ESP (registry)");
+
+ _espFinalExitSeen = true;
+ _stateData.EspFinalExitSeen = true;
+ _stateData.EspFinalExitUtc = DateTime.UtcNow;
+ RecordSignal("device_only_esp_registry");
+
+ _emitEvent(new EnrollmentEvent
+ {
+ SessionId = _sessionId,
+ TenantId = _tenantId,
+ EventType = "esp_phase_changed",
+ Severity = EventSeverity.Info,
+ Source = "EnrollmentTracker",
+ Phase = EnrollmentPhase.FinalizingSetup,
+ Message = "ESP phase: FinalizingSetup (device-only ESP — SkipUserStatusPage confirmed via registry)",
+ Data = new Dictionary
+ {
+ { "espPhase", "FinalizingSetup" },
+ { "autoDetected", true },
+ { "triggeredBy", "device_only_esp_registry" },
+ { "previousPhase", _lastEspPhase ?? "unknown" },
+ { "skipUserStatusPage", true }
+ }
+ });
+
+ CollectDeviceInfoAtFinalizingSetup("device_only_esp_registry");
+ _espAndHelloTracker?.StartHelloWaitTimer();
+ TryEmitEnrollmentComplete("device_only_esp_registry");
+ }
else
{
- // DeviceSetup phase exiting without desktop — intermediate transition (Device→Account) or device-only ESP
- _logger.Info($"EnrollmentTracker: ESP phase exiting from '{_lastEspPhase ?? "unknown"}' - intermediate transition, starting device-only ESP detection timer ({DeviceOnlyEspTimerMinutes}min)");
+ // Registry keys unknown or SkipUserStatusPage=false → fallback to timer-based detection
+ var fallbackReason = _skipUserStatusPage == null ? "registry keys not found" : "SkipUserStatusPage=false";
+ _logger.Info($"EnrollmentTracker: ESP phase exiting from '{_lastEspPhase ?? "unknown"}' — {fallbackReason}, starting device-only ESP detection timer ({DeviceOnlyEspTimerMinutes}min)");
// Start device-only ESP detection timer
_deviceOnlyEspTimer?.Dispose();
@@ -928,6 +971,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
_isWaitingForHello = loaded.IsWaitingForHello;
_enrollmentCompleteEmitted = loaded.EnrollmentCompleteEmitted;
_enrollmentType = loaded.EnrollmentType ?? _enrollmentType;
+ _skipUserStatusPage = loaded.SkipUserStatusPage;
+ _skipDeviceStatusPage = loaded.SkipDeviceStatusPage;
_stateData = loaded;
_logger.Info($"EnrollmentTracker: state restored — espEverSeen={_espEverSeen}, espFinalExitSeen={_espFinalExitSeen}, desktopArrived={_desktopArrived}, lastEspPhase={_lastEspPhase}, enrollmentCompleteEmitted={_enrollmentCompleteEmitted}");
@@ -1083,7 +1128,9 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
{ "helloResolved", _espAndHelloTracker?.IsHelloCompleted ?? false },
{ "helloPolicyConfigured", _espAndHelloTracker?.IsPolicyConfigured ?? false },
{ "enrollmentType", _enrollmentType },
- { "lastEspPhase", _lastEspPhase ?? "none" }
+ { "lastEspPhase", _lastEspPhase ?? "none" },
+ { "skipUserStatusPage", _skipUserStatusPage?.ToString() ?? "unknown" },
+ { "skipDeviceStatusPage", _skipDeviceStatusPage?.ToString() ?? "unknown" }
}
});
}
diff --git a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt
index 0d389107..9d683f8c 100644
--- a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt
+++ b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt
@@ -1 +1 @@
-212
+214