added ESP Configuration detection

This commit is contained in:
Oliver Kieselbach
2026-03-04 21:33:33 +01:00
parent e89f6222cd
commit 074f5ff600
4 changed files with 139 additions and 7 deletions
@@ -38,9 +38,9 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
/// <summary>
/// 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.
/// </summary>
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);
}
/// <summary>
@@ -581,6 +582,86 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
}
}
/// <summary>
/// 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.
/// </summary>
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<string, object>
{
{ "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<string, object> data)
{
_emitEvent(new EnrollmentEvent
@@ -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; }
@@ -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<string, object>
{
{ "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" }
}
});
}
@@ -1 +1 @@
212
214