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";
+ }
+ }
+}