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 GUID of the device's setup class. public Guid ClassGuid => data.ClassGuid; /// Gets the data the identifies the device. /// A SP_DEVINFO_DATA structure with identifying device information. public SP_DEVINFO_DATA Data => data; /// The description of the device instance. /// The description. public string Description => desc.Value; /// Gets the driver path. public string DriverPath => instParam.Value.DriverPath; /// Gets the handle to the device. /// A HDEVINFO handle. public HDEVINFO Handle => hdi; /// /// 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; /// /// 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; /// 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.")); /// 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(); #pragma warning disable IDE0052 // Remove unread private members private static readonly FinalizeImgLists imgListFinalizer = new(); #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) => 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(() => 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. /// /// 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. /// /// A sequence of instances on this machine. public IEnumerable GetDevices(DIGCF filter = DIGCF.DIGCF_PRESENT) => new DeviceCollection(null, null, MachineName, filter); /// 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 { } }