From 43502fd8da35112fe42cfd54cedf84f5a28f4442 Mon Sep 17 00:00:00 2001 From: jessikitty Date: Tue, 21 Apr 2026 21:32:21 +1000 Subject: [PATCH] Add device comparison service - compares AD managedBy vs Disco assigned user --- Features/DeviceCompareService.cs | 167 +++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 Features/DeviceCompareService.cs diff --git a/Features/DeviceCompareService.cs b/Features/DeviceCompareService.cs new file mode 100644 index 0000000..b40c313 --- /dev/null +++ b/Features/DeviceCompareService.cs @@ -0,0 +1,167 @@ +using Disco.Data.Repository; +using Disco.Models.Repository; +using Disco.Plugins.ADCompare.Models; +using Disco.Services.Interop.ActiveDirectory; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Disco.Plugins.ADCompare.Features +{ + public class DeviceCompareService + { + private readonly DiscoDataContext database; + + public DeviceCompareService(DiscoDataContext database) + { + this.database = database; + } + + /// + /// Compare all Disco devices' assigned users against the AD computer managedBy field. + /// Only considers active (not decommissioned) devices with a domain ID. + /// + public DeviceComparisonSummary CompareAllDevices() + { + var summary = new DeviceComparisonSummary(); + + // Load active devices that have a domain computer account + var devices = database.Devices + .Include("AssignedUser") + .Where(d => d.DeviceDomainId != null && d.DecommissionedDate == null) + .ToList(); + + summary.TotalDevices = devices.Count; + + foreach (var device in devices) + { + var result = CompareDevice(device); + summary.Results.Add(result); + } + + summary.DevicesWithAssignment = summary.Results.Count(r => r.HasAssignment); + summary.DevicesNotInAD = summary.Results.Count(r => !r.FoundInAD); + summary.DevicesMatched = summary.Results.Count(r => r.IsMatch); + summary.DevicesMismatched = summary.Results.Count(r => !r.IsMatch && r.FoundInAD); + summary.DevicesNoAssignment = summary.Results.Count(r => !r.HasAssignment); + summary.DevicesNoManagedBy = summary.Results.Count(r => r.FoundInAD && !r.HasManagedBy); + summary.DevicesADDisabled = summary.Results.Count(r => r.ADAccountDisabled); + + return summary; + } + + /// + /// Compare a single device's Disco assignment against AD managedBy. + /// + public DeviceComparisonResult CompareDevice(Device device) + { + var result = new DeviceComparisonResult + { + SerialNumber = device.SerialNumber, + DeviceDomainId = device.DeviceDomainId, + ComputerName = device.ComputerName, + DiscoAssignedUserId = device.AssignedUserId, + DiscoAssignedUserDisplayName = device.AssignedUser?.DisplayName, + HasAssignment = !string.IsNullOrEmpty(device.AssignedUserId) + }; + + try + { + // Look up the computer in AD, requesting the managedBy attribute + var adAccount = ActiveDirectory.RetrieveADMachineAccount(device.DeviceDomainId, new[] { "managedBy" }); + + if (adAccount == null) + { + result.FoundInAD = false; + result.MismatchReason = "Computer not found in AD"; + return result; + } + + result.FoundInAD = true; + result.ADAccountDisabled = adAccount.IsDisabled; + + // Get the managedBy DN from AD + var managedByDN = adAccount.GetPropertyValue("managedBy"); + result.ADManagedByDN = managedByDN; + result.HasManagedBy = !string.IsNullOrEmpty(managedByDN); + + // Resolve managedBy DN to a DOMAIN\username + if (result.HasManagedBy) + { + try + { + var managedByUser = ActiveDirectory.RetrieveADUserAccount(managedByDN); + if (managedByUser != null) + { + result.ADManagedByUserId = managedByUser.Id; + result.ADManagedByDisplayName = managedByUser.DisplayName; + } + else + { + result.ADManagedByUserId = managedByDN; // fallback to DN + } + } + catch + { + // If we can't resolve the DN, store it raw + result.ADManagedByUserId = managedByDN; + } + } + + // Now compare + result.IsMatch = DetermineMatch(result); + if (!result.IsMatch) + { + result.MismatchReason = DetermineMismatchReason(result); + } + } + catch (Exception ex) + { + result.FoundInAD = false; + result.MismatchReason = $"AD lookup error: {ex.Message}"; + } + + return result; + } + + /// + /// Determine if the Disco assignment matches the AD managedBy. + /// + private bool DetermineMatch(DeviceComparisonResult result) + { + // Both empty = match (neither has an assignment) + if (!result.HasAssignment && !result.HasManagedBy) + return true; + + // One has assignment, other doesn't = mismatch + if (result.HasAssignment != result.HasManagedBy) + return false; + + // Both have values - compare the user IDs (case-insensitive) + return string.Equals( + result.DiscoAssignedUserId, + result.ADManagedByUserId, + StringComparison.OrdinalIgnoreCase); + } + + /// + /// Generate a human-readable reason for the mismatch. + /// + private string DetermineMismatchReason(DeviceComparisonResult result) + { + if (!result.FoundInAD) + return "Computer not found in AD"; + + if (result.HasAssignment && !result.HasManagedBy) + return "Assigned in Disco but AD managedBy is empty"; + + if (!result.HasAssignment && result.HasManagedBy) + return "Not assigned in Disco but AD managedBy is set"; + + if (result.HasAssignment && result.HasManagedBy) + return $"Different users: Disco={result.DiscoAssignedUserId}, AD managedBy={result.ADManagedByUserId}"; + + return "Unknown mismatch"; + } + } +}