diff --git a/Disco.Services/Devices/DeviceActionExtensions.cs b/Disco.Services/Devices/DeviceActionExtensions.cs
index 59f5f2bb..7a898cab 100644
--- a/Disco.Services/Devices/DeviceActionExtensions.cs
+++ b/Disco.Services/Devices/DeviceActionExtensions.cs
@@ -80,7 +80,7 @@ namespace Disco.Services
return true;
}
- public static void OnDecommission(this Device d, Disco.Models.Repository.DecommissionReasons Reason)
+ public static void OnDecommission(this Device d, DecommissionReasons Reason)
{
if (!d.CanDecommission())
throw new InvalidOperationException("Decommission of Device is Denied");
@@ -95,6 +95,7 @@ namespace Disco.Services
if (adAccount != null && !adAccount.IsCriticalSystemObject)
{
adAccount.DisableAccount();
+ adAccount.SetDescription(d);
}
}
}
@@ -122,6 +123,7 @@ namespace Disco.Services
if (adAccount != null && !adAccount.IsCriticalSystemObject)
{
adAccount.EnableAccount();
+ adAccount.SetDescription(d);
}
}
}
diff --git a/Disco.Services/Disco.Services.csproj b/Disco.Services/Disco.Services.csproj
index 40b3c644..e50a8f9e 100644
--- a/Disco.Services/Disco.Services.csproj
+++ b/Disco.Services/Disco.Services.csproj
@@ -322,6 +322,7 @@
+
diff --git a/Disco.Services/Interop/ActiveDirectory/ADDeviceDescriptionUpdateTask.cs b/Disco.Services/Interop/ActiveDirectory/ADDeviceDescriptionUpdateTask.cs
new file mode 100644
index 00000000..59fd1a39
--- /dev/null
+++ b/Disco.Services/Interop/ActiveDirectory/ADDeviceDescriptionUpdateTask.cs
@@ -0,0 +1,138 @@
+using Disco.Data.Repository;
+using Disco.Services.Tasks;
+using Quartz;
+using System;
+using System.IO;
+using System.Linq;
+
+namespace Disco.Services.Interop.ActiveDirectory
+{
+ public class ADDeviceDescriptionUpdateTask : ScheduledTask
+ {
+ public override string TaskName { get { return "Active Directory Device Description Update"; } }
+
+ public override bool SingleInstanceTask { get { return true; } }
+ public override bool CancelInitiallySupported { get { return false; } }
+
+ #region Required Helpers
+ private static string RequiredFilePath(DiscoDataContext Database)
+ {
+ if (Database.DiscoConfiguration.DataStoreLocation != null)
+ return Path.Combine(Database.DiscoConfiguration.DataStoreLocation, "_ADDeviceDescriptionUpdateRequired.txt");
+ else
+ return null;
+ }
+
+ public static bool IsRequired(DiscoDataContext Database)
+ {
+ var requiredFilePath = RequiredFilePath(Database);
+
+ if (requiredFilePath == null)
+ return false;
+ else
+ return File.Exists(requiredFilePath);
+ }
+ public static void SetRequired(DiscoDataContext Database)
+ {
+ var requiredFilePath = RequiredFilePath(Database);
+
+ if (requiredFilePath != null)
+ {
+ File.WriteAllText(requiredFilePath, "This file exists to indicate an update to AD Device Descriptions is required. It will automatically be deleted when the update completes.");
+ File.SetAttributes(requiredFilePath, FileAttributes.Hidden);
+ }
+ }
+ #endregion
+
+ public override void InitalizeScheduledTask(DiscoDataContext Database)
+ {
+ if (IsRequired(Database))
+ {
+ // Schedule in 5mins
+ var trigger = TriggerBuilder.Create()
+ .StartAt(DateTimeOffset.Now.AddMinutes(3));
+
+ this.ScheduleTask(trigger);
+ }
+ }
+
+ public static ScheduledTaskStatus ScheduleImmediately()
+ {
+ var existingTask = ScheduledTasks.GetTaskStatuses(typeof(ADDeviceDescriptionUpdateTask)).Where(s => s.IsRunning).FirstOrDefault();
+ if (existingTask != null)
+ return existingTask;
+
+ var instance = new ADDeviceDescriptionUpdateTask();
+ return instance.ScheduleTask();
+ }
+
+ protected override void ExecuteTask()
+ {
+ using (DiscoDataContext database = new DiscoDataContext())
+ {
+ Status.UpdateStatus(0, "Updating Active Directory Device Descriptions", "Reading Devices");
+
+ // Devices
+ var devices = database.Devices.Where(d => d.DeviceDomainId != null)
+ .ToList();
+
+ int failedTotal = 0;
+ int notFoundTotal = 0;
+ int completedTotal = 0;
+
+ // Refine valid devices
+ devices = devices.Where(d => ActiveDirectory.IsValidDomainAccountId(d.DeviceDomainId)).ToList();
+
+ foreach (var domainGroup in devices.GroupBy(d => d.ComputerDomainName).ToList())
+ {
+ ADDomain domain;
+ if (domainGroup.Key != null && ActiveDirectory.Context.TryGetDomainByNetBiosName(domainGroup.Key, out domain))
+ {
+ var controller = domain.GetAvailableDomainController(RequireWritable: true);
+
+ foreach (var device in domainGroup)
+ {
+ completedTotal++;
+ if ((completedTotal % 10) == 0)
+ {
+ Status.UpdateStatus((100D / devices.Count) * completedTotal, $"Processing: {device.DeviceDomainId} ({device.SerialNumber})");
+ }
+ try
+ {
+ var adAccount = device.ActiveDirectoryAccount();
+ if (adAccount == null)
+ {
+ notFoundTotal++;
+
+ if (!device.DecommissionedDate.HasValue)
+ {
+ Status.LogWarning($"Unable to locate [{device.DeviceDomainId}] for commissioned device [{device.SerialNumber}] in the domain");
+ }
+ }
+ else
+ {
+ adAccount.SetDescription(controller, device);
+ }
+ }
+ catch (Exception ex)
+ {
+ failedTotal++;
+ Status.LogWarning($"Error when setting description of computer account [{device.DeviceDomainId}] for device [{device.SerialNumber}]: [{ex.GetType().Name}] {ex.Message}");
+ }
+ }
+ }
+ }
+
+ // Finished - Remove Placeholder File
+ var requiredFilePath = RequiredFilePath(database);
+ if (requiredFilePath != null && File.Exists(requiredFilePath))
+ File.Delete(requiredFilePath);
+
+ Status.SetFinishedMessage($"Finished updating device descriptions for {devices.Count:N0}. {notFoundTotal:N0} were not found. {failedTotal:N0} failed.");
+ Status.LogInformation(Status.FinishedMessage);
+ Status.Finished();
+ }
+ }
+
+ }
+}
diff --git a/Disco.Services/Interop/ActiveDirectory/ADMachineAccount.cs b/Disco.Services/Interop/ActiveDirectory/ADMachineAccount.cs
index fa87d456..66806c81 100644
--- a/Disco.Services/Interop/ActiveDirectory/ADMachineAccount.cs
+++ b/Disco.Services/Interop/ActiveDirectory/ADMachineAccount.cs
@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
+using System.Text;
namespace Disco.Services.Interop.ActiveDirectory
{
@@ -178,15 +179,18 @@ namespace Disco.Services.Interop.ActiveDirectory
using (var deAccount = WritableDomainController.RetrieveDirectoryEntry(this.DistinguishedName))
{
var descriptionProp = deAccount.Entry.Properties["description"];
- if (descriptionProp.Count > 0)
+ if (descriptionProp.Count != 1 || (descriptionProp[0] as string) != Description)
{
- descriptionProp.Clear();
+ if (descriptionProp.Count > 0)
+ {
+ descriptionProp.Clear();
+ }
+ if (!string.IsNullOrEmpty(Description))
+ {
+ descriptionProp.Add(Description);
+ }
+ deAccount.Entry.CommitChanges();
}
- if (!string.IsNullOrEmpty(Description))
- {
- descriptionProp.Add(Description);
- }
- deAccount.Entry.CommitChanges();
}
}
public void SetDescription(string Description)
@@ -196,19 +200,28 @@ namespace Disco.Services.Interop.ActiveDirectory
public void SetDescription(ADDomainController WritableDomainController, Device Device)
{
- System.Text.StringBuilder descriptionBuilder = new System.Text.StringBuilder();
+ var descriptionBuilder = new StringBuilder();
- if (Device.AssignedUserId != null)
+ if (Device.DecommissionedDate.HasValue)
{
- descriptionBuilder.Append(Device.AssignedUser.UserId).Append(" (").Append(Device.AssignedUser.DisplayName).Append("); ");
+ descriptionBuilder.Append("Decommissioned: ")
+ .Append(Device.DecommissionReason.ReasonMessage())
+ .Append(" (").Append(Device.DecommissionedDate.Value.ToString("yyyy-MM-dd")).Append(')');
}
-
- if (Device.DeviceModelId.HasValue)
+ else
{
- descriptionBuilder.Append(Device.DeviceModel.Description).Append("; ");
- }
+ if (Device.AssignedUserId != null)
+ {
+ descriptionBuilder.Append(Device.AssignedUser.UserId).Append(" (").Append(Device.AssignedUser.DisplayName).Append("); ");
+ }
- descriptionBuilder.Append(Device.DeviceProfile.Description).Append(";");
+ if (Device.DeviceModelId.HasValue)
+ {
+ descriptionBuilder.Append(Device.DeviceModel.Description).Append("; ");
+ }
+
+ descriptionBuilder.Append(Device.DeviceProfile.Description).Append(";");
+ }
string description = descriptionBuilder.ToString().Trim();
if (description.Length > 1024)
@@ -336,7 +349,7 @@ namespace Disco.Services.Interop.ActiveDirectory
{
i.Entry.UsePropertyCache = false;
i.Entry.MoveTo(ou.Entry);
-
+
// Update Distinguished Name
this.DistinguishedName = i.Entry.Properties["distinguishedName"][0].ToString();
}
diff --git a/Disco.Web/App_Start/AppConfig.cs b/Disco.Web/App_Start/AppConfig.cs
index 421c4bef..fa0104ce 100644
--- a/Disco.Web/App_Start/AppConfig.cs
+++ b/Disco.Web/App_Start/AppConfig.cs
@@ -114,6 +114,10 @@ namespace Disco.Web
// Attachment PDF Thumbnail Update
if (PreviousVersion != null && PreviousVersion < new Version(2, 2, 0, 0))
Services.Documents.AttachmentImport.ThumbnailUpdateTask.SetRequired(Database);
+
+ // AD Device Description Update
+ if (PreviousVersion != null && PreviousVersion < new Version(2, 2, 16281, 0))
+ Services.Interop.ActiveDirectory.ADDeviceDescriptionUpdateTask.SetRequired(Database);
}
public static void DisposeEnvironment()
diff --git a/Disco.Web/Areas/API/Controllers/SystemController.cs b/Disco.Web/Areas/API/Controllers/SystemController.cs
index b610b3c2..fe58f457 100644
--- a/Disco.Web/Areas/API/Controllers/SystemController.cs
+++ b/Disco.Web/Areas/API/Controllers/SystemController.cs
@@ -33,6 +33,14 @@ namespace Disco.Web.Areas.API.Controllers
return RedirectToAction(MVC.Config.Logging.TaskStatus(ts.SessionId));
}
+ [DiscoAuthorize(Claims.DiscoAdminAccount)]
+ public virtual ActionResult UpdateADDeviceDescriptions()
+ {
+ var ts = Disco.Services.Interop.ActiveDirectory.ADDeviceDescriptionUpdateTask.ScheduleImmediately();
+ ts.SetFinishedUrl(Url.Action(MVC.Config.SystemConfig.Index()));
+ return RedirectToAction(MVC.Config.Logging.TaskStatus(ts.SessionId));
+ }
+
[DiscoAuthorize(Claims.Config.System.Show)]
public virtual ActionResult UpdateCheck()
{
diff --git a/Disco.Web/Extensions/T4MVC/API.SystemController.generated.cs b/Disco.Web/Extensions/T4MVC/API.SystemController.generated.cs
index f0430318..ddb3cdd7 100644
--- a/Disco.Web/Extensions/T4MVC/API.SystemController.generated.cs
+++ b/Disco.Web/Extensions/T4MVC/API.SystemController.generated.cs
@@ -147,6 +147,7 @@ namespace Disco.Web.Areas.API.Controllers
{
public readonly string UpdateLastNetworkLogonDates = "UpdateLastNetworkLogonDates";
public readonly string UpdateAttachmentThumbnails = "UpdateAttachmentThumbnails";
+ public readonly string UpdateADDeviceDescriptions = "UpdateADDeviceDescriptions";
public readonly string UpdateCheck = "UpdateCheck";
public readonly string UpdateOrganisationName = "UpdateOrganisationName";
public readonly string OrganisationLogo = "OrganisationLogo";
@@ -169,6 +170,7 @@ namespace Disco.Web.Areas.API.Controllers
{
public const string UpdateLastNetworkLogonDates = "UpdateLastNetworkLogonDates";
public const string UpdateAttachmentThumbnails = "UpdateAttachmentThumbnails";
+ public const string UpdateADDeviceDescriptions = "UpdateADDeviceDescriptions";
public const string UpdateCheck = "UpdateCheck";
public const string UpdateOrganisationName = "UpdateOrganisationName";
public const string OrganisationLogo = "OrganisationLogo";
@@ -349,6 +351,17 @@ namespace Disco.Web.Areas.API.Controllers
return callInfo;
}
+ [NonAction]
+ partial void UpdateADDeviceDescriptionsOverride(T4MVC_System_Web_Mvc_ActionResult callInfo);
+
+ [NonAction]
+ public override System.Web.Mvc.ActionResult UpdateADDeviceDescriptions()
+ {
+ var callInfo = new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.UpdateADDeviceDescriptions);
+ UpdateADDeviceDescriptionsOverride(callInfo);
+ return callInfo;
+ }
+
[NonAction]
partial void UpdateCheckOverride(T4MVC_System_Web_Mvc_ActionResult callInfo);