diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Security/TenantIdResolver.cs b/src/Agent/AutopilotMonitor.Agent.Core/Security/TenantIdResolver.cs index 36c5551a..c9d92651 100644 --- a/src/Agent/AutopilotMonitor.Agent.Core/Security/TenantIdResolver.cs +++ b/src/Agent/AutopilotMonitor.Agent.Core/Security/TenantIdResolver.cs @@ -7,7 +7,15 @@ namespace AutopilotMonitor.Agent.Core.Security { /// /// Resolves the device's AAD TenantId from one of several Windows registry signals. - /// V1 backport of the V2 resolver — same probe order, same diagnostic logging. + /// + /// All registry reads go through the explicit + /// view, never . Reason: SDK-style net48 EXEs + /// may resolve AnyCPU to 32-bit at runtime depending on MSBuild defaults, in which + /// case HKLM\SOFTWARE\Microsoft\Enrollments silently redirects to the + /// WOW6432Node mirror, a different (and on most devices stale/empty) hive + /// that never carries the active MDM enrollment. Forcing Registry64 makes the + /// resolver bitness-independent regardless of how AnyCPU resolves at runtime. + /// /// /// Probe order, first non-empty hit wins: /// @@ -29,9 +37,9 @@ namespace AutopilotMonitor.Agent.Core.Security /// /// /// Returns null when no source carries a TenantId. Never throws. On miss the - /// resolver dumps a diagnostic summary of which keys/values it saw so the bootstrap - /// log carries enough evidence to classify "device truly not enrolled" vs. "agent - /// fired before enrollment finished writing". + /// resolver dumps a compact diagnostic summary so the agent log carries enough + /// evidence to classify "device truly not enrolled" vs. "agent fired before + /// enrollment finished writing". /// /// public static class TenantIdResolver @@ -79,11 +87,15 @@ namespace AutopilotMonitor.Agent.Core.Security return null; } + private static RegistryKey OpenLocalMachine64() => + RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64); + private static string TryEnrollmentsRegistry(ResolverDiagnostics diag) { try { - using (var enrollmentsKey = Registry.LocalMachine.OpenSubKey(EnrollmentsKeyPath)) + using (var hklm64 = OpenLocalMachine64()) + using (var enrollmentsKey = hklm64.OpenSubKey(EnrollmentsKeyPath)) { if (enrollmentsKey == null) { @@ -91,9 +103,9 @@ namespace AutopilotMonitor.Agent.Core.Security return null; } - foreach (var enrollmentGuid in enrollmentsKey.GetSubKeyNames()) + foreach (var subKeyName in enrollmentsKey.GetSubKeyNames()) { - using (var enrollmentKey = enrollmentsKey.OpenSubKey(enrollmentGuid)) + using (var enrollmentKey = enrollmentsKey.OpenSubKey(subKeyName)) { if (enrollmentKey == null) continue; @@ -105,19 +117,34 @@ namespace AutopilotMonitor.Agent.Core.Security catch { /* unparseable — record as null */ } } - var aadTenantId = enrollmentKey.GetValue("AADTenantID")?.ToString(); - var enrollmentState = enrollmentKey.GetValue("EnrollmentState")?.ToString(); - - diag.EnrollmentsSeen.Add(new EnrollmentSnapshot + // Sub-keys without EnrollmentType are well-known structural folders + // (Context, Ownership, Status, ValidNodePaths). Skip them silently — + // they never carry AADTenantID and only add log noise on miss. + if (typeValue == null) { - Guid = enrollmentGuid, - EnrollmentType = typeValue, - EnrollmentState = enrollmentState, - HasAadTenantId = !string.IsNullOrEmpty(aadTenantId), - }); + diag.EnrollmentsStructuralCount++; + continue; + } - if (typeValue == IntuneMdmEnrollmentType && !string.IsNullOrEmpty(aadTenantId)) - return aadTenantId; + var aadTenantId = enrollmentKey.GetValue("AADTenantID")?.ToString(); + + if (typeValue == IntuneMdmEnrollmentType) + { + var enrollmentState = enrollmentKey.GetValue("EnrollmentState")?.ToString(); + diag.IntuneMdmEnrollments.Add(new IntuneMdmSnapshot + { + Guid = subKeyName, + EnrollmentState = enrollmentState, + HasAadTenantId = !string.IsNullOrEmpty(aadTenantId), + }); + + if (!string.IsNullOrEmpty(aadTenantId)) + return aadTenantId; + } + else + { + diag.OtherEnrollmentTypeCount++; + } } } } @@ -134,7 +161,8 @@ namespace AutopilotMonitor.Agent.Core.Security { try { - using (var tenantInfoKey = Registry.LocalMachine.OpenSubKey(CloudDomainJoinTenantInfoPath)) + using (var hklm64 = OpenLocalMachine64()) + using (var tenantInfoKey = hklm64.OpenSubKey(CloudDomainJoinTenantInfoPath)) { if (tenantInfoKey == null) { @@ -164,7 +192,8 @@ namespace AutopilotMonitor.Agent.Core.Security { try { - using (var joinInfoKey = Registry.LocalMachine.OpenSubKey(CloudDomainJoinJoinInfoPath)) + using (var hklm64 = OpenLocalMachine64()) + using (var joinInfoKey = hklm64.OpenSubKey(CloudDomainJoinJoinInfoPath)) { if (joinInfoKey == null) { @@ -207,20 +236,33 @@ namespace AutopilotMonitor.Agent.Core.Security // Enrollments root. if (diag.EnrollmentsProbeError != null) + { logger.Warning($"TenantIdResolver: Enrollments probe failed: {diag.EnrollmentsProbeError}"); + } else if (diag.EnrollmentsRootMissing) - logger.Warning($"TenantIdResolver: HKLM\\{EnrollmentsKeyPath} not found."); - else if (diag.EnrollmentsSeen.Count == 0) - logger.Warning($"TenantIdResolver: HKLM\\{EnrollmentsKeyPath} present but empty."); + { + logger.Warning($"TenantIdResolver: HKLM\\{EnrollmentsKeyPath} not found (Registry64 view)."); + } else { - logger.Warning($"TenantIdResolver: HKLM\\{EnrollmentsKeyPath} carries {diag.EnrollmentsSeen.Count} enrollment(s) but none was Intune MDM (Type=6) with AADTenantID:"); - foreach (var e in diag.EnrollmentsSeen) + logger.Warning( + $"TenantIdResolver: HKLM\\{EnrollmentsKeyPath} (Registry64) — " + + $"Intune-MDM (Type=6): {diag.IntuneMdmEnrollments.Count}, " + + $"other enrollment types: {diag.OtherEnrollmentTypeCount}, " + + $"structural sub-keys: {diag.EnrollmentsStructuralCount}."); + + if (diag.IntuneMdmEnrollments.Count == 0) { - logger.Warning( - $" - {e.Guid}: EnrollmentType={(e.EnrollmentType?.ToString() ?? "")}, " + - $"EnrollmentState={e.EnrollmentState ?? ""}, " + - $"AADTenantID={(e.HasAadTenantId ? "" : "")}"); + logger.Warning("TenantIdResolver: no Type=6 enrollment present yet — MDM enrollment hasn't reached registry write phase."); + } + else + { + foreach (var e in diag.IntuneMdmEnrollments) + { + logger.Warning( + $" - Type=6 {e.Guid}: EnrollmentState={e.EnrollmentState ?? ""}, " + + $"AADTenantID={(e.HasAadTenantId ? "" : "")}"); + } } } @@ -253,7 +295,9 @@ namespace AutopilotMonitor.Agent.Core.Security { public bool EnrollmentsRootMissing; public string EnrollmentsProbeError; - public readonly List EnrollmentsSeen = new List(); + public readonly List IntuneMdmEnrollments = new List(); + public int OtherEnrollmentTypeCount; + public int EnrollmentsStructuralCount; public bool TenantInfoRootMissing; public string TenantInfoProbeError; @@ -264,10 +308,9 @@ namespace AutopilotMonitor.Agent.Core.Security public readonly List JoinInfoEntries = new List(); } - private struct EnrollmentSnapshot + private struct IntuneMdmSnapshot { public string Guid; - public int? EnrollmentType; public string EnrollmentState; public bool HasAadTenantId; } diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Security/TenantIdResolver.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Security/TenantIdResolver.cs index 6741eee7..6c41afee 100644 --- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Security/TenantIdResolver.cs +++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Security/TenantIdResolver.cs @@ -7,7 +7,15 @@ namespace AutopilotMonitor.Agent.V2.Core.Security { /// /// Resolves the device's AAD TenantId from one of several Windows registry signals. - /// Plan §4.x M4.5.b. + /// + /// All registry reads go through the explicit + /// view, never . Reason: SDK-style net48 EXEs + /// may resolve AnyCPU to 32-bit at runtime depending on MSBuild defaults, in which + /// case HKLM\SOFTWARE\Microsoft\Enrollments silently redirects to the + /// WOW6432Node mirror, a different (and on most devices stale/empty) hive + /// that never carries the active MDM enrollment. Forcing Registry64 makes the + /// resolver bitness-independent regardless of how AnyCPU resolves at runtime. + /// /// /// Probe order, first non-empty hit wins: /// @@ -29,9 +37,9 @@ namespace AutopilotMonitor.Agent.V2.Core.Security /// /// /// Returns null when no source carries a TenantId. Never throws. On miss the - /// resolver dumps a diagnostic summary of which keys/values it saw so the bootstrap - /// log carries enough evidence to classify "device truly not enrolled" vs. "agent - /// fired before enrollment finished writing". + /// resolver dumps a compact diagnostic summary so the agent log carries enough + /// evidence to classify "device truly not enrolled" vs. "agent fired before + /// enrollment finished writing". /// /// public static class TenantIdResolver @@ -79,11 +87,15 @@ namespace AutopilotMonitor.Agent.V2.Core.Security return null; } + private static RegistryKey OpenLocalMachine64() => + RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64); + private static string TryEnrollmentsRegistry(ResolverDiagnostics diag) { try { - using (var enrollmentsKey = Registry.LocalMachine.OpenSubKey(EnrollmentsKeyPath)) + using (var hklm64 = OpenLocalMachine64()) + using (var enrollmentsKey = hklm64.OpenSubKey(EnrollmentsKeyPath)) { if (enrollmentsKey == null) { @@ -91,9 +103,9 @@ namespace AutopilotMonitor.Agent.V2.Core.Security return null; } - foreach (var enrollmentGuid in enrollmentsKey.GetSubKeyNames()) + foreach (var subKeyName in enrollmentsKey.GetSubKeyNames()) { - using (var enrollmentKey = enrollmentsKey.OpenSubKey(enrollmentGuid)) + using (var enrollmentKey = enrollmentsKey.OpenSubKey(subKeyName)) { if (enrollmentKey == null) continue; @@ -105,19 +117,34 @@ namespace AutopilotMonitor.Agent.V2.Core.Security catch { /* unparseable — record as null */ } } - var aadTenantId = enrollmentKey.GetValue("AADTenantID")?.ToString(); - var enrollmentState = enrollmentKey.GetValue("EnrollmentState")?.ToString(); - - diag.EnrollmentsSeen.Add(new EnrollmentSnapshot + // Sub-keys without EnrollmentType are well-known structural folders + // (Context, Ownership, Status, ValidNodePaths). Skip them silently — + // they never carry AADTenantID and only add log noise on miss. + if (typeValue == null) { - Guid = enrollmentGuid, - EnrollmentType = typeValue, - EnrollmentState = enrollmentState, - HasAadTenantId = !string.IsNullOrEmpty(aadTenantId), - }); + diag.EnrollmentsStructuralCount++; + continue; + } - if (typeValue == IntuneMdmEnrollmentType && !string.IsNullOrEmpty(aadTenantId)) - return aadTenantId; + var aadTenantId = enrollmentKey.GetValue("AADTenantID")?.ToString(); + + if (typeValue == IntuneMdmEnrollmentType) + { + var enrollmentState = enrollmentKey.GetValue("EnrollmentState")?.ToString(); + diag.IntuneMdmEnrollments.Add(new IntuneMdmSnapshot + { + Guid = subKeyName, + EnrollmentState = enrollmentState, + HasAadTenantId = !string.IsNullOrEmpty(aadTenantId), + }); + + if (!string.IsNullOrEmpty(aadTenantId)) + return aadTenantId; + } + else + { + diag.OtherEnrollmentTypeCount++; + } } } } @@ -134,7 +161,8 @@ namespace AutopilotMonitor.Agent.V2.Core.Security { try { - using (var tenantInfoKey = Registry.LocalMachine.OpenSubKey(CloudDomainJoinTenantInfoPath)) + using (var hklm64 = OpenLocalMachine64()) + using (var tenantInfoKey = hklm64.OpenSubKey(CloudDomainJoinTenantInfoPath)) { if (tenantInfoKey == null) { @@ -164,7 +192,8 @@ namespace AutopilotMonitor.Agent.V2.Core.Security { try { - using (var joinInfoKey = Registry.LocalMachine.OpenSubKey(CloudDomainJoinJoinInfoPath)) + using (var hklm64 = OpenLocalMachine64()) + using (var joinInfoKey = hklm64.OpenSubKey(CloudDomainJoinJoinInfoPath)) { if (joinInfoKey == null) { @@ -207,20 +236,33 @@ namespace AutopilotMonitor.Agent.V2.Core.Security // Enrollments root. if (diag.EnrollmentsProbeError != null) + { logger.Warning($"TenantIdResolver: Enrollments probe failed: {diag.EnrollmentsProbeError}"); + } else if (diag.EnrollmentsRootMissing) - logger.Warning($"TenantIdResolver: HKLM\\{EnrollmentsKeyPath} not found."); - else if (diag.EnrollmentsSeen.Count == 0) - logger.Warning($"TenantIdResolver: HKLM\\{EnrollmentsKeyPath} present but empty."); + { + logger.Warning($"TenantIdResolver: HKLM\\{EnrollmentsKeyPath} not found (Registry64 view)."); + } else { - logger.Warning($"TenantIdResolver: HKLM\\{EnrollmentsKeyPath} carries {diag.EnrollmentsSeen.Count} enrollment(s) but none was Intune MDM (Type=6) with AADTenantID:"); - foreach (var e in diag.EnrollmentsSeen) + logger.Warning( + $"TenantIdResolver: HKLM\\{EnrollmentsKeyPath} (Registry64) — " + + $"Intune-MDM (Type=6): {diag.IntuneMdmEnrollments.Count}, " + + $"other enrollment types: {diag.OtherEnrollmentTypeCount}, " + + $"structural sub-keys: {diag.EnrollmentsStructuralCount}."); + + if (diag.IntuneMdmEnrollments.Count == 0) { - logger.Warning( - $" - {e.Guid}: EnrollmentType={(e.EnrollmentType?.ToString() ?? "")}, " + - $"EnrollmentState={e.EnrollmentState ?? ""}, " + - $"AADTenantID={(e.HasAadTenantId ? "" : "")}"); + logger.Warning("TenantIdResolver: no Type=6 enrollment present yet — MDM enrollment hasn't reached registry write phase."); + } + else + { + foreach (var e in diag.IntuneMdmEnrollments) + { + logger.Warning( + $" - Type=6 {e.Guid}: EnrollmentState={e.EnrollmentState ?? ""}, " + + $"AADTenantID={(e.HasAadTenantId ? "" : "")}"); + } } } @@ -253,7 +295,9 @@ namespace AutopilotMonitor.Agent.V2.Core.Security { public bool EnrollmentsRootMissing; public string EnrollmentsProbeError; - public readonly List EnrollmentsSeen = new List(); + public readonly List IntuneMdmEnrollments = new List(); + public int OtherEnrollmentTypeCount; + public int EnrollmentsStructuralCount; public bool TenantInfoRootMissing; public string TenantInfoProbeError; @@ -264,10 +308,9 @@ namespace AutopilotMonitor.Agent.V2.Core.Security public readonly List JoinInfoEntries = new List(); } - private struct EnrollmentSnapshot + private struct IntuneMdmSnapshot { public string Guid; - public int? EnrollmentType; public string EnrollmentState; public bool HasAadTenantId; } diff --git a/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt index 80e3e6ea..055b6671 100644 --- a/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt +++ b/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt @@ -1 +1 @@ -506 +507 diff --git a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt index fe1b0608..53d108b9 100644 --- a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt +++ b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt @@ -1 +1 @@ -1228 +1231