using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; namespace ParsecDisplay { internal static unsafe class Device { public enum Status { OK, INACCESSIBLE, UNKNOWN, UNKNOWN_PROBLEM, DISABLED, DRIVER_ERROR, RESTART_REQUIRED, DISABLED_SERVICE, NOT_INSTALLED } public static Status QueryStatus(string classGuid, string hardwareId, out Version driverVersion) { var status = Status.INACCESSIBLE; driverVersion = new Version(0, 0, 0, 0); var guid = Guid.Parse(classGuid); var devInfo = Native.SetupDiGetClassDevsA(ref guid, null, null, Native.DIGCF_PRESENT); if (devInfo.IsValidHandle()) { status = Status.NOT_INSTALLED; const int hwndBufSize = 512; byte* hwidBuffer = stackalloc byte[hwndBufSize]; // ANSI var devInfoData = new Native.SP_DEVINFO_DATA(); devInfoData.cbSize = sizeof(Native.SP_DEVINFO_DATA); for (uint index = 0; Native.SetupDiEnumDeviceInfo(devInfo, index, &devInfoData); index++) { uint regDataType = 0; if (Native.SetupDiGetDeviceRegistryPropertyA(devInfo, &devInfoData, Native.SPDRP_HARDWAREID, ®DataType, hwidBuffer, hwndBufSize, null)) { if (regDataType == Native.REG_SZ || regDataType == Native.REG_MULTI_SZ) { var length = Native.lstrlenA(hwidBuffer); if (length == hardwareId.Length) { var str = Marshal.PtrToStringAnsi((IntPtr)hwidBuffer, length); if (string.Compare(str, hardwareId) == 0) { status = GetDeviceStatus(devInfoData.DevInst); driverVersion = GetDeviceDriverVersion(devInfoData.DevInst); break; } } } } } Native.SetupDiDestroyDeviceInfoList(devInfo); } return status; } public static bool OpenHandle(string guid, out IntPtr handle) { handle = IntPtr.Zero; var interfaceGuid = Guid.Parse(guid); var devInfo = Native.SetupDiGetClassDevsA(ref interfaceGuid, null, null, Native.DIGCF_PRESENT | Native.DIGCF_DEVICEINTERFACE); if (devInfo != Native.INVALID_HANDLE_VALUE) { var devInterface = new Native.SP_DEVICE_INTERFACE_DATA(); devInterface.cbSize = sizeof(Native.SP_DEVICE_INTERFACE_DATA); for (uint i = 0; Native.SetupDiEnumDeviceInterfaces(devInfo, null, ref interfaceGuid, i, &devInterface); ++i) { int detailSize = 0; Native.SetupDiGetDeviceInterfaceDetailA(devInfo, &devInterface, null, 0, &detailSize, null); var detail = (Native.SP_DEVICE_INTERFACE_DETAIL_DATA_A*)Marshal.AllocHGlobal(detailSize); detail->cbSize = sizeof(Native.SP_DEVICE_INTERFACE_DETAIL_DATA_A); if (Native.SetupDiGetDeviceInterfaceDetailA(devInfo, &devInterface, detail, detailSize, &detailSize, null)) { handle = Native.CreateFileA(&detail->DevicePath, Native.GENERIC_READ | Native.GENERIC_WRITE, Native.FILE_SHARE_READ | Native.FILE_SHARE_WRITE, null, Native.OPEN_EXISTING, Native.FILE_ATTRIBUTE_NORMAL | Native.FILE_FLAG_NO_BUFFERING | Native.FILE_FLAG_OVERLAPPED | Native.FILE_FLAG_WRITE_THROUGH, null); if (handle.IsValidHandle()) { Marshal.FreeHGlobal((IntPtr)detail); break; } } Marshal.FreeHGlobal((IntPtr)detail); } Native.SetupDiDestroyDeviceInfoList(devInfo); } return handle.IsValidHandle(); } public static void CloseHandle(IntPtr handle) { if (handle.IsValidHandle()) { Native.CloseHandle(handle); } } private static Status GetDeviceStatus(uint devInst) { uint devStatus; uint devProblemNum; if (Native.CM_Get_DevNode_Status(&devStatus, &devProblemNum, devInst, 0) != Native.CR_SUCCESS) return Status.NOT_INSTALLED; if ((devStatus & (Native.DN_DRIVER_LOADED | Native.DN_STARTED)) != 0) return Status.OK; if ((devStatus & Native.DN_HAS_PROBLEM) != 0) { switch (devProblemNum) { case Native.CM_PROB_NEED_RESTART: return Status.RESTART_REQUIRED; case Native.CM_PROB_DISABLED: case Native.CM_PROB_HARDWARE_DISABLED: return Status.DISABLED; case Native.CM_PROB_DISABLED_SERVICE: return Status.DISABLED_SERVICE; default: return devProblemNum == Native.CM_PROB_FAILED_POST_START ? Status.DRIVER_ERROR : Status.UNKNOWN_PROBLEM; } } return Status.UNKNOWN; } public static DateTime GetDeviceLastArrival(uint devInst) { uint propType; long lastArrival; int bufferLength = sizeof(long); Native.CM_Get_DevNode_PropertyW(devInst, ref Native.DEVPROPKEY.Device_LastArrivalDate, &propType, &lastArrival, &bufferLength, 0); return DateTime.FromFileTime(lastArrival); } public static bool GetParentDeviceInstance(uint devInst, out uint parentInst, out string parentId) { if (Native.CM_Get_Parent(out parentInst, devInst, 0) == 0) { byte[] idBuffer = new byte[Native.MAX_DEVICE_ID_LEN]; Native.CM_Get_Device_IDA(parentInst, idBuffer, idBuffer.Length, 0); parentId = Encoding.ASCII.GetString(idBuffer).TrimEnd('\0'); return true; } parentInst = 0; parentId = string.Empty; return false; } public static Version GetDeviceDriverVersion(uint devInst) { int length = 64; var buffer = stackalloc byte[length]; uint propType; Native.CM_Get_DevNode_PropertyW(devInst, ref Native.DEVPROPKEY.Device_DriverVersion, &propType, buffer, &length, 0); var vstr = Marshal.PtrToStringUni((IntPtr)buffer); return new Version(vstr); } public static bool GetDeviceInstance(string deviceId, out uint devInst) { return Native.CM_Locate_DevNodeA(out devInst, deviceId, 0) == 0; } public struct ClassDevice { public string InstanceId; public uint DevInst; } /// /// Enumerate present devices of a given setup class. Returns the device /// instance ID (e.g. "DISPLAY\PSCCDD0\5&abc&UID256") and the /// CM devnode handle for each. This goes through the PnP device tree /// rather than GDI, so it is session-independent — useful when the app /// runs inside an RDP session and EnumDisplayDevices is blind to the /// console session's displays. /// public static List EnumerateClass(string classGuid) { var list = new List(); var guid = Guid.Parse(classGuid); var devInfo = Native.SetupDiGetClassDevsA(ref guid, null, null, Native.DIGCF_PRESENT); if (!devInfo.IsValidHandle()) return list; try { var devInfoData = new Native.SP_DEVINFO_DATA(); devInfoData.cbSize = sizeof(Native.SP_DEVINFO_DATA); const int bufSize = Native.MAX_DEVICE_ID_LEN; byte* buf = stackalloc byte[bufSize]; for (uint i = 0; Native.SetupDiEnumDeviceInfo(devInfo, i, &devInfoData); i++) { int required = 0; if (Native.SetupDiGetDeviceInstanceIdA(devInfo, &devInfoData, buf, bufSize, &required)) { list.Add(new ClassDevice { InstanceId = Marshal.PtrToStringAnsi((IntPtr)buf), DevInst = devInfoData.DevInst, }); } } } finally { Native.SetupDiDestroyDeviceInfoList(devInfo); } return list; } public static bool IsValidHandle(this IntPtr handle) { return handle != IntPtr.Zero && handle != (IntPtr)(-1); } static class Native { public const int MAX_DEVICE_ID_LEN = 200; public static readonly IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1); public const uint GENERIC_READ = 0x80000000; public const uint GENERIC_WRITE = 0x40000000; public const uint FILE_SHARE_READ = 0x1; public const uint FILE_SHARE_WRITE = 0x2; public const uint OPEN_EXISTING = 3; public const uint FILE_ATTRIBUTE_NORMAL = 0x00000080; public const uint FILE_FLAG_NO_BUFFERING = 0x20000000; public const uint FILE_FLAG_OVERLAPPED = 0x40000000; public const uint FILE_FLAG_WRITE_THROUGH = 0x80000000; public const uint DIGCF_PRESENT = 0x2; public const uint DIGCF_DEVICEINTERFACE = 0x10; public const uint SPDRP_HARDWAREID = 0x1; public const uint REG_SZ = 1; public const uint REG_MULTI_SZ = 7; public const uint CR_SUCCESS = 0; public const uint CM_PROB_NEED_RESTART = 0x0000000E; // requires restart public const uint CM_PROB_DISABLED = 0x00000016; // devinst is disabled public const uint CM_PROB_HARDWARE_DISABLED = 0x0000001D; // device disabled public const uint CM_PROB_DISABLED_SERVICE = 0x00000020; // service's Start = 4 public const uint CM_PROB_FAILED_POST_START = 0x0000002B; // The drivers set the device state to failed public const uint DN_DRIVER_LOADED = 0x00000002; // Has Register_Device_Driver public const uint DN_STARTED = 0x00000008; // Is currently configured public const uint DN_HAS_PROBLEM = 0x00000400; // Need device installer [DllImport("kernel32.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int lstrlenA(byte* lpString); [DllImport("kernel32.dll")] public static extern IntPtr CreateFileA( char* lpFileName, uint dwDesiredAccess, uint dwShareMode, void* lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, void* hTemplateFile); [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool CloseHandle(IntPtr handle); [StructLayout(LayoutKind.Sequential)] public struct OVERLAPPED { public IntPtr Internal; public IntPtr InternalHigh; public IntPtr Pointer; public IntPtr hEvent; } [StructLayout(LayoutKind.Sequential)] public struct SP_DEVICE_INTERFACE_DATA { public int cbSize; public Guid InterfaceClassGuid; public uint Flags; IntPtr _Reserved; } [StructLayout(LayoutKind.Sequential)] public struct SP_DEVINFO_DATA { public int cbSize; public Guid ClassGuid; public uint DevInst; IntPtr _Reserved; } [StructLayout(LayoutKind.Sequential)] public struct SP_DEVICE_INTERFACE_DETAIL_DATA_A { public int cbSize; public char DevicePath; } [DllImport("setupapi.dll", SetLastError = true)] public static extern IntPtr SetupDiGetClassDevsA( ref Guid ClassGuid, void* Enumerator, void* hwndParent, uint Flags); [DllImport("setupapi.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetupDiEnumDeviceInterfaces( IntPtr DeviceInfoSet, SP_DEVINFO_DATA* DeviceInfoData, ref Guid InterfaceClassGuid, uint MemberIndex, SP_DEVICE_INTERFACE_DATA* DeviceInterfaceData); [DllImport("setupapi.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetupDiGetDeviceInterfaceDetailA( IntPtr DeviceInfoSet, SP_DEVICE_INTERFACE_DATA* DeviceInterfaceData, void* DeviceInterfaceDetailData, int DeviceInterfaceDetailDataSize, int* RequiredSize, SP_DEVINFO_DATA* DeviceInfoData); [DllImport("setupapi.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetupDiDestroyDeviceInfoList( IntPtr DeviceInfoSet); [DllImport("setupapi.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetupDiEnumDeviceInfo( IntPtr DeviceInfoSet, uint MemberIndex, SP_DEVINFO_DATA* DeviceInfoData); [DllImport("setupapi.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetupDiGetDeviceRegistryPropertyA( IntPtr DeviceInfoSet, SP_DEVINFO_DATA* DeviceInfoData, uint Property, uint* PropertyRegDataType, void* PropertyBuffer, int PropertyBufferSize, int* RequiredSize); [DllImport("setupapi.dll", SetLastError = true, EntryPoint = "SetupDiGetDeviceInstanceIdA", CharSet = CharSet.Ansi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetupDiGetDeviceInstanceIdA( IntPtr DeviceInfoSet, SP_DEVINFO_DATA* DeviceInfoData, byte* DeviceInstanceId, int DeviceInstanceIdSize, int* RequiredSize); [DllImport("setupapi.dll")] public static extern uint CM_Get_DevNode_Status( uint* pulStatus, uint* pulProblemNumber, uint dnDevInst, uint ulFlags); [DllImport("setupapi.dll")] public static extern uint CM_Get_Parent(out uint pdnDevInst, uint dnDevInst, uint ulFlags); [DllImport("setupapi.dll")] public static extern uint CM_Get_Device_IDA(uint dnDevInst, byte[] Buffer, int BufferLen, uint ulFlags); [StructLayout(LayoutKind.Sequential)] public struct DEVPROPKEY { public Guid fmtid; public uint pid; public static DEVPROPKEY Device_LastArrivalDate = new DEVPROPKEY { fmtid = Guid.Parse("{83DA6326-97A6-4088-9453-A1923F573B29}"), pid = 102, }; public static DEVPROPKEY Device_DriverVersion = new DEVPROPKEY { fmtid = Guid.Parse("{A8B865DD-2E3D-4094-AD97-E593A70C75D6}"), pid = 3 }; } [DllImport("CfgMgr32.dll")] public static extern uint CM_Get_DevNode_PropertyW( uint dnDevInst, ref DEVPROPKEY PropertyKey, uint* PropertyType, void* PropertyBuffer, int* PropertyBufferSize, uint ulFlags); [DllImport("CfgMgr32.dll", CharSet = CharSet.Ansi)] public static extern uint CM_Locate_DevNodeA(out uint devInst, string deviceId, uint flags); } } }