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
{
}
}