diff --git a/System/AssemblyReport.md b/System/AssemblyReport.md index 827b1606..bfb33cc0 100644 --- a/System/AssemblyReport.md +++ b/System/AssemblyReport.md @@ -23,10 +23,22 @@ Interface | Description ---- | ---- [Vanara.Network.NetworkListManager.IEnumerableList](https://github.com/dahall/Vanara/search?l=C%23&q=IEnumerableList) | An enumerable list that supports a length and indexer. [Vanara.INamedEntity](https://github.com/dahall/Vanara/search?l=C%23&q=INamedEntity) | An object that exposes a name. +[Vanara.Diagnostics.IPropertyProvider](https://github.com/dahall/Vanara/search?l=C%23&q=IPropertyProvider) | A dictionary of properties. ### Classes Class | Description ---- | ---- [Vanara.Computer](https://github.com/dahall/Vanara/search?l=C%23&q=Computer) | Represents a single connected (authenticated) computer. +[Vanara.Diagnostics.Device](https://github.com/dahall/Vanara/search?l=C%23&q=Device) | A class that represents a device on a machine. +[Vanara.Diagnostics.DeviceClass](https://github.com/dahall/Vanara/search?l=C%23&q=DeviceClass) | A class that provides detail about a device setup class available on a machine. +[Vanara.Diagnostics.DeviceClassCollection](https://github.com/dahall/Vanara/search?l=C%23&q=DeviceClassCollection) | A class that provides the collection of device setup classes available on a machine. +[Vanara.Diagnostics.DeviceClass.DeviceClassProperties](https://github.com/dahall/Vanara/search?l=C%23&q=DeviceClassProperties) | Accesses properties with a device class. +[Vanara.Diagnostics.DeviceClass.DeviceClassRegProperties](https://github.com/dahall/Vanara/search?l=C%23&q=DeviceClassRegProperties) | Accesses registry properties with a device class. +[Vanara.Diagnostics.DeviceCollection](https://github.com/dahall/Vanara/search?l=C%23&q=DeviceCollection) | A class that provides the collection of devices available on a machine. +[Vanara.Diagnostics.DeviceExtensions](https://github.com/dahall/Vanara/search?l=C%23&q=DeviceExtensions) | Extension methods for SetupAPI functions and structs. +[Vanara.Diagnostics.DeviceInterface](https://github.com/dahall/Vanara/search?l=C%23&q=DeviceInterface) | +[Vanara.Diagnostics.DeviceManager](https://github.com/dahall/Vanara/search?l=C%23&q=DeviceManager) | Class to manage local and remote devices. +[Vanara.Diagnostics.Device.DeviceProperties](https://github.com/dahall/Vanara/search?l=C%23&q=DeviceProperties) | Accesses properties with a device. +[Vanara.Diagnostics.Device.DeviceRegProperties](https://github.com/dahall/Vanara/search?l=C%23&q=DeviceRegProperties) | Accesses registry properties with a device class. [Vanara.Extensions.FileInfoExtension](https://github.com/dahall/Vanara/search?l=C%23&q=FileInfoExtension) | Extension methods for `System.IO.FileSystemInfo` and derived classes to facilitate retrieval of extended properties. [Vanara.Network.InternetProxyOptions](https://github.com/dahall/Vanara/search?l=C%23&q=InternetProxyOptions) | Provides access to proxy settings for an internet connection. [Vanara.Diagnostics.IoCompletionPort](https://github.com/dahall/Vanara/search?l=C%23&q=IoCompletionPort) | Represents a system I/O completion port. diff --git a/System/Computer/DeviceManager.cs b/System/Computer/DeviceManager.cs new file mode 100644 index 00000000..6bb108b2 --- /dev/null +++ b/System/Computer/DeviceManager.cs @@ -0,0 +1,937 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Vanara.Collections; +using Vanara.Extensions; +using Vanara.InteropServices; +using Vanara.PInvoke; +using static Vanara.PInvoke.SetupAPI; + +namespace Vanara.Diagnostics +{ + /// A dictionary of properties. + /// The type of the key. + /// The type of the value. + /// + public interface IPropertyProvider : IDictionary + { + } + + /// Extension methods for SetupAPI functions and structs. + public static class DeviceExtensions + { + /// + /// Trys to get friendly name for the property key. If not available via PSGetNameFromPropertyKey, then the name of the + /// defined field is returned (i.e. "DEVPKEY_Device_Class"). If not available, then the Guid and ID are returned. + /// + /// The property key. + /// The best string representation available. + public static string LookupName(this DEVPROPKEY propKey) + { + if (PropSys.PSGetNameFromPropertyKey(new Ole32.PROPERTYKEY(propKey.fmtid, propKey.pid), out var str).Succeeded) + return str; + return LookupField(propKey)?.Name ?? $"{propKey.fmtid:B}[{propKey.pid}]"; + } + + internal static IEnumerable GetCorrespondingTypes(this DEVPROPKEY propKey) + { + var fi = LookupField(propKey); + if (fi is null) return Enumerable.Empty(); + return fi.GetCustomAttributes().Select(a => a.TypeRef); + } + + internal static System.Reflection.FieldInfo LookupField(this DEVPROPKEY propKey) => + typeof(SetupAPI).GetFields(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public). + Where(fi => fi.FieldType == typeof(DEVPROPKEY) && fi.Name.StartsWith("DEVPKEY") && propKey.Equals((DEVPROPKEY)fi.GetValue(null))).FirstOrDefault(); + } + + /// A class that represents a device on a machine. + public class Device : IDisposable + { + private readonly SafeHDEVINFO hdi; + private readonly Lazy instParam; + private readonly Lazy name, desc, instId; + private SP_DEVINFO_DATA data; + private DeviceProperties props; + private DeviceRegProperties rprops; + + internal Device(SafeHDEVINFO hdi, SP_DEVINFO_DATA data) + { + this.hdi = hdi; + this.data = data; + name = new Lazy(() => Properties[DEVPKEY_NAME]?.ToString() ?? ""); + desc = new Lazy(() => RegistryProperties[SPDRP.SPDRP_DEVICEDESC]?.ToString() ?? ""); + instId = new Lazy(() => Properties[DEVPKEY_Device_InstanceId]?.ToString() ?? ""); + instParam = new Lazy(GetInstallParams); + } + + /// The description of the device instance. + /// The description. + public string Description => desc.Value; + + /// + /// Gets the flags that control installation and user interface operations. Some flags can be set before sending the device + /// installation request while other flags are set automatically during the processing of some requests. + /// + public DI_FLAGS InstallFlags => instParam.Value.Flags; + + /// + /// Gets additional flags that provide control over installation and user interface operations. Some flags can be set before calling + /// the device installer functions while other flags are set automatically during the processing of some functions. + /// + public DI_FLAGSEX InstallFlagsEx => instParam.Value.FlagsEx; + + /// The instance identifier of the device instance. + /// The instance identifier. + public string InstanceId => instId.Value; + + /// The name of the device instance. + /// The name. + public string Name => name.Value; + + /// Gets a dictionary of properties. + /// The properties. + public IPropertyProvider Properties => + props ??= (MachineName is null ? new DeviceProperties(this) : throw new InvalidOperationException("Properties cannot be retrieved for remote devices.")); + + /// Gets a dictionary of registry properties. + /// The registry properties. + public IPropertyProvider RegistryProperties => + rprops ??= (MachineName is null ? new DeviceRegProperties(this) : throw new InvalidOperationException("Properties cannot be retrieved for remote devices.")); + + /// The GUID of the device's setup class. + public Guid ClassGuid => data.ClassGuid; + + /// Gets the driver path. + public string DriverPath => instParam.Value.DriverPath; + + /// + /// A string that contains the name of the remote computer. If the device information set is for the local computer, this member is + /// . + /// + public string MachineName => hdi.MachineName; + + /// + public void Dispose() { } + + /// Retrieves a specified custom device property from the registry. + /// A registry value name representing a custom property. + /// + /// If , the function retrieves both device instance-specific property values and hardware ID-specific + /// property values, concatenated as a REG_MULTI_SZ-typed string. + /// + /// The requested property information or if the property does not exist. + public object GetCustomProperty(string propName, bool combine = false) + { + if (!SetupDiGetCustomDeviceProperty(hdi, data, propName, combine ? DICUSTOMDEVPROP.DICUSTOMDEVPROP_MERGE_MULTISZ : 0, out _, default, 0, out var bufSz) && + Win32Error.GetLastError() == Win32Error.ERROR_INSUFFICIENT_BUFFER) + { + using var mem = new SafeCoTaskMemHandle(bufSz); + Win32Error.ThrowLastErrorIfFalse(SetupDiGetCustomDeviceProperty(hdi, data, propName, combine ? DICUSTOMDEVPROP.DICUSTOMDEVPROP_MERGE_MULTISZ : 0, out var regType, mem, mem.Size, out bufSz)); + return DeviceClass.DeviceClassRegProperties.GetRegValue(mem, regType); + } + return null; + } + + /// Retrieves an icon for this device. + /// + /// The size, in pixels, of the icon to be retrieved. Use the system metric index SM_CXICON to specify a default-sized icon or use + /// the system metric index SM_CXSMICON to specify a small icon. + /// + /// A safe handle to the icon that this function retrieves. + /// + /// GetIcon attempts to retrieve an icon for the device as follows: + /// + /// + /// + /// If the DEVPKEY_DrvPkg_Icon device property of the device includes a list of resource-identifier strings, the function attempts + /// to retrieve the icon that is specified by the first resource-identifier string in the list. + /// + /// + /// + /// + /// If the function cannot retrieve a device-specific icon, it will then attempt to retrieve the class icon for the device. For + /// information about class icons, see SetupDiLoadClassIcon. + /// + /// + /// + /// + /// If the function cannot retrieve the class icon for the device, it will then attempt to retrieve the icon for the Unknown device + /// setup class, where the icon for the Unknown device setup class includes the image of a question mark (?). + /// + /// + /// + /// + public User32.SafeHICON GetIcon(System.Drawing.Size iconSize) + { + Win32Error.ThrowLastErrorIfFalse(SetupDiLoadDeviceIcon(hdi, data, (uint)iconSize.Width, (uint)iconSize.Height, 0, out var hIcon)); + return new User32.SafeHICON((IntPtr)hIcon); + } + + private SP_DEVINSTALL_PARAMS GetInstallParams() + { + var p = StructHelper.InitWithSize(); + Win32Error.ThrowLastErrorIfFalse(SetupDiGetDeviceInstallParams(hdi, data, ref p)); + return p; + } + + /// Accesses properties with a device. + public class DeviceProperties : VirtualDictionary, IPropertyProvider + { + private readonly Device parent; + + internal DeviceProperties(Device dev) : base(false) => parent = dev; + + private DeviceProperties() : base(false) => throw new InvalidOperationException(); + + /// + public override int Count + { + get + { + SetupDiGetDevicePropertyKeys(parent.hdi, parent.data, null, 0, out var cnt); + Win32Error.ThrowLastErrorUnless(Win32Error.ERROR_INSUFFICIENT_BUFFER); + return (int)cnt; + } + } + + /// + public override ICollection Keys + { + get + { + SetupDiGetDevicePropertyKeys(parent.hdi, parent.data, null, 0, out var cnt); + Win32Error.ThrowLastErrorUnless(Win32Error.ERROR_INSUFFICIENT_BUFFER); + var propKeys = new DEVPROPKEY[(int)cnt]; + Win32Error.ThrowLastErrorIfFalse(SetupDiGetDevicePropertyKeys(parent.hdi, parent.data, propKeys, cnt, out _)); + return propKeys; + } + } + + /// + public override bool Remove(DEVPROPKEY key) => + SetupDiSetDeviceProperty(parent.hdi, parent.data, key, 0, default, 0); + + /// + public override bool TryGetValue(DEVPROPKEY key, out object value) => GetValue(key, out value).Succeeded; + + /// + protected override void SetValue(DEVPROPKEY key, object value) + { + value = DeviceClass.DeviceClassProperties.CommonPropConv(value); + var type = GetPropType(key, value?.GetType()); + var mem = DeviceClass.DeviceClassProperties.PrepValue(value, type); + try + { + if (mem is null) + throw new ArgumentException("Unable to convert object to property type.", nameof(value)); + Win32Error.ThrowLastErrorIfFalse(SetupDiSetDeviceProperty(parent.hdi, parent.data, key, type, mem.DangerousGetHandle(), mem.Size)); + } + finally + { + mem?.Dispose(); + } + } + + private DEVPROPTYPE GetPropType(DEVPROPKEY propKey, Type valType) + { + if (SetupDiGetDeviceProperty(parent.hdi, parent.data, propKey, out var type, default, default, out _)) + return type; + var fi = typeof(DEVPROPTYPE).GetFields().Where(fi => fi.IsLiteral && fi.GetCustomAttributes(false, a => a.TypeRef == valType).Any()).FirstOrDefault(); + return fi is not null ? (DEVPROPTYPE)fi.GetValue(null) : throw new ArgumentException("Unable to determine DEVPROPTYPE."); + } + + private Win32Error GetValue(in DEVPROPKEY propKey, out object value) + { + value = null; + if (!SetupDiGetDeviceProperty(parent.hdi, parent.data, propKey, out _, default, 0, out var bufSz)) + { + var err = Win32Error.GetLastError(); + if (err != Win32Error.ERROR_INSUFFICIENT_BUFFER) + return err; + using var mem = new SafeCoTaskMemHandle(bufSz); + if (!SetupDiGetDeviceProperty(parent.hdi, parent.data, propKey, out var propType, mem, mem.Size, out bufSz)) + return Win32Error.GetLastError(); + value = SetupDiPropertyToManagedObject(mem, propType, propKey.GetCorrespondingTypes().FirstOrDefault()); + } + return Win32Error.ERROR_SUCCESS; + } + } + + /// Accesses registry properties with a device class. + public class DeviceRegProperties : VirtualDictionary, IPropertyProvider + { + private readonly Device parent; + + internal DeviceRegProperties(Device dev) : base(false) => parent = dev; + + private DeviceRegProperties() : base(false) => throw new InvalidOperationException(); + + /// + public override int Count => Enum.GetValues(typeof(SPDRP)).Length; + + /// + public override ICollection Keys => (ICollection)Enum.GetValues(typeof(SPDRP)); + + /// + public override bool Remove(SPDRP key) => SetupDiSetDeviceRegistryProperty(parent.hdi, ref parent.data, key, default, 0); + + /// + public override bool TryGetValue(SPDRP key, out object value) => GetValue(key, out value).Succeeded; + + /// + protected override void SetValue(SPDRP key, object value) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + value = DeviceClass.DeviceClassRegProperties.CommonPropConv(value); + if (!CorrespondingTypeAttribute.CanSet(key, value.GetType())) + throw new ArgumentException("Value type not valid for key."); + SafeAllocatedMemoryHandle mem = value switch + { + var v when v.GetType().IsValueType && !v.GetType().IsEnum => SafeCoTaskMemHandle.CreateFromStructure(value), + var v when v.GetType().IsValueType && v.GetType().IsEnum => SafeCoTaskMemHandle.CreateFromStructure((uint)value), + IEnumerable ies => SafeCoTaskMemHandle.CreateFromStringList(ies), + byte[] ba => new SafeCoTaskMemHandle(ba), + string s => new SafeCoTaskMemString(s, System.Runtime.InteropServices.CharSet.Auto), + _ => throw new ArgumentException("Unable to convert object to property type.", nameof(value)) + }; + try + { + Win32Error.ThrowLastErrorIfFalse(SetupDiSetDeviceRegistryProperty(parent.hdi, ref parent.data, key, mem.DangerousGetHandle(), mem.Size)); + } + finally + { + mem?.Dispose(); + } + } + + private Win32Error GetValue(SPDRP propKey, out object value) + { + value = null; + if (!SetupDiGetDeviceRegistryProperty(parent.hdi, parent.data, propKey, out _, default, 0, out var bufSz)) + { + if (bufSz == 0) + return Win32Error.ERROR_NOT_FOUND; + using var mem = new SafeCoTaskMemHandle(bufSz); + if (!SetupDiGetDeviceRegistryProperty(parent.hdi, parent.data, propKey, out var propType, mem, mem.Size, out bufSz)) + return Win32Error.GetLastError(); + value = DeviceClass.DeviceClassRegProperties.GetRegValue(propKey, mem, propType); + } + return Win32Error.ERROR_SUCCESS; + } + } + } + + /// A class that provides detail about a device setup class available on a machine. + /// + /// Device setup classes provide a mechanism for grouping devices that are installed and configured in the same way. A setup class + /// identifies the class installer and class co-installers that are involved in installing the devices that belong to the class. For + /// example, all CD-ROM drives belong to the CDROM setup class and will use the same co-installer when installed. + /// + public class DeviceClass : IDisposable + { + private static readonly Dictionary imgListData = new Dictionary(); +#pragma warning disable IDE0052 // Remove unread private members + private static readonly FinalizeImgLists imgListFinalizer = new FinalizeImgLists(); +#pragma warning restore IDE0052 // Remove unread private members + private readonly Lazy bmpIdx, imgIdx; + private readonly Lazy name, desc; + private DeviceClassProperties props; + private DeviceClassRegProperties rprops; + + /// Initializes a new instance of the class with its GUID and optional machine name. + /// The GUID for the device setup class. + /// + /// The name of the machine on which devices are managed. indicates the local machine. + /// + public DeviceClass(Guid guid, string machineName = null) + { + Guid = guid; + MachineName = machineName; + name = new Lazy(() => GetClassString(SetupDiClassNameFromGuidEx, 32)); + desc = new Lazy(() => GetClassString(SetupDiGetClassDescriptionEx, 256)); + bmpIdx = new Lazy(() => SetupDiGetClassBitmapIndex(Guid, out var idx) ? idx : null); + imgIdx = new Lazy(() => SetupDiGetClassImageIndex(GetImageList(), Guid, out var idx) ? idx : null); + } + + /// Initializes a new instance of the class from its class name and optional machine name. + /// The name of the class. + /// + /// The name of the machine on which devices are managed. indicates the local machine. + /// + public DeviceClass(string name, string machineName = null) : this(FromName(name, machineName), machineName) + { + } + + private delegate bool GetClassStringDelegate(in Guid ClassGuid, StringBuilder ClassName, uint ClassNameSize, out uint RequiredSize, + string MachineName, IntPtr Reserved = default); + + /// Gets the index of the mini-icon supplied for this class. + /// The index of the mini-icon supplied for this class. + public int? BitmapIndex => bmpIdx.Value; + + /// Gets the class description associated with the specified setup class GUID. + /// The class description associated with the specified setup class GUID. + /// + /// If there is a friendly name in the registry key for the class, this routine returns the friendly name. Otherwise, this routine + /// returns the class name. + /// + public string Description => desc.Value; + + /// Gets the GUID for this setup class. + /// The GUID for this setup class. + public Guid Guid { get; } + + /// Gets the index within the class image list. + /// The index within the class image list. + public int? ImageIndex => imgIdx.Value; + + /// Gets the image list of bitmaps for every class installed on this system. + /// The image list handle of bitmaps for every class installed on this system. + public HIMAGELIST ImageListHandle => GetImageList().ImageList; + + /// Gets the name of the machine on which devices are managed. indicates the local machine. + /// The machine name for this manager. + public string MachineName { get; } + + /// Gets the class name associated with a class GUID. + /// The class name associated with a class GUID. + public string Name => name.Value; + + /// Gets a value that controls whether devices in this setup class are displayed by the Device Manager. + public bool? NoDisplay => (bool?)Properties[DEVPKEY_DeviceClass_NoDisplayClass]; + + /// Gets a value that controls whether devices in this device setup class are displayed in the Add Hardware Wizard. + public bool? NoInstall => (bool?)Properties[DEVPKEY_DeviceClass_NoInstallClass]; + + /// Gets a dictionary of properties. + /// The properties. + public IPropertyProvider Properties => props ??= new DeviceClassProperties(Guid, MachineName); + + /// Gets a dictionary of registry properties. + /// The registry properties. + public IPropertyProvider RegistryProperties => rprops ??= new DeviceClassRegProperties(Guid, MachineName); + + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + } + + /// Gets the devices associated with this device class. + /// A sequence of instances with this device class. + public IEnumerable GetDevices() => new DeviceCollection(Guid, null, MachineName); + + private static Guid FromName(string name, string machine) + { + SetupDiClassGuidsFromNameEx(name, null, 0, out var len, machine); + if (len == 0) Win32Error.ThrowLastError(); + var guids = new Guid[(int)len]; + Win32Error.ThrowLastErrorIfFalse(SetupDiClassGuidsFromNameEx(name, guids, len, out _, machine)); + return guids[0]; + } + + private string GetClassString(GetClassStringDelegate f, uint initSz) + { + var sz = initSz; + var sb = new StringBuilder((int)sz); + if (!f(Guid, sb, sz, out sz, MachineName)) + { + Win32Error.ThrowLastErrorUnless(Win32Error.ERROR_INSUFFICIENT_BUFFER); + sb.Capacity = (int)sz; + Win32Error.ThrowLastErrorIfFalse(f(Guid, sb, sz, out _, MachineName)); + } + return sb.ToString(); + } + + private SP_CLASSIMAGELIST_DATA GetImageList() + { + if (!imgListData.TryGetValue(MachineName ?? "", out var data)) + { + data = StructHelper.InitWithSize(); + Win32Error.ThrowLastErrorIfFalse(SetupDiGetClassImageListEx(ref data, MachineName)); + imgListData.Add(MachineName ?? "", data); + } + return data; + } + + /// Accesses properties with a device class. + public class DeviceClassProperties : VirtualDictionary, IPropertyProvider + { + private readonly Guid Guid; + + private readonly string MachineName; + + internal DeviceClassProperties(Guid guid, string machineName) : base(false) + { + Guid = guid; + MachineName = machineName; + } + + private DeviceClassProperties() : base(false) => throw new InvalidOperationException(); + + /// + public override int Count + { + get + { + SetupDiGetClassPropertyKeysEx(Guid, null, 0, out var cnt, DICLASSPROP.DICLASSPROP_INSTALLER, MachineName); + Win32Error.ThrowLastErrorUnless(Win32Error.ERROR_INSUFFICIENT_BUFFER); + return (int)cnt; + } + } + + /// + public override ICollection Keys + { + get + { + SetupDiGetClassPropertyKeysEx(Guid, null, 0, out var cnt, DICLASSPROP.DICLASSPROP_INSTALLER, MachineName); + Win32Error.ThrowLastErrorUnless(Win32Error.ERROR_INSUFFICIENT_BUFFER); + var propKeys = new DEVPROPKEY[(int)cnt]; + Win32Error.ThrowLastErrorIfFalse(SetupDiGetClassPropertyKeysEx(Guid, propKeys, cnt, out _, DICLASSPROP.DICLASSPROP_INSTALLER, MachineName)); + return propKeys; + } + } + + /// + public override bool Remove(DEVPROPKEY key) => + SetupDiSetClassPropertyEx(Guid, key, 0, default, 0, DICLASSPROP.DICLASSPROP_INSTALLER, MachineName); + + /// + public override bool TryGetValue(DEVPROPKEY key, out object value) => GetValue(key, out value).Succeeded; + + internal static object CommonPropConv(object value) => value switch + { + bool b => (BOOLEAN)b, + DateTime dt => dt.ToFileTimeStruct(), + decimal dec => new CY(dec), + ISafeMemoryHandle h => h.GetBytes(0, h.Size), + _ => value, + }; + + internal static ISafeMemoryHandle PrepValue(object value, DEVPROPTYPE type) + { + // Changes types that need conversion or quick outs + if (value is null) + return SafeCoTaskMemHandle.Null; + if (value is IEnumerable sa && (type.IsFlagSet(DEVPROPTYPE.DEVPROP_TYPEMOD_LIST) && (type.IsFlagSet(DEVPROPTYPE.DEVPROP_TYPE_STRING) || type.IsFlagSet(DEVPROPTYPE.DEVPROP_TYPE_SECURITY_DESCRIPTOR_STRING)))) + return SafeCoTaskMemHandle.CreateFromStringList(sa, StringListPackMethod.Concatenated, System.Runtime.InteropServices.CharSet.Unicode); + var valType = value.GetType(); + + // Validate type of property against supplied type + if (type.IsFlagSet(DEVPROPTYPE.DEVPROP_TYPEMOD_LIST)) + throw new ArgumentException("Invalid list.", nameof(value)); + if (type.IsFlagSet(DEVPROPTYPE.DEVPROP_TYPEMOD_ARRAY)) + { + if (!valType.IsArray) + throw new ArgumentException("Array required.", nameof(value)); + valType = valType.GetElementType(); + type &= DEVPROPTYPE.DEVPROP_MASK_TYPE; + } + var cTypes = CorrespondingTypeAttribute.GetCorrespondingTypes(type & DEVPROPTYPE.DEVPROP_MASK_TYPE).ToArray(); + if (cTypes.Length > 0 && Array.IndexOf(cTypes, valType) < 0) + throw new ArgumentException($"Value type or element type cannot be {valType.Name}", nameof(value)); + + // Push value into mem + switch (value) + { + case byte[] ba: + return new SafeCoTaskMemHandle(ba); + + case string s: + return new SafeCoTaskMemHandle(s); + + default: + var sz = InteropExtensions.SizeOf(valType); + if (valType.IsArray) sz *= ((Array)value).Length; + var mem = new SafeCoTaskMemHandle(sz); + mem.DangerousGetHandle().Write(value, 0, mem.Size); + return mem; + } + } + + /// + protected override void SetValue(DEVPROPKEY key, object value) + { + value = CommonPropConv(value); + var type = GetPropType(key, value?.GetType()); + var mem = PrepValue(value, type); + try + { + if (mem is null) + throw new ArgumentException("Unable to convert object to property type.", nameof(value)); + Win32Error.ThrowLastErrorIfFalse(SetupDiSetClassPropertyEx(Guid, key, type, mem.DangerousGetHandle(), mem.Size, DICLASSPROP.DICLASSPROP_INSTALLER, MachineName)); + } + finally + { + mem?.Dispose(); + } + } + + private DEVPROPTYPE GetPropType(DEVPROPKEY propKey, Type valType) + { + if (SetupDiGetClassPropertyEx(Guid, propKey, out var type, default, default, out _, DICLASSPROP.DICLASSPROP_INSTALLER, MachineName)) + return type; + var fi = typeof(DEVPROPTYPE).GetFields().Where(fi => fi.IsLiteral && fi.GetCustomAttributes(false, a => a.TypeRef == valType).Any()).FirstOrDefault(); + return fi is not null ? (DEVPROPTYPE)fi.GetValue(null) : throw new ArgumentException("Unable to determine DEVPROPTYPE."); + } + + private Win32Error GetValue(in DEVPROPKEY propKey, out object value) + { + value = null; + if (!SetupDiGetClassPropertyEx(Guid, propKey, out _, default, 0, out var bufSz, DICLASSPROP.DICLASSPROP_INSTALLER, MachineName)) + { + var err = Win32Error.GetLastError(); + if (err != Win32Error.ERROR_INSUFFICIENT_BUFFER) + return err; + using var mem = new SafeCoTaskMemHandle(bufSz); + if (!SetupDiGetClassPropertyEx(Guid, propKey, out var propType, mem, mem.Size, out bufSz, DICLASSPROP.DICLASSPROP_INSTALLER, MachineName)) + return Win32Error.GetLastError(); + value = SetupDiPropertyToManagedObject(mem, propType, propKey.GetCorrespondingTypes().FirstOrDefault()); + } + return Win32Error.ERROR_SUCCESS; + } + } + + /// Accesses registry properties with a device class. + public class DeviceClassRegProperties : VirtualDictionary, IPropertyProvider + { + private readonly Guid Guid; + + private readonly string MachineName; + + internal DeviceClassRegProperties(Guid guid, string machineName) : base(false) + { + Guid = guid; + MachineName = machineName; + } + + private DeviceClassRegProperties() : base(false) => throw new InvalidOperationException(); + + /// + public override int Count => Enum.GetValues(typeof(SPCRP)).Length; + + /// + public override ICollection Keys => (ICollection)Enum.GetValues(typeof(SPCRP)); + + /// + public override bool Remove(SPCRP key) => SetupDiSetClassRegistryProperty(Guid, key, default, 0, MachineName); + + /// + public override bool TryGetValue(SPCRP key, out object value) => GetValue(key, out value).Succeeded; + + internal static object CommonPropConv(object value) => value switch + { + bool b => (BOOL)b, + ISafeMemoryHandle h => h.GetBytes(0, h.Size), + _ => value, + }; + + internal static object GetRegValue(T key, SafeAllocatedMemoryHandle mem, REG_VALUE_TYPE propType) where T : Enum => + GetRegValue(mem, propType, CorrespondingTypeAttribute.GetCorrespondingTypes(key).FirstOrDefault()); + + internal static object GetRegValue(SafeAllocatedMemoryHandle mem, REG_VALUE_TYPE propType, Type cType = null) + { + return propType switch + { + REG_VALUE_TYPE.REG_DWORD when cType is not null => ((IntPtr)mem).Convert(mem.Size, cType), + REG_VALUE_TYPE.REG_BINARY when cType is not null && cType != typeof(byte[]) => ((IntPtr)mem).Convert(mem.Size, cType), + _ => propType.GetValue(mem, mem.Size), + }; + } + + /// + protected override void SetValue(SPCRP key, object value) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + value = CommonPropConv(value); + if (!CorrespondingTypeAttribute.CanSet(key, value.GetType())) + throw new ArgumentException("Value type not valid for key."); + SafeAllocatedMemoryHandle mem = key switch + { + SPCRP.SPCRP_UPPERFILTERS => value is IEnumerable uf ? SafeCoTaskMemHandle.CreateFromStringList(uf) : null, + SPCRP.SPCRP_LOWERFILTERS => value is IEnumerable lf ? SafeCoTaskMemHandle.CreateFromStringList(lf) : null, + SPCRP.SPCRP_SECURITY => value is byte[] ba ? new SafeCoTaskMemHandle(ba) : null, + SPCRP.SPCRP_SECURITY_SDS => value is string s ? new SafeCoTaskMemString(s, System.Runtime.InteropServices.CharSet.Auto) : null, + SPCRP.SPCRP_DEVTYPE => value is FILE_DEVICE fd ? SafeCoTaskMemHandle.CreateFromStructure(fd) : null, + SPCRP.SPCRP_EXCLUSIVE => value is BOOL b ? SafeCoTaskMemHandle.CreateFromStructure(b) : null, + SPCRP.SPCRP_CHARACTERISTICS => value is uint u ? SafeCoTaskMemHandle.CreateFromStructure(u) : null, + _ => null, + }; + try + { + if (mem is null) + throw new ArgumentException("Unable to convert object to property type.", nameof(value)); + Win32Error.ThrowLastErrorIfFalse(SetupDiSetClassRegistryProperty(Guid, key, mem.DangerousGetHandle(), mem.Size, MachineName)); + } + finally + { + mem?.Dispose(); + } + } + + private Win32Error GetValue(SPCRP propKey, out object value) + { + value = null; + if (!SetupDiGetClassRegistryProperty(Guid, propKey, out _, default, 0, out var bufSz, MachineName)) + { + _ = Win32Error.GetLastError(); + if (bufSz == 0) + return Win32Error.ERROR_NOT_FOUND; + using var mem = new SafeCoTaskMemHandle(bufSz); + if (!SetupDiGetClassRegistryProperty(Guid, propKey, out var propType, mem, mem.Size, out bufSz, MachineName)) + return Win32Error.GetLastError(); + value = GetRegValue(propKey, mem, propType); + } + return Win32Error.ERROR_SUCCESS; + } + } + + private sealed class FinalizeImgLists + { + ~FinalizeImgLists() + { + foreach (var val in imgListData.Values) + SetupDiDestroyClassImageList(val); + } + } + } + + /// A class that provides the collection of device setup classes available on a machine. + public class DeviceClassCollection : IReadOnlyCollection, IDisposable + { + /// + /// Initializes a new instance of the class with the flags and machine name to be used by to retrieve the classes. + /// + /// + /// + /// Flags used to control exclusion of classes from the list. If no flags are specified, all setup classes are included in the list. + /// Can be a combination of the following values: + /// + /// DIBCI_NOINSTALLCLASS + /// Exclude a class if it has the NoInstallClass value entry in its registry key. + /// DIBCI_NODISPLAYCLASS + /// Exclude a class if it has the NoDisplayClass value entry in its registry key. + /// + /// + /// A string that contains the name of a remote computer from which to retrieve installed setup classes. This parameter is optional + /// and can be . If MachineName is , this class provides a list of classes installed on + /// the local computer. + /// + public DeviceClassCollection(DIBCI flags = 0, string machineName = null) + { + Flags = flags; + MachineName = machineName; + } + + /// Gets the number of elements in the collection. + /// The number of elements in the collection. + public int Count + { + get + { + SetupDiBuildClassInfoListEx(Flags, null, 0, out var len, MachineName); + return (int)len; + } + } + + /// + /// Gets the flags used to control exclusion of classes from the list. If no flags are specified, all setup classes are included in + /// the list. + /// + /// + /// Flags used to control exclusion of classes from the list. If no flags are specified, all setup classes are included in the list. + /// + public DIBCI Flags { get; } + + /// Gets the name of the machine on which devices are managed. indicates the local machine. + /// The machine name for this manager. + public string MachineName { get; } + + // TODO + //bool ICollection.IsReadOnly => false; + //public void Add(DeviceClass item) => throw new NotImplementedException(); + //public void Clear() => throw new NotImplementedException(); + //public bool Contains(DeviceClass item) => throw new NotImplementedException(); + //public void CopyTo(DeviceClass[] array, int arrayIndex) => throw new NotImplementedException(); + //public bool Remove(DeviceClass item) => throw new NotImplementedException(); + + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { + } + + /// Returns an enumerator that iterates through the collection. + /// A that can be used to iterate through the collection. + public IEnumerator GetEnumerator() + { + SetupDiBuildClassInfoListEx(Flags, null, 0, out var len, MachineName); + if (len == 0) Win32Error.ThrowLastError(); + + var guids = new Guid[(int)len]; + Win32Error.ThrowLastErrorIfFalse(SetupDiBuildClassInfoListEx(Flags, guids, len, out _, MachineName)); + for (int i = 0; i < len; i++) + yield return new DeviceClass(guids[i], MachineName); + } + + /// Returns an enumerator that iterates through a collection. + /// An object that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + /// A class that provides the collection of devices available on a machine. + /// + /// + public class DeviceCollection : IEnumerable, IDisposable + { + private SafeHDEVINFO devInfoSet; + + /// Initializes a new instance of the class. + /// + /// The GUID for a device setup class or a device interface class. This value is optional and can be . If a + /// GUID value is not used to select devices, set to . + /// + /// + /// A string that specifies: + /// + /// + /// + /// An identifier (ID) of a Plug and Play (PnP) enumerator. This ID can either be the enumerator's globally unique identifier (GUID) + /// or symbolic name. For example, "PCI" can be used to specify the PCI PnP enumerator. Other examples of symbolic names for PnP + /// enumerators include "USB", "PCMCIA", and "SCSI". + /// + /// + /// + /// + /// A PnP device instance IDs. When specifying a PnP device instance ID, DIGCF_DEVICEINTERFACE must be set in the property. + /// + /// + /// + /// This value is optional and can be . + /// + /// + /// A string that contains the name of a remote computer on which the devices reside. A value of for + /// specifies that the device is installed on the local computer. + /// + /// + /// Specifies control options that filter the device information elements that are added to the device information set. This + /// property can be a bitwise OR of one or more of the flags. + /// + public DeviceCollection(Guid? classGuid = null, string enumerator = null, string machineName = null, DIGCF filter = DIGCF.DIGCF_PRESENT) + { + ClassGuid = classGuid; + PnPEnumeratorOrDevInstId = enumerator; + MachineName = machineName; + Filter = PnPEnumeratorOrDevInstId is null ? filter : filter.SetFlags(DIGCF.DIGCF_DEVICEINTERFACE, PnPEnumeratorOrDevInstId.Contains("\\")); + Reset(); + } + + /// Gets the GUID for the device's setup class or interface class. This value is optional and may be . + /// The GUID for the device's setup class or interface class. This value is optional and may be . + public Guid? ClassGuid { get; } + + /// + /// Gets a value that specifies control options that filter the device information elements that are added to the device information + /// set. This property can be a bitwise OR of one or more of the flags. + /// + /// The filter options. + public DIGCF Filter { get; } + + /// + /// Gets the name of a remote computer on which the devices reside. A value of specifies that the device is + /// installed on the local computer. + /// + /// + /// The name of a remote computer on which the devices reside. A value of specifies that the device is + /// installed on the local computer. + /// + public string MachineName { get; } + + /// + /// Gets a string that specifies: + /// + /// + /// + /// An identifier (ID) of a Plug and Play (PnP) enumerator. This ID can either be the enumerator's globally unique identifier (GUID) + /// or symbolic name. For example, "PCI" can be used to specify the PCI PnP enumerator. Other examples of symbolic names for PnP + /// enumerators include "USB", "PCMCIA", and "SCSI". + /// + /// + /// + /// + /// A PnP device instance IDs. When specifying a PnP device instance ID, DIGCF_DEVICEINTERFACE must be set in the property. + /// + /// + /// + /// This value is optional and can be . + /// + /// The PnP enumerator or device instance ID. + public string PnPEnumeratorOrDevInstId { get; } + + /// Releases unmanaged and - optionally - managed resources. + public void Dispose() { } + + /// + public IEnumerator GetEnumerator() + { + var data = StructHelper.InitWithSize(); + for (uint i = 0; SetupDiEnumDeviceInfo(devInfoSet, i, ref data); i++) + yield return new Device(devInfoSet, data); + Win32Error.ThrowLastErrorUnless(Win32Error.ERROR_NO_MORE_ITEMS); + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private void Reset() + { + devInfoSet?.Dispose(); + devInfoSet = SafeHDEVINFO.Create(ClassGuid, Filter, PnPEnumeratorOrDevInstId, MachineName); + Win32Error.ThrowLastErrorIfInvalid(devInfoSet); + } + } + + /// Class to manage local and remote devices. + public class DeviceManager + { + private static readonly Lazy local = new Lazy(() => new DeviceManager(null)); + + /// Initializes a new instance of the class on a specified machine. + /// Name of the machine on which to manage devices. Specify for the local machine. + public DeviceManager(string machineName = null) => MachineName = machineName; + + /// Provides access to the local machine's devices. + public static DeviceManager LocalInstance => local.Value; + + /// Gets the name of the machine on which devices are managed. indicates the local machine. + /// The machine name for this manager. + public string MachineName { get; } + + /// Gets the devices associated with this machine. + /// A sequence of instances on this machine. + public IEnumerable GetDevices() => new DeviceCollection(null, null, MachineName); + + /// Gets the setup classes available on the machine. + /// A class that provides the collection of setup classes. + public IEnumerable GetSetupClasses() => new DeviceClassCollection(0, MachineName); + + internal static SP_DEVINFO_LIST_DETAIL_DATA GetDevInfoDetail(HDEVINFO hdi) + { + var disData = StructHelper.InitWithSize(); + Win32Error.ThrowLastErrorIfFalse(SetupDiGetDeviceInfoListDetail(hdi, ref disData)); + return disData; + } + } + + // TODO + internal class DeviceInterface + { + } +} \ No newline at end of file diff --git a/System/Vanara.SystemServices.csproj b/System/Vanara.SystemServices.csproj index c8c65928..22dc82c3 100644 --- a/System/Vanara.SystemServices.csproj +++ b/System/Vanara.SystemServices.csproj @@ -10,7 +10,7 @@ Currently implements: Classes -IEnumerableList<T>, INamedEntity, Computer, FileInfoExtension, InternetProxyOptions, IoCompletionPort, Job, JobEventArgs, JobHelper, JobLimits, JobNotificationEventArgs, JobNotifications, JobSecurity, JobSettings, JobStatistics, NetworkConnection, NetworkDeviceConnection, NetworkDeviceConnectionCollection, NetworkInterfaceExt, NetworkListManager, NetworkProfile, OpenFile, PathEx, PoweredDevice, PoweredDeviceCollection, PowerEventArgs<T>, PowerManager, PowerScheme, PowerSchemeCollection, PowerSchemeGroup, PowerSchemeGroupCollection, PowerSchemeSetting, PowerSchemeSettingCollection, ProcessExtension, RegistryEventArgs, RegistryEventMonitor, ServiceControllerAccessRule, ServiceControllerAuditRule, ServiceControllerExtension, ServiceControllerSecurity, ShareConnection, SharedDevice, SharedDevices, SystemShutdown, Wow64Redirect +IEnumerableList<T>, INamedEntity, IPropertyProvider<T>, Computer, Device, DeviceClass, DeviceClassCollection, DeviceClassProperties, DeviceClassRegProperties, DeviceCollection, DeviceExtensions, DeviceInterface, DeviceManager, DeviceProperties, DeviceRegProperties, FileInfoExtension, InternetProxyOptions, IoCompletionPort, Job, JobEventArgs, JobHelper, JobLimits, JobNotificationEventArgs, JobNotifications, JobSecurity, JobSettings, JobStatistics, NetworkConnection, NetworkDeviceConnection, NetworkDeviceConnectionCollection, NetworkInterfaceExt, NetworkListManager, NetworkProfile, OpenFile, PathEx, PoweredDevice, PoweredDeviceCollection, PowerEventArgs<T>, PowerManager, PowerScheme, PowerSchemeCollection, PowerSchemeGroup, PowerSchemeGroupCollection, PowerSchemeSetting, PowerSchemeSettingCollection, ProcessExtension, RegistryEventArgs, RegistryEventMonitor, ServiceControllerAccessRule, ServiceControllerAuditRule, ServiceControllerExtension, ServiceControllerSecurity, ShareConnection, SharedDevice, SharedDevices, SystemShutdown, Wow64Redirect Enumerations BatteryStatus, EnergySaverStatus, JobLimit, NetworkInterfaceAccessType, NetworkInterfaceAdministrativeStatus, NetworkInterfaceConnectionType, NetworkInterfaceDirectionType, NetworkInterfaceMediaType, NetworkInterfacePhysicalMedium, PathCharType, PowerCapabilities, PowerSupplyStatus, ProcessIntegrityLevel, ServiceControllerAccessRights, ShareOfflineSettings @@ -33,6 +33,7 @@ BatteryStatus, EnergySaverStatus, JobLimit, NetworkInterfaceAccessType, NetworkI + diff --git a/UnitTests/System/DeviceTests.cs b/UnitTests/System/DeviceTests.cs new file mode 100644 index 00000000..fe66b782 --- /dev/null +++ b/UnitTests/System/DeviceTests.cs @@ -0,0 +1,102 @@ +using NUnit.Framework; +using System.Linq; +using Vanara.PInvoke.Tests; +using static Vanara.PInvoke.SetupAPI; + +namespace Vanara.Diagnostics.Tests +{ + [TestFixture] + public class DeviceTests + { + [Test] + public void ClassNamesTest() + { + var coll = DeviceManager.LocalInstance.SetupClasses; + Assert.That(coll, Is.Not.Empty); + TestContext.Write(string.Join("\r\n", coll.Select(c => c.Description).OrderBy(s => s))); + } + + [Test] + public void ClassPropsTest() + { + using var cl = new DeviceClass("DiskDrive"); + cl.Guid.WriteValues(); + cl.Name.WriteValues(); + cl.Description.WriteValues(); + cl.BitmapIndex.WriteValues(); + cl.ImageIndex.WriteValues(); + cl.NoDisplay.WriteValues(); + cl.NoInstall.WriteValues(); + foreach (var kv in cl.Properties) + TestContext.WriteLine($"{kv.Key.LookupName()} = {kv.Value.GetStringVal()}"); + foreach (var kv in cl.RegistryProperties) + TestContext.WriteLine($"{kv.Key} = {kv.Value.GetStringVal()}"); + } + + [Test] + public void EnumDevicesTest() + { + using var coll = new DeviceCollection(GUID_DEVCLASS_DISKDRIVE);//, null, null, DIGCF.DIGCF_ALLCLASSES); + var devs = coll.ToArray(); + Assert.That(devs, Is.Not.Empty); + TestContext.WriteLine($"Found {devs.Length} devices."); + TestContext.Write(string.Join("\r\n", devs.Select(c => c.Name).OrderBy(s => s))); + } + + [Test] + public void PropsTest() + { + using var cl = new DeviceClass(GUID_DEVCLASS_DISKDRIVE); + var devs = cl.GetDevices().ToArray(); + TestContext.WriteLine($"Found {devs.Length} devices."); + foreach (var dev in devs) + { + TestContext.WriteLine(new string('=', 20) + dev.Name + new string('=', 20)); + dev.Description.WriteValues(); + dev.DriverPath.WriteValues(); + dev.InstallFlags.WriteValues(); + dev.InstallFlagsEx.WriteValues(); + dev.InstanceId.WriteValues(); + foreach (var kv in dev.Properties) + TestContext.WriteLine($"{kv.Key.LookupName()} = {kv.Value.GetStringVal()}"); + foreach (var kv in dev.RegistryProperties) + TestContext.WriteLine($"{kv.Key} = {kv.Value.GetStringVal()}"); + } + } + + [Test] + public void SetClassPropTest() + { + using var cl = new DeviceClass(GUID_DEVCLASS_DISKDRIVE); + var val = cl.Properties[DEVPKEY_DeviceClass_Exclusive]; + Assert.IsNull(val); + try + { + cl.Properties[DEVPKEY_DeviceClass_Exclusive] = false; + val = cl.Properties[DEVPKEY_DeviceClass_Exclusive]; + Assert.IsFalse((BOOLEAN)val); + } + finally + { + Assert.That(cl.Properties.Remove(DEVPKEY_DeviceClass_Exclusive), ResultIs.Successful); + } + } + + [Test] + public void SetClassRegPropTest() + { + using var cl = new DeviceClass(GUID_DEVCLASS_DISKDRIVE); + var val = cl.RegistryProperties[SPCRP.SPCRP_LOWERFILTERS]; + try + { + cl.RegistryProperties[SPCRP.SPCRP_LOWERFILTERS] = new[] { "MyFilter" }; + var newval = cl.RegistryProperties[SPCRP.SPCRP_LOWERFILTERS]; + Assert.That(newval, Is.Not.EqualTo(val)); + } + finally + { + cl.RegistryProperties[SPCRP.SPCRP_LOWERFILTERS] = val; + } + } + } +} \ No newline at end of file