From 486ce17857cf52ad9f86708f09d270ea865f1c0a Mon Sep 17 00:00:00 2001 From: Gary Sharp Date: Sun, 29 Nov 2020 16:46:20 +1100 Subject: [PATCH] Feature: Export device hardware audit information Allows processor, memory, disk and network adapter information to be included in device exports --- .../Exporting/DeviceExportFieldMetadata.cs | 16 +- .../Devices/Exporting/DeviceExportOptions.cs | 46 ++-- .../Devices/Exporting/DeviceExportRecord.cs | 15 +- .../Devices/Exporting/DeviceExport.cs | 249 ++++++++++++------ 4 files changed, 215 insertions(+), 111 deletions(-) diff --git a/Disco.Models/Services/Devices/Exporting/DeviceExportFieldMetadata.cs b/Disco.Models/Services/Devices/Exporting/DeviceExportFieldMetadata.cs index fd8384cc..aeaae448 100644 --- a/Disco.Models/Services/Devices/Exporting/DeviceExportFieldMetadata.cs +++ b/Disco.Models/Services/Devices/Exporting/DeviceExportFieldMetadata.cs @@ -10,12 +10,18 @@ namespace Disco.Models.Services.Devices.Exporting public Func Accessor { get; set; } public Func CsvEncoder { get; set; } - public DeviceExportFieldMetadata(string Name, Type ValueType, Func Accessor, Func CsvEncoder) + public DeviceExportFieldMetadata(string name, Type valueType, Func accessor, Func csvEncoder) { - this.Name = Name; - this.ValueType = ValueType; - this.Accessor = Accessor; - this.CsvEncoder = CsvEncoder; + Name = name; + ValueType = valueType; + Accessor = accessor; + CsvEncoder = csvEncoder; + } + + public DeviceExportFieldMetadata(string name, string columnName, Type valueType, Func accessor, Func csvEncoder) + : this(name, valueType, accessor, csvEncoder) + { + ColumnName = columnName; } } } diff --git a/Disco.Models/Services/Devices/Exporting/DeviceExportOptions.cs b/Disco.Models/Services/Devices/Exporting/DeviceExportOptions.cs index aebb3e30..992a2f5b 100644 --- a/Disco.Models/Services/Devices/Exporting/DeviceExportOptions.cs +++ b/Disco.Models/Services/Devices/Exporting/DeviceExportOptions.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.ComponentModel.DataAnnotations; namespace Disco.Models.Services.Devices.Exporting { @@ -38,18 +33,6 @@ namespace Disco.Models.Services.Devices.Exporting [Display(ShortName = "Device", Name = "Decommissioned Reason", Description = "The reason the device was decommissioned")] public bool DeviceDecommissionedReason { get; set; } - // Details - [Display(ShortName = "Details", Name = "LAN MAC Address", Description = "The LAN MAC Address associated with the device")] - public bool DetailLanMacAddress { get; set; } - [Display(ShortName = "Details", Name = "Wireless LAN MAC Address", Description = "The Wireless LAN MAC Address associated with the device")] - public bool DetailWLanMacAddress { get; set; } - [Display(ShortName = "Details", Name = "AC Adapter", Description = "The AC Adapter associated with the device")] - public bool DetailACAdapter { get; set; } - [Display(ShortName = "Details", Name = "Battery", Description = "The Battery associated with the device")] - public bool DetailBattery { get; set; } - [Display(ShortName = "Details", Name = "Keyboard", Description = "The Keyboard associated with the device")] - public bool DetailKeyboard { get; set; } - // Model [Display(ShortName = "Model", Name = "Identifier", Description = "The identifier of the device model associated with the device")] public bool ModelId { get; set; } @@ -117,15 +100,26 @@ namespace Disco.Models.Services.Devices.Exporting public bool AttachmentsCount { get; set; } // Certificates - [Display(ShortName = "Certificates", Name = "Name", Description = "The name of the most recently assigned active certificate associated with the device")] - public bool CertificateName { get; set; } - [Display(ShortName = "Certificates", Name = "Allocated Date", Description = "The allocated date of the most recently assigned active certificate associated with the device")] - public bool CertificateAllocatedDate { get; set; } - [Display(ShortName = "Certificates", Name = "Expiration Date", Description = "The expiration date of the most recently assigned active certificate associated with the device")] - public bool CertificateExpirationDate { get; set; } - [Display(ShortName = "Certificates", Name = "Provider Id", Description = "The provider identifier of the most recently assigned active certificate associated with the device")] - public bool CertificateProviderId { get; set; } + [Display(ShortName = "Certificates", Name = "Certificates", Description = "The assigned active certificates associated with the device")] + public bool Certificates { get; set; } + // Details + [Display(ShortName = "Details", Name = "Processors", Description = "The CPU Processors associated with the device")] + public bool DetailProcessors { get; set; } + [Display(ShortName = "Details", Name = "Memory", Description = "The Memory/RAM associated with the device")] + public bool DetailMemory { get; set; } + [Display(ShortName = "Details", Name = "Disk Drives", Description = "The Disk Drives associated with the device")] + public bool DetailDiskDrives { get; set; } + [Display(ShortName = "Details", Name = "LAN Adapters", Description = "The LAN Adapters associated with the device")] + public bool DetailLanAdapters { get; set; } + [Display(ShortName = "Details", Name = "Wireless LAN Adapters", Description = "The Wireless LAN Adapters associated with the device")] + public bool DetailWLanAdapters { get; set; } + [Display(ShortName = "Details", Name = "AC Adapter", Description = "The AC Adapter associated with the device")] + public bool DetailACAdapter { get; set; } + [Display(ShortName = "Details", Name = "Battery", Description = "The Battery associated with the device")] + public bool DetailBattery { get; set; } + [Display(ShortName = "Details", Name = "Keyboard", Description = "The Keyboard associated with the device")] + public bool DetailKeyboard { get; set; } public static DeviceExportOptions DefaultOptions() { diff --git a/Disco.Models/Services/Devices/Exporting/DeviceExportRecord.cs b/Disco.Models/Services/Devices/Exporting/DeviceExportRecord.cs index 05e37e5c..8b6c920e 100644 --- a/Disco.Models/Services/Devices/Exporting/DeviceExportRecord.cs +++ b/Disco.Models/Services/Devices/Exporting/DeviceExportRecord.cs @@ -1,4 +1,5 @@ -using Disco.Models.Repository; +using Disco.Models.ClientServices.EnrolmentInformation; +using Disco.Models.Repository; using System; using System.Collections.Generic; @@ -9,8 +10,14 @@ namespace Disco.Models.Services.Devices.Exporting public Device Device { get; set; } // Details - public IEnumerable DeviceDetails { get; set; } - + public IList DeviceDetails { get; set; } + public List DeviceDetailProcessors { get; set; } + public List DeviceDetailPhysicalMemory { get; set; } + public List DeviceDetailDiskDrives { get; set; } + public List DeviceDetailNetworkAdapters { get; set; } + public List DeviceDetailLanMacAddresses { get; set; } + public List DeviceDetailWlanMacAddresses { get; set; } + // Model public int? ModelId { get; set; } public string ModelDescription { get; set; } @@ -46,6 +53,6 @@ namespace Disco.Models.Services.Devices.Exporting public int AttachmentsCount { get; set; } // Certificates - public DeviceCertificate DeviceCertificate { get; set; } + public IEnumerable DeviceCertificates { get; set; } } } diff --git a/Disco.Services/Devices/Exporting/DeviceExport.cs b/Disco.Services/Devices/Exporting/DeviceExport.cs index e65b37a9..2d514168 100644 --- a/Disco.Services/Devices/Exporting/DeviceExport.cs +++ b/Disco.Services/Devices/Exporting/DeviceExport.cs @@ -19,8 +19,32 @@ namespace Disco.Services.Devices.Exporting public static DeviceExportResult GenerateExport(DiscoDataContext Database, IQueryable Devices, DeviceExportOptions Options, IScheduledTaskStatus TaskStatus) { - TaskStatus.UpdateStatus(15, "Building metadata and database query"); - var metadata = Options.BuildMetadata(); + + TaskStatus.UpdateStatus(15, "Extracting records from the database"); + + var records = BuildRecords(Devices).ToList(); + // materialize device details + records.ForEach(r => + { + if (Options.DetailProcessors) + r.DeviceDetailProcessors = r.DeviceDetails.Processors(); + if (Options.DetailMemory) + r.DeviceDetailPhysicalMemory = r.DeviceDetails.PhysicalMemory(); + if (Options.DetailDiskDrives) + r.DeviceDetailDiskDrives = r.DeviceDetails.DiskDrives(); + if (Options.DetailLanAdapters || Options.DetailWLanAdapters) + { + r.DeviceDetailNetworkAdapters = r.DeviceDetails.NetworkAdapters(); + if (r.DeviceDetailNetworkAdapters == null) + { + r.DeviceDetailLanMacAddresses = r.DeviceDetails.LanMacAddress()?.Split(';').Select(a => a.Trim()).ToList(); + r.DeviceDetailWlanMacAddresses = r.DeviceDetails.WLanMacAddress()?.Split(';').Select(a => a.Trim()).ToList(); + } + } + }); + + TaskStatus.UpdateStatus(40, "Building metadata and database query"); + var metadata = Options.BuildMetadata(records); if (metadata.Count == 0) throw new ArgumentException("At least one export field must be specified", "Options"); @@ -32,7 +56,7 @@ namespace Disco.Services.Devices.Exporting Options.AssignedUserPhoneNumber || Options.AssignedUserEmailAddress) { - TaskStatus.UpdateStatus(20, "Updating Assigned User details"); + TaskStatus.UpdateStatus(45, "Updating Assigned User details"); var users = Devices.Where(d => d.AssignedUserId != null).Select(d => d.AssignedUser).Distinct().ToList(); users.Select((user, index) => @@ -49,12 +73,12 @@ namespace Disco.Services.Devices.Exporting // Update Last Network Logon Date if (Options.DeviceLastNetworkLogon) { - TaskStatus.UpdateStatus(40, "Updating device last network logon dates"); + TaskStatus.UpdateStatus(50, "Updating device last network logon dates"); try { TaskStatus.IgnoreCurrentProcessChanges = true; - TaskStatus.ProgressMultiplier = (double)20 / 100; - TaskStatus.ProgressOffset = 40; + TaskStatus.ProgressMultiplier = (double)30 / 100; + TaskStatus.ProgressOffset = 50; Interop.ActiveDirectory.ADNetworkLogonDatesUpdateTask.UpdateLastNetworkLogonDates(Database, TaskStatus); Database.SaveChanges(); @@ -66,10 +90,6 @@ namespace Disco.Services.Devices.Exporting catch (Exception) { } // Ignore Errors } - TaskStatus.UpdateStatus(60, "Extracting records from the database"); - - var records = BuildRecords(Devices).ToList(); - var stream = new MemoryStream(); TaskStatus.UpdateStatus(80, string.Format("Formatting {0} records for export", records.Count)); @@ -173,19 +193,11 @@ namespace Disco.Services.Devices.Exporting private static IEnumerable BuildRecords(IQueryable Devices) { - var deviceDetailHardwareKeys = new List { - DeviceDetail.HardwareKeyLanMacAddress, - DeviceDetail.HardwareKeyWLanMacAddress, - DeviceDetail.HardwareKeyACAdapter, - DeviceDetail.HardwareKeyBattery, - DeviceDetail.HardwareKeyKeyboard - }; - return Devices.Select(d => new DeviceExportRecord() { Device = d, - DeviceDetails = d.DeviceDetails.Where(dd => dd.Scope == DeviceDetail.ScopeHardware && deviceDetailHardwareKeys.Contains(dd.Key)), + DeviceDetails = d.DeviceDetails, ModelId = d.DeviceModelId, ModelDescription = d.DeviceModel.Description, @@ -215,13 +227,20 @@ namespace Disco.Services.Devices.Exporting AttachmentsCount = d.DeviceAttachments.Count(), - DeviceCertificate = d.DeviceCertificates.Where(dc => dc.Enabled).FirstOrDefault() + DeviceCertificates = d.DeviceCertificates.Where(dc => dc.Enabled).OrderByDescending(dc => dc.AllocatedDate) }); } - private static List BuildMetadata(this DeviceExportOptions Options) + private static List BuildMetadata(this DeviceExportOptions options, List records) { - var allAssessors = BuildRecordAccessors().ToList(); + var processorMaxCount = Math.Max(1, records.Max(r => r.DeviceDetailProcessors?.Count ?? 0)); + var memoryMaxCount = Math.Max(1, records.Max(r => r.DeviceDetailPhysicalMemory?.Count ?? 0)); + var diskDriveMaxCount = Math.Max(1, records.Max(r => r.DeviceDetailDiskDrives?.Count ?? 0)); + var lanAdapterMaxCount = Math.Max(1, records.Max(r => r.DeviceDetailNetworkAdapters?.Count(a => !a.IsWlanAdapter) ?? r.DeviceDetailLanMacAddresses?.Count ?? 0)); + var wlanAdapterMaxCount = Math.Max(1, records.Max(r => r.DeviceDetailNetworkAdapters?.Count(a => a.IsWlanAdapter) ?? r.DeviceDetailWlanMacAddresses?.Count ?? 0)); + var certificateMaxCount = Math.Max(1, records.Max(r => r.DeviceCertificates?.Count() ?? 0)); + + var allAssessors = BuildRecordAccessors(processorMaxCount, memoryMaxCount, diskDriveMaxCount, lanAdapterMaxCount, wlanAdapterMaxCount, certificateMaxCount); return typeof(DeviceExportOptions).GetProperties() .Where(p => p.PropertyType == typeof(bool)) @@ -230,16 +249,20 @@ namespace Disco.Services.Devices.Exporting property = p, details = (DisplayAttribute)p.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() }) - .Where(p => p.details != null && (bool)p.property.GetValue(Options)) - .Select(p => + .Where(p => p.details != null && (bool)p.property.GetValue(options)) + .SelectMany(p => { - var fieldMetadata = allAssessors.First(i => i.Name == p.property.Name); - fieldMetadata.ColumnName = (p.details.ShortName == "Device" || p.details.ShortName == "Details") ? p.details.Name : $"{p.details.ShortName} {p.details.Name}"; + var fieldMetadata = allAssessors[p.property.Name]; + fieldMetadata.ForEach(f => + { + if (f.ColumnName == null) + f.ColumnName = (p.details.ShortName == "Device" || p.details.ShortName == "Details") ? p.details.Name : $"{p.details.ShortName} {p.details.Name}"; + }); return fieldMetadata; }).ToList(); } - private static IEnumerable BuildRecordAccessors() + private static Dictionary> BuildRecordAccessors(int processorMaxCount, int memoryMaxCount, int diskDriveMaxCount, int lanAdapterMaxCount, int wlanAdapterMaxCount, int certificateMaxCount) { const string DateFormat = "yyyy-MM-dd"; const string DateTimeFormat = DateFormat + " HH:mm:ss"; @@ -252,70 +275,144 @@ namespace Disco.Services.Devices.Exporting Func csvNullableDateEncoded = (o) => ((DateTime?)o).HasValue ? csvDateEncoded(o) : null; Func csvNullableDateTimeEncoded = (o) => ((DateTime?)o).HasValue ? csvDateTimeEncoded(o) : null; - // Device - yield return new DeviceExportFieldMetadata("DeviceSerialNumber", typeof(string), r => r.Device.SerialNumber, csvStringEncoded); - yield return new DeviceExportFieldMetadata("DeviceAssetNumber", typeof(string), r => r.Device.AssetNumber, csvStringEncoded); - yield return new DeviceExportFieldMetadata("DeviceLocation", typeof(string), r => r.Device.Location, csvStringEncoded); - yield return new DeviceExportFieldMetadata("DeviceComputerName", typeof(string), r => r.Device.DeviceDomainId, csvStringEncoded); - yield return new DeviceExportFieldMetadata("DeviceLastNetworkLogon", typeof(DateTime), r => r.Device.LastNetworkLogonDate, csvNullableDateTimeEncoded); - yield return new DeviceExportFieldMetadata("DeviceCreatedDate", typeof(DateTime), r => r.Device.CreatedDate, csvDateTimeEncoded); - yield return new DeviceExportFieldMetadata("DeviceFirstEnrolledDate", typeof(DateTime), r => r.Device.EnrolledDate, csvNullableDateTimeEncoded); - yield return new DeviceExportFieldMetadata("DeviceLastEnrolledDate", typeof(DateTime), r => r.Device.LastEnrolDate, csvNullableDateTimeEncoded); - yield return new DeviceExportFieldMetadata("DeviceAllowUnauthenticatedEnrol", typeof(bool), r => r.Device.AllowUnauthenticatedEnrol, csvToStringEncoded); - yield return new DeviceExportFieldMetadata("DeviceDecommissionedDate", typeof(DateTime), r => r.Device.DecommissionedDate, csvNullableDateTimeEncoded); - yield return new DeviceExportFieldMetadata("DeviceDecommissionedReason", typeof(string), r => r.Device.DecommissionReason, csvToStringEncoded); + var metadata = new Dictionary>(); - // Details - yield return new DeviceExportFieldMetadata("DetailLanMacAddress", typeof(string), r => r.DeviceDetails.Where(dd => dd.Key == DeviceDetail.HardwareKeyLanMacAddress).Select(dd => dd.Value).FirstOrDefault(), csvStringEncoded); - yield return new DeviceExportFieldMetadata("DetailWLanMacAddress", typeof(string), r => r.DeviceDetails.Where(dd => dd.Key == DeviceDetail.HardwareKeyWLanMacAddress).Select(dd => dd.Value).FirstOrDefault(), csvStringEncoded); - yield return new DeviceExportFieldMetadata("DetailACAdapter", typeof(string), r => r.DeviceDetails.Where(dd => dd.Key == DeviceDetail.HardwareKeyACAdapter).Select(dd => dd.Value).FirstOrDefault(), csvStringEncoded); - yield return new DeviceExportFieldMetadata("DetailBattery", typeof(string), r => r.DeviceDetails.Where(dd => dd.Key == DeviceDetail.HardwareKeyBattery).Select(dd => dd.Value).FirstOrDefault(), csvStringEncoded); - yield return new DeviceExportFieldMetadata("DetailKeyboard", typeof(string), r => r.DeviceDetails.Where(dd => dd.Key == DeviceDetail.HardwareKeyKeyboard).Select(dd => dd.Value).FirstOrDefault(), csvStringEncoded); + // Device + metadata.Add(nameof(DeviceExportOptions.DeviceSerialNumber), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.DeviceSerialNumber), typeof(string), r => r.Device.SerialNumber, csvStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.DeviceAssetNumber), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.DeviceAssetNumber), typeof(string), r => r.Device.AssetNumber, csvStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.DeviceLocation), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.DeviceLocation), typeof(string), r => r.Device.Location, csvStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.DeviceComputerName), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.DeviceComputerName), typeof(string), r => r.Device.DeviceDomainId, csvStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.DeviceLastNetworkLogon), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.DeviceLastNetworkLogon), typeof(DateTime), r => r.Device.LastNetworkLogonDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(DeviceExportOptions.DeviceCreatedDate), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.DeviceCreatedDate), typeof(DateTime), r => r.Device.CreatedDate, csvDateTimeEncoded) }); + metadata.Add(nameof(DeviceExportOptions.DeviceFirstEnrolledDate), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.DeviceFirstEnrolledDate), typeof(DateTime), r => r.Device.EnrolledDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(DeviceExportOptions.DeviceLastEnrolledDate), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.DeviceLastEnrolledDate), typeof(DateTime), r => r.Device.LastEnrolDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(DeviceExportOptions.DeviceAllowUnauthenticatedEnrol), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.DeviceAllowUnauthenticatedEnrol), typeof(bool), r => r.Device.AllowUnauthenticatedEnrol, csvToStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.DeviceDecommissionedDate), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.DeviceDecommissionedDate), typeof(DateTime), r => r.Device.DecommissionedDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(DeviceExportOptions.DeviceDecommissionedReason), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.DeviceDecommissionedReason), typeof(string), r => r.Device.DecommissionReason, csvToStringEncoded) }); // Model - yield return new DeviceExportFieldMetadata("ModelId", typeof(int), r => r.ModelId, csvToStringEncoded); - yield return new DeviceExportFieldMetadata("ModelDescription", typeof(string), r => r.ModelDescription, csvStringEncoded); - yield return new DeviceExportFieldMetadata("ModelManufacturer", typeof(string), r => r.ModelManufacturer, csvStringEncoded); - yield return new DeviceExportFieldMetadata("ModelModel", typeof(string), r => r.ModelModel, csvStringEncoded); - yield return new DeviceExportFieldMetadata("ModelType", typeof(string), r => r.ModelType, csvStringEncoded); + metadata.Add(nameof(DeviceExportOptions.ModelId), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.ModelId), typeof(int), r => r.ModelId, csvToStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.ModelDescription), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.ModelDescription), typeof(string), r => r.ModelDescription, csvStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.ModelManufacturer), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.ModelManufacturer), typeof(string), r => r.ModelManufacturer, csvStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.ModelModel), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.ModelModel), typeof(string), r => r.ModelModel, csvStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.ModelType), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.ModelType), typeof(string), r => r.ModelType, csvStringEncoded) }); // Batch - yield return new DeviceExportFieldMetadata("BatchId", typeof(int), r => r.BatchId, csvToStringEncoded); - yield return new DeviceExportFieldMetadata("BatchName", typeof(string), r => r.BatchName, csvStringEncoded); - yield return new DeviceExportFieldMetadata("BatchPurchaseDate", typeof(DateTime), r => r.BatchPurchaseDate, csvNullableDateEncoded); - yield return new DeviceExportFieldMetadata("BatchSupplier", typeof(string), r => r.BatchSupplier, csvStringEncoded); - yield return new DeviceExportFieldMetadata("BatchUnitCost", typeof(decimal), r => r.BatchUnitCost, csvCurrencyEncoded); - yield return new DeviceExportFieldMetadata("BatchWarrantyValidUntilDate", typeof(DateTime), r => r.BatchWarrantyValidUntilDate, csvNullableDateEncoded); - yield return new DeviceExportFieldMetadata("BatchInsuredDate", typeof(DateTime), r => r.BatchInsuredDate, csvNullableDateEncoded); - yield return new DeviceExportFieldMetadata("BatchInsuranceSupplier", typeof(string), r => r.BatchInsuranceSupplier, csvStringEncoded); - yield return new DeviceExportFieldMetadata("BatchInsuredUntilDate", typeof(DateTime), r => r.BatchInsuredUntilDate, csvNullableDateEncoded); + metadata.Add(nameof(DeviceExportOptions.BatchId), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.BatchId), typeof(int), r => r.BatchId, csvToStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.BatchName), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.BatchName), typeof(string), r => r.BatchName, csvStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.BatchPurchaseDate), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.BatchPurchaseDate), typeof(DateTime), r => r.BatchPurchaseDate, csvNullableDateEncoded) }); + metadata.Add(nameof(DeviceExportOptions.BatchSupplier), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.BatchSupplier), typeof(string), r => r.BatchSupplier, csvStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.BatchUnitCost), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.BatchUnitCost), typeof(decimal), r => r.BatchUnitCost, csvCurrencyEncoded) }); + metadata.Add(nameof(DeviceExportOptions.BatchWarrantyValidUntilDate), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.BatchWarrantyValidUntilDate), typeof(DateTime), r => r.BatchWarrantyValidUntilDate, csvNullableDateEncoded) }); + metadata.Add(nameof(DeviceExportOptions.BatchInsuredDate), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.BatchInsuredDate), typeof(DateTime), r => r.BatchInsuredDate, csvNullableDateEncoded) }); + metadata.Add(nameof(DeviceExportOptions.BatchInsuranceSupplier), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.BatchInsuranceSupplier), typeof(string), r => r.BatchInsuranceSupplier, csvStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.BatchInsuredUntilDate), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.BatchInsuredUntilDate), typeof(DateTime), r => r.BatchInsuredUntilDate, csvNullableDateEncoded) }); // Profile - yield return new DeviceExportFieldMetadata("ProfileId", typeof(int), r => r.ProfileId, csvToStringEncoded); - yield return new DeviceExportFieldMetadata("ProfileName", typeof(string), r => r.ProfileName, csvStringEncoded); - yield return new DeviceExportFieldMetadata("ProfileShortName", typeof(string), r => r.ProfileShortName, csvStringEncoded); + metadata.Add(nameof(DeviceExportOptions.ProfileId), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.ProfileId), typeof(int), r => r.ProfileId, csvToStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.ProfileName), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.ProfileName), typeof(string), r => r.ProfileName, csvStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.ProfileShortName), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.ProfileShortName), typeof(string), r => r.ProfileShortName, csvStringEncoded) }); // User - yield return new DeviceExportFieldMetadata("AssignedUserId", typeof(string), r => r.AssignedUser?.UserId, csvStringEncoded); - yield return new DeviceExportFieldMetadata("AssignedUserDate", typeof(DateTime), r => r.DeviceUserAssignment?.AssignedDate, csvNullableDateTimeEncoded); - yield return new DeviceExportFieldMetadata("AssignedUserDisplayName", typeof(string), r => r.AssignedUser?.DisplayName, csvStringEncoded); - yield return new DeviceExportFieldMetadata("AssignedUserSurname", typeof(string), r => r.AssignedUser?.Surname, csvStringEncoded); - yield return new DeviceExportFieldMetadata("AssignedUserGivenName", typeof(string), r => r.AssignedUser?.GivenName, csvStringEncoded); - yield return new DeviceExportFieldMetadata("AssignedUserPhoneNumber", typeof(string), r => r.AssignedUser?.PhoneNumber, csvStringEncoded); - yield return new DeviceExportFieldMetadata("AssignedUserEmailAddress", typeof(string), r => r.AssignedUser?.EmailAddress, csvStringEncoded); + metadata.Add(nameof(DeviceExportOptions.AssignedUserId), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.AssignedUserId), typeof(string), r => r.AssignedUser?.UserId, csvStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.AssignedUserDate), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.AssignedUserDate), typeof(DateTime), r => r.DeviceUserAssignment?.AssignedDate, csvNullableDateTimeEncoded) }); + metadata.Add(nameof(DeviceExportOptions.AssignedUserDisplayName), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.AssignedUserDisplayName), typeof(string), r => r.AssignedUser?.DisplayName, csvStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.AssignedUserSurname), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.AssignedUserSurname), typeof(string), r => r.AssignedUser?.Surname, csvStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.AssignedUserGivenName), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.AssignedUserGivenName), typeof(string), r => r.AssignedUser?.GivenName, csvStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.AssignedUserPhoneNumber), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.AssignedUserPhoneNumber), typeof(string), r => r.AssignedUser?.PhoneNumber, csvStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.AssignedUserEmailAddress), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.AssignedUserEmailAddress), typeof(string), r => r.AssignedUser?.EmailAddress, csvStringEncoded) }); // Jobs - yield return new DeviceExportFieldMetadata("JobsTotalCount", typeof(int), r => r.JobsTotalCount, csvToStringEncoded); - yield return new DeviceExportFieldMetadata("JobsOpenCount", typeof(int), r => r.JobsOpenCount, csvToStringEncoded); + metadata.Add(nameof(DeviceExportOptions.JobsTotalCount), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.JobsTotalCount), typeof(int), r => r.JobsTotalCount, csvToStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.JobsOpenCount), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.JobsOpenCount), typeof(int), r => r.JobsOpenCount, csvToStringEncoded) }); // Attachments - yield return new DeviceExportFieldMetadata("AttachmentsCount", typeof(int), r => r.AttachmentsCount, csvToStringEncoded); + metadata.Add(nameof(DeviceExportOptions.AttachmentsCount), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.AttachmentsCount), typeof(int), r => r.AttachmentsCount, csvToStringEncoded) }); // Certificates - yield return new DeviceExportFieldMetadata("CertificateName", typeof(string), r => r.DeviceCertificate?.Name, csvStringEncoded); - yield return new DeviceExportFieldMetadata("CertificateAllocatedDate", typeof(DateTime), r => r.DeviceCertificate?.AllocatedDate, csvNullableDateTimeEncoded); - yield return new DeviceExportFieldMetadata("CertificateExpirationDate", typeof(DateTime), r => r.DeviceCertificate?.ExpirationDate, csvNullableDateTimeEncoded); - yield return new DeviceExportFieldMetadata("CertificateProviderId", typeof(string), r => r.DeviceCertificate?.ProviderId, csvStringEncoded); + var certificateFields = new List(certificateMaxCount * 4); + for (int i = 0; i < certificateMaxCount; i++) + { + var v = i; + var index = i + 1; + certificateFields.Add(new DeviceExportFieldMetadata($"Certificate{index}Name", $"Certificate {index} Name", typeof(string), r => r.DeviceCertificates.Skip(v).FirstOrDefault()?.Name, csvStringEncoded)); + certificateFields.Add(new DeviceExportFieldMetadata($"Certificate{index}AllocationDate", $"Certificate {index} Allocation Date", typeof(DateTime), r => r.DeviceCertificates.Skip(v).FirstOrDefault()?.AllocatedDate, csvNullableDateTimeEncoded)); + certificateFields.Add(new DeviceExportFieldMetadata($"Certificate{index}ExpirationDate", $"Certificate {index} Expiration Date", typeof(DateTime), r => r.DeviceCertificates.Skip(v).FirstOrDefault()?.ExpirationDate, csvNullableDateTimeEncoded)); + certificateFields.Add(new DeviceExportFieldMetadata($"Certificate{index}ProviderId", $"Certificate {index} Provider Id", typeof(string), r => r.DeviceCertificates.Skip(v).FirstOrDefault()?.ProviderId, csvStringEncoded)); + } + metadata.Add(nameof(DeviceExportOptions.Certificates), certificateFields); + + // Details + var processorFields = new List(processorMaxCount * 6); + for (int i = 0; i < processorMaxCount; i++) + { + var v = i; + var index = i + 1; + processorFields.Add(new DeviceExportFieldMetadata($"Processor{index}Name", $"Processor {index} Name", typeof(string), r => r.DeviceDetailProcessors?.Skip(v).FirstOrDefault()?.Name, csvStringEncoded)); + processorFields.Add(new DeviceExportFieldMetadata($"Processor{index}Description", $"Processor {index} Description", typeof(string), r => r.DeviceDetailProcessors?.Skip(v).FirstOrDefault()?.Description, csvStringEncoded)); + processorFields.Add(new DeviceExportFieldMetadata($"Processor{index}Architecture", $"Processor {index} Architecture", typeof(string), r => r.DeviceDetailProcessors?.Skip(v).FirstOrDefault()?.Architecture, csvStringEncoded)); + processorFields.Add(new DeviceExportFieldMetadata($"Processor{index}ClockSpeed", $"Processor {index} Clock Speed", typeof(string), r => r.DeviceDetailProcessors?.Skip(v).FirstOrDefault()?.MaxClockSpeedFriendly(), csvStringEncoded)); + processorFields.Add(new DeviceExportFieldMetadata($"Processor{index}Cores", $"Processor {index} Cores", typeof(int), r => r.DeviceDetailProcessors?.Skip(v).FirstOrDefault()?.NumberOfCores, csvToStringEncoded)); + processorFields.Add(new DeviceExportFieldMetadata($"Processor{index}LogicalProcessors", $"Processor {index} Logical Processors", typeof(int), r => r.DeviceDetailProcessors?.Skip(v).FirstOrDefault()?.NumberOfLogicalProcessors, csvToStringEncoded)); + } + metadata.Add(nameof(DeviceExportOptions.DetailProcessors), processorFields); + var memoryFields = new List((memoryMaxCount * 6) + 1); + memoryFields.Add(new DeviceExportFieldMetadata($"MemoryTotalCapacity", $"Memory Total Capacity", typeof(string), r => MeasurementUnitExtensions.ByteSizeToFriendly((ulong)(r.DeviceDetailPhysicalMemory?.Sum(m => (long)m.Capacity) ?? 0L)), csvStringEncoded)); + for (int i = 0; i < memoryMaxCount; i++) + { + var v = i; + var index = i + 1; + memoryFields.Add(new DeviceExportFieldMetadata($"Memory{index}Location", $"Memory {index} Location", typeof(string), r => r.DeviceDetailPhysicalMemory?.Skip(v).FirstOrDefault()?.DeviceLocator, csvStringEncoded)); + memoryFields.Add(new DeviceExportFieldMetadata($"Memory{index}Manufacturer", $"Memory {index} Manufacturer", typeof(string), r => r.DeviceDetailPhysicalMemory?.Skip(v).FirstOrDefault()?.Manufacturer, csvStringEncoded)); + memoryFields.Add(new DeviceExportFieldMetadata($"Memory{index}PartNumber", $"Memory {index} Part Number", typeof(string), r => r.DeviceDetailPhysicalMemory?.Skip(v).FirstOrDefault()?.PartNumber, csvStringEncoded)); + memoryFields.Add(new DeviceExportFieldMetadata($"Memory{index}SerialNumber", $"Memory {index} Serial Number", typeof(string), r => r.DeviceDetailPhysicalMemory?.Skip(v).FirstOrDefault()?.SerialNumber, csvStringEncoded)); + memoryFields.Add(new DeviceExportFieldMetadata($"Memory{index}Capacity", $"Memory {index} Capacity", typeof(string), r => r.DeviceDetailPhysicalMemory?.Skip(v).FirstOrDefault()?.CapacityFriendly(), csvStringEncoded)); + memoryFields.Add(new DeviceExportFieldMetadata($"Memory{index}ConfiguredClockSpeed", $"Memory {index} Clock Speed", typeof(int), r => r.DeviceDetailPhysicalMemory?.Skip(v).FirstOrDefault()?.ConfiguredClockSpeed, csvToStringEncoded)); + } + metadata.Add(nameof(DeviceExportOptions.DetailMemory), memoryFields); + var diskFields = new List(diskDriveMaxCount * 6); + for (int i = 0; i < diskDriveMaxCount; i++) + { + var v = i; + var index = i + 1; + diskFields.Add(new DeviceExportFieldMetadata($"Disk{index}Manufacturer", $"Disk {index} Manufacturer", typeof(string), r => r.DeviceDetailDiskDrives?.Skip(v).FirstOrDefault()?.Manufacturer, csvStringEncoded)); + diskFields.Add(new DeviceExportFieldMetadata($"Disk{index}Model", $"Disk {index} Model", typeof(string), r => r.DeviceDetailDiskDrives?.Skip(v).FirstOrDefault()?.Model, csvStringEncoded)); + diskFields.Add(new DeviceExportFieldMetadata($"Disk{index}SerialNumber", $"Disk {index} Serial Number", typeof(string), r => r.DeviceDetailDiskDrives?.Skip(v).FirstOrDefault()?.SerialNumber, csvStringEncoded)); + diskFields.Add(new DeviceExportFieldMetadata($"Disk{index}Firmware", $"Disk {index} Firmware", typeof(string), r => r.DeviceDetailDiskDrives?.Skip(v).FirstOrDefault()?.FirmwareRevision, csvStringEncoded)); + diskFields.Add(new DeviceExportFieldMetadata($"Disk{index}Capacity", $"Disk {index} Size", typeof(string), r => r.DeviceDetailDiskDrives?.Skip(v).FirstOrDefault()?.SizeFriendly(), csvStringEncoded)); + diskFields.Add(new DeviceExportFieldMetadata($"Disk{index}Capacity", $"Disk {index} Total Free Space", typeof(string), r => MeasurementUnitExtensions.ByteSizeToFriendly((ulong)(r.DeviceDetailDiskDrives?.Skip(v).FirstOrDefault()?.Partitions?.Sum(p => (long)(p.LogicalDisk?.FreeSpace ?? 0L)) ?? 0L)), csvStringEncoded)); + } + metadata.Add(nameof(DeviceExportOptions.DetailDiskDrives), diskFields); + var lanAdapterFields = new List(lanAdapterMaxCount * 5); + for (int i = 0; i < lanAdapterMaxCount; i++) + { + var v = i; + var index = i + 1; + lanAdapterFields.Add(new DeviceExportFieldMetadata($"LanAdapter{index}Connection", $"Lan Adapter {index} Connection", typeof(string), r => r.DeviceDetailNetworkAdapters?.Where(a => !a.IsWlanAdapter).Skip(v).FirstOrDefault()?.NetConnectionID, csvStringEncoded)); + lanAdapterFields.Add(new DeviceExportFieldMetadata($"LanAdapter{index}Manufacturer", $"Lan Adapter {index} Manufacturer", typeof(string), r => r.DeviceDetailNetworkAdapters?.Where(a => !a.IsWlanAdapter).Skip(v).FirstOrDefault()?.Manufacturer, csvStringEncoded)); + lanAdapterFields.Add(new DeviceExportFieldMetadata($"LanAdapter{index}ProductName", $"Lan Adapter {index} Product Name", typeof(string), r => r.DeviceDetailNetworkAdapters?.Where(a => !a.IsWlanAdapter).Skip(v).FirstOrDefault()?.ProductName, csvStringEncoded)); + lanAdapterFields.Add(new DeviceExportFieldMetadata($"LanAdapter{index}Speed", $"Lan Adapter {index} Speed", typeof(string), r => r.DeviceDetailNetworkAdapters?.Where(a => !a.IsWlanAdapter).Skip(v).FirstOrDefault()?.SpeedFriendly(), csvStringEncoded)); + lanAdapterFields.Add(new DeviceExportFieldMetadata($"LanAdapter{index}MacAddress", $"Lan Adapter {index} Mac Address", typeof(string), r => r.DeviceDetailNetworkAdapters?.Where(a => !a.IsWlanAdapter).Skip(v).FirstOrDefault()?.MACAddress ?? r.DeviceDetailLanMacAddresses?.Skip(v).FirstOrDefault(), csvStringEncoded)); + } + metadata.Add(nameof(DeviceExportOptions.DetailLanAdapters), lanAdapterFields); + var fields = new List(wlanAdapterMaxCount * 5); + for (int i = 0; i < wlanAdapterMaxCount; i++) + { + var v = i; + var wlanAdapterFields = i + 1; + fields.Add(new DeviceExportFieldMetadata($"WlanAdapter{wlanAdapterFields}Connection", $"Wlan Adapter {wlanAdapterFields} Connection", typeof(string), r => r.DeviceDetailNetworkAdapters?.Where(a => a.IsWlanAdapter).Skip(v).FirstOrDefault()?.NetConnectionID, csvStringEncoded)); + fields.Add(new DeviceExportFieldMetadata($"WlanAdapter{wlanAdapterFields}Manufacturer", $"Wlan Adapter {wlanAdapterFields} Manufacturer", typeof(string), r => r.DeviceDetailNetworkAdapters?.Where(a => a.IsWlanAdapter).Skip(v).FirstOrDefault()?.Manufacturer, csvStringEncoded)); + fields.Add(new DeviceExportFieldMetadata($"WlanAdapter{wlanAdapterFields}ProductName", $"Wlan Adapter {wlanAdapterFields} Product Name", typeof(string), r => r.DeviceDetailNetworkAdapters?.Where(a => a.IsWlanAdapter).Skip(v).FirstOrDefault()?.ProductName, csvStringEncoded)); + fields.Add(new DeviceExportFieldMetadata($"WlanAdapter{wlanAdapterFields}Speed", $"Wlan Adapter {wlanAdapterFields} Speed", typeof(string), r => r.DeviceDetailNetworkAdapters?.Where(a => a.IsWlanAdapter).Skip(v).FirstOrDefault()?.SpeedFriendly(), csvStringEncoded)); + fields.Add(new DeviceExportFieldMetadata($"WlanAdapter{wlanAdapterFields}MacAddress", $"Wlan Adapter {wlanAdapterFields} Mac Address", typeof(string), r => r.DeviceDetailNetworkAdapters?.Where(a => a.IsWlanAdapter).Skip(v).FirstOrDefault()?.MACAddress ?? r.DeviceDetailWlanMacAddresses?.Skip(v).FirstOrDefault(), csvStringEncoded)); + } + metadata.Add(nameof(DeviceExportOptions.DetailWLanAdapters), fields); + + metadata.Add(nameof(DeviceExportOptions.DetailACAdapter), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.DetailACAdapter), typeof(string), r => r.DeviceDetails.Where(dd => dd.Key == DeviceDetail.HardwareKeyACAdapter).Select(dd => dd.Value).FirstOrDefault(), csvStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.DetailBattery), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.DetailBattery), typeof(string), r => r.DeviceDetails.Where(dd => dd.Key == DeviceDetail.HardwareKeyBattery).Select(dd => dd.Value).FirstOrDefault(), csvStringEncoded) }); + metadata.Add(nameof(DeviceExportOptions.DetailKeyboard), new List() { new DeviceExportFieldMetadata(nameof(DeviceExportOptions.DetailKeyboard), typeof(string), r => r.DeviceDetails.Where(dd => dd.Key == DeviceDetail.HardwareKeyKeyboard).Select(dd => dd.Value).FirstOrDefault(), csvStringEncoded) }); + + return metadata; } }