diff --git a/Features/ADCompareDetailsProvider.cs b/Features/ADCompareDetailsProvider.cs
new file mode 100644
index 0000000..97ba743
--- /dev/null
+++ b/Features/ADCompareDetailsProvider.cs
@@ -0,0 +1,129 @@
+using Disco.Data.Repository;
+using Disco.Models.Repository;
+using Disco.Services.Interop.ActiveDirectory;
+using Disco.Services.Plugins;
+using Disco.Services.Plugins.Features.DetailsProvider;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Disco.Plugins.ADCompare.Features
+{
+ [PluginFeature(Id = "ADCompareDetails", Name = "AD Compare Detail Provider", PrimaryFeature = true)]
+ public class ADCompareDetailsProvider : DetailsProviderFeature
+ {
+ private const string DetailsScope = "Details";
+ private const string ADCompareScope = "ADCompare";
+
+ ///
+ /// Updates all user details by comparing AD attributes against Disco records.
+ /// Stores comparison metadata as UserDetails with ADCompare scope.
+ ///
+ public override void UpdateAllDetails(DiscoDataContext database)
+ {
+ var users = database.Users.ToList();
+
+ foreach (var user in users)
+ {
+ try
+ {
+ var adUser = ActiveDirectory.RetrieveADUserAccount(user.UserId);
+ if (adUser == null)
+ {
+ SetUserDetail(database, user, ADCompareScope, "ADStatus", "NotFound");
+ SetUserDetail(database, user, ADCompareScope, "LastChecked", DateTime.Now.ToString("o"));
+ continue;
+ }
+
+ // Store comparison results as UserDetails
+ SetUserDetail(database, user, ADCompareScope, "ADStatus", adUser.IsDisabled ? "Disabled" : "Active");
+ SetUserDetail(database, user, ADCompareScope, "LastChecked", DateTime.Now.ToString("o"));
+
+ // Store AD values for reference
+ SetUserDetail(database, user, ADCompareScope, "AD_DisplayName", adUser.DisplayName ?? string.Empty);
+ SetUserDetail(database, user, ADCompareScope, "AD_Surname", adUser.Surname ?? string.Empty);
+ SetUserDetail(database, user, ADCompareScope, "AD_GivenName", adUser.GivenName ?? string.Empty);
+ SetUserDetail(database, user, ADCompareScope, "AD_Email", adUser.Email ?? string.Empty);
+ SetUserDetail(database, user, ADCompareScope, "AD_Phone", adUser.Phone ?? string.Empty);
+ SetUserDetail(database, user, ADCompareScope, "AD_DistinguishedName", adUser.DistinguishedName ?? string.Empty);
+
+ // Check for mismatches
+ var mismatches = new List();
+ if (!StringMatch(user.DisplayName, adUser.DisplayName)) mismatches.Add("DisplayName");
+ if (!StringMatch(user.Surname, adUser.Surname)) mismatches.Add("Surname");
+ if (!StringMatch(user.GivenName, adUser.GivenName)) mismatches.Add("GivenName");
+ if (!StringMatch(user.EmailAddress, adUser.Email)) mismatches.Add("Email");
+ if (!StringMatch(user.PhoneNumber, adUser.Phone)) mismatches.Add("Phone");
+
+ SetUserDetail(database, user, ADCompareScope, "MismatchedFields",
+ mismatches.Count > 0 ? string.Join(",", mismatches) : "None");
+ }
+ catch (Exception ex)
+ {
+ SetUserDetail(database, user, ADCompareScope, "ADStatus", "Error");
+ SetUserDetail(database, user, ADCompareScope, "ADError", ex.Message);
+ SetUserDetail(database, user, ADCompareScope, "LastChecked", DateTime.Now.ToString("o"));
+ }
+ }
+
+ database.SaveChanges();
+ }
+
+ ///
+ /// Gets user photo from AD (thumbnailPhoto attribute).
+ /// Returns null if no photo or cache is still valid.
+ ///
+ public override byte[] GetUserPhoto(DiscoDataContext database, User user, DateTime? cacheTimestamp)
+ {
+ // Only refresh photos older than 24 hours
+ if (cacheTimestamp.HasValue && cacheTimestamp.Value > DateTime.Now.AddHours(-24))
+ return null;
+
+ try
+ {
+ var adUser = ActiveDirectory.RetrieveADUserAccount(user.UserId, new[] { "thumbnailPhoto" });
+ if (adUser == null)
+ return null;
+
+ var photoData = adUser.GetPropertyValue("thumbnailPhoto");
+ return photoData;
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ #region Helpers
+
+ private bool StringMatch(string discoValue, string adValue)
+ {
+ var a = string.IsNullOrWhiteSpace(discoValue) ? string.Empty : discoValue.Trim();
+ var b = string.IsNullOrWhiteSpace(adValue) ? string.Empty : adValue.Trim();
+ return string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
+ }
+
+ private void SetUserDetail(DiscoDataContext database, User user, string scope, string key, string value)
+ {
+ var existing = database.UserDetails
+ .FirstOrDefault(d => d.UserId == user.UserId && d.Scope == scope && d.Key == key);
+
+ if (existing != null)
+ {
+ existing.Value = value;
+ }
+ else
+ {
+ database.UserDetails.Add(new UserDetail
+ {
+ UserId = user.UserId,
+ Scope = scope,
+ Key = key,
+ Value = value
+ });
+ }
+ }
+
+ #endregion
+ }
+}