Showing posts with label setupapi. Show all posts
Showing posts with label setupapi. Show all posts

Using Devcon

A while back I wrote about using SetupAPI functions in VB.NET. The goal was to be able to work with devices and ultimately enable and disable a device.

The code was what I would eventually use, but initially I used Devcon. Devcon is essentially the Device Manager over a command line. It is freely available. You can even get the source code from the Windows Server 2003 Driver Development Kit (DDK). It may be available in the newer Windows Driver Kit (WDK) as well.

To demonstrate the usefulness of devcon, we can look at some examples from my blog and see how they coorelate to devcon commands

Retrieving Device Setup Class Names and Getting Friendly Class Names:
We can use the following command

devcon classes

This would give us output similar to:

Listing 53 setup class(es).
WCEUSBS : Windows CE USB Devices
USB : Universal Serial Bus controllers
PnpPrinters : IEEE 1394 and SCSI printers
Dot4 : IEEE 1284.4 devices
Dot4Print : IEEE 1284.4 compatible printers
CDROM : DVD/CD-ROM drives
Computer : Computer
DiskDrive : Disk drives
Display : Display adapters
fdc : Floppy disk controllers
hdc : IDE ATA/ATAPI controllers
Keyboard : Keyboards
MEDIA : Sound, video and game controllers
Modem : Modems
Monitor : Monitors
Mouse : Mice and other pointing devices
MTD : PCMCIA and Flash memory devices
MultiFunction : Multifunction adapters
Net : Network adapters
NetClient : Network Client
NetService : Network Service
NetTrans : Network Protocol
PCMCIA : PCMCIA adapters
Ports : Ports (COM & LPT)
Printer : Printers
SCSIAdapter : SCSI and RAID controllers
System : System devices
Unknown : Other devices
FloppyDisk : Floppy disk drives
Processor : Processors
MultiPortSerial : Multi-port serial adapters
SmartCardReader : Smart card readers
VolumeSnapshot : Storage volume shadow copies
1394Debug : 1394 Debugger Device
1394 : IEEE 1394 Bus host controllers
Infrared : Infrared devices
Image : Imaging devices
TapeDrive : Tape drives
Volume : Storage volumes
Battery : Batteries
HIDClass : Human Interface Devices
61883 : 61883 Device Class
LegacyDriver : Non-Plug and Play Drivers
SDHost : Secure Digital host controllers
UsbPcCardReader : USB PC-Card Readers
Avc : AVC Device Class
Enum1394 : IEEE 1394 IP Network Enumerator
MediumChanger : Medium Changers
NtApm : NT Apm/Legacy Support
SBP2 : SBP2 IEEE 1394 Devices
Bluetooth : Bluetooth Radios
WPD : Windows Portable Devices
USB : Motorola USB Device

You'll note that we aren't receiving the Class Guid. I'm not sure if this is that important anyway.

Actually Getting to the Devices and Getting Device Names:

There are a couple of ways we can do this. The first option is to simply specify a class. That happens with the = argument

devcon find =net

PCI\VEN_14E4&DEV_169D&SUBSYS_105E147B&REV_11\4&1B41B794&0&00E0: Broadcom NetLink (TM) Gigabit Ethernet
ROOT\CNTX_VPCNETS2_MP\0000 : Linksys Wireless-G USB Network Adapter #9 - Virtual Machine Network Services Driver
ROOT\CNTX_VPCNETS2_MP\0009 : Broadcom NetLink (TM) Gigabit Ethernet - Virtual Machine Network Services Driver
ROOT\CNTX_VPCNETS2_MP\0010 : Linksys Wireless-G USB Network Adapter #10 - Virtual Machine Network Services Driver
ROOT\MS_L2TPMINIPORT\0000 : WAN Miniport (L2TP)
ROOT\MS_NDISWANBH\0000 : WAN Miniport (Network Monitor)
ROOT\MS_PSCHEDMP\0001 : Broadcom NetLink (TM) Gigabit Ethernet - Packet Scheduler Miniport
ROOT\MS_PSCHEDMP\0002 : Linksys Wireless-G USB Network Adapter - Packet Scheduler Miniport
ROOT\MS_PTIMINIPORT\0000 : Direct Parallel
...
31 matching device(s) found.

We can also use

devcon listclass Net

To find all devices, even those that aren't present, we type

devcon findall =Net

We can also filter based on how the device is connected. This command will show just PCI devices in the net class

devcon find =Net PCI\*

You can do this without even specifying the class as well.

devcon find PCI\*

Getting Status on a Device:

Similar to the last set of commands we can use

devcon status =Net

PCI\VEN_14E4&DEV_169D&SUBSYS_105E147B&REV_11\4&1B41B794&0&00E0
Name: Broadcom NetLink (TM) Gigabit Ethernet
Driver is running.
ROOT\CNTX_VPCNETS2_MP\0000
Name: Linksys Wireless-G USB Network Adapter #9 - Virtual Machine Network Se
rvices Driver
Driver is running.
ROOT\CNTX_VPCNETS2_MP\0009
Name: Broadcom NetLink (TM) Gigabit Ethernet - Virtual Machine Network Servi
ces Driver
Driver is running.
ROOT\MS_L2TPMINIPORT\0000
Name: WAN Miniport (L2TP)
Driver is running.
ROOT\MS_NDISWANBH\0000
Name: WAN Miniport (Network Monitor)
Driver is running.
ROOT\MS_PSCHEDMP\0001
Name: Broadcom NetLink (TM) Gigabit Ethernet - Packet Scheduler Miniport
Driver is running.
ROOT\MS_PSCHEDMP\0002
Name: Linksys Wireless-G USB Network Adapter - Packet Scheduler Miniport
Driver is running.
ROOT\MS_PSCHEDMP\0010
Name: Linksys Wireless-G USB Network Adapter #7 - Packet Scheduler Miniport
Driver is running.
ROOT\MS_PTIMINIPORT\0000
Name: Direct Parallel
Driver is running.
...
31 matching device(s) found.

The same arguments as last time can be used.

Enabling/Disabling a device:

You can use wildcards again, although I usually am very explicit with what I am enabling or disabling.


devcon disable @"HDAUDIO\FUNC_01&VEN_10EC&DEV_0880&SUBSYS_147B9B01&REV_1008\4&EEB356C&0&0001"
HDAUDIO\FUNC_01&VEN_10EC&DEV_0880&SUBSYS_147B9B01&REV_1008\4&EEB356C&0&0001: Disabled
1 device(s) disabled.

devcon enable @"HDAUDIO\FUNC_01&VEN_10EC&DEV_0880&SUBSYS_147B9B01&REV_1008\4&EEB356C&0&0001"
HDAUDIO\FUNC_01&VEN_10EC&DEV_0880&SUBSYS_147B9B01&REV_1008\4&EEB356C&0&0001: Enabled
1 device(s) enabled.



All this works quite well in most situations. There were a couple reasons why I eventually opted with the VB.NET way of doing things. First, efficiency. There is a lot of overhead to enabling and disabling a device with Devcon. This can slow down a program, especially when enabling and disabling a device can be quite time consuming in a computers way of imagining time. 500 milliseconds can be an eternity. 2-3 seconds can be a long time for a user to look at no progress.

The Second reason is just the nature of devcon. Since it is meant to be similar to the Device Manager itself, it acts like the Device Manager. This means devices are disabled on a per-profile basis. So, if you diable a device in one hardware profile and then boot in a different profile, it won't continue to be disabled. In my particular project, this wasn't ideal.

If these reasons aren't compelling enough for you to code this in .NET or C++, then you should look at using Devcon to accomplish your task.

Working With Devices: Enabling/Disabling a device

All the code in previous blog posts leading up to this were really for one purpose. I wanted the ability to enable and disable a device.

We will use two functions to change the state of a device. SetupDiSetClassInstallParams and SetupDiCallClassInstaller

Private Declare Auto Function SetupDiSetClassInstallParams Lib "setupapi.dll" (ByVal DeviceInfoSet As IntPtr, ByVal DeviceInfoData As IntPtr, ByVal InstallParams As IntPtr, ByVal InstallParamsSize As Integer) As Boolean
Private Declare Auto Function SetupDiCallClassInstaller Lib "setupapi.dll" (ByVal InstallFunction As Integer, ByVal DeviceInfoSet As IntPtr, ByVal DeviceInfoData As IntPtr) As Boolean

Private Const DIF_PROPERTYCHANGE As Integer = &H12

Private Const DICS_ENABLE As Integer = 1
Private Const DICS_DISABLE As Integer = 2

Private Const DICS_FLAG_GLOBAL As Integer = 1
Private Const DICS_FLAG_CONFIGSPECIFIC As Integer = 2

'Struct for Enabling/Disabling a device
Private Structure SP_PROPCHANGE_PARAMS
Public ClassInstallHeader As SP_CLASSINSTALL_HEADER
Public StateChange As Integer
Public Scope As Integer
Public HwProfile As Integer
End Structure

'Two actions on a device. Just to make things simple
Public Enum Change_Action
Disable
Enable
End Enum

'Changes the state of a given device.
Public Function ChangeState(ByVal DevInfoSet As IntPtr, ByVal Device As Device, ByVal newState As Change_Action) As Boolean

'We make sure that the device isn't already set to the newState
Dim DeviceEnabled As Boolean = IsDeviceEnabled(Device.DevInfo.DevInst)
If DeviceEnabled And newState = Change_Action.Enable Then Return True
If Not DeviceEnabled And newState = Change_Action.Disable Then Return True

'We first try to set the device Globally. If it fails then we try setting it just for the current Profile
'Device Manager seems to disable devices by hardware profile. That would make since. In our case we try disable the device Globally
'first.
If ChangeDevState(DevInfoSet, Device, newState, DICS_FLAG_GLOBAL) Then
Return True
Else
Return ChangeDevState(DevInfoSet, Device, newState, DICS_FLAG_CONFIGSPECIFIC)
End If
End Function

'Changes a device state
Private Function ChangeDevState(ByVal DevInfoSet As IntPtr, ByVal Device As Device, ByVal newState As Change_Action, ByVal Scope As Integer) As Boolean

'Sets up the Change Structure
Dim Prop_Change As SP_PROPCHANGE_PARAMS = New SP_PROPCHANGE_PARAMS
Prop_Change.ClassInstallHeader.cbSize = Marshal.SizeOf(GetType(SP_CLASSINSTALL_HEADER))
Prop_Change.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE
Prop_Change.StateChange = IIf(newState = Change_Action.Enable, DICS_ENABLE, DICS_DISABLE)
Prop_Change.Scope = Scope
Prop_Change.HwProfile = 0

'We create two pointers. One for the Change Structure and a second for the Device. We set the Structures to their pointer
Dim intProp_ChangeSize As Integer = Marshal.SizeOf(Prop_Change)
Dim intProp_Change As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(Prop_Change.ClassInstallHeader))
Marshal.StructureToPtr(Prop_Change, intProp_Change, True)

Dim intDevInfoSize As Integer = Marshal.SizeOf(Device.DevInfo)
Dim intDevInfo As IntPtr = Marshal.AllocHGlobal(intDevInfoSize)
Marshal.StructureToPtr(Device.DevInfo, intDevInfo, True)


'Sets the Property Change Structure for the Device. True is success
Dim Result As Boolean = SetupDiSetClassInstallParams(DevInfoSet, intDevInfo, intProp_Change, intProp_ChangeSize)
If Result Then
'Attempts to execute the Call. True is success
Result = SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, DevInfoSet, intDevInfo)
If Result Then
'Depending on the scope used to enable/disable the device it may not work. So we'll check the device state.
If newState = Change_Action.Enable Then
Return IsDeviceEnabled(Device.DevInfo.DevInst)
Else
Return Not IsDeviceEnabled(Device.DevInfo.DevInst)
End If
Else
Throw New Win32Exception(Marshal.GetLastWin32Error)
Return False
End If
Else
Throw New Win32Exception(Marshal.GetLastWin32Error)
Return False
End If
End Function

To use this, we call the ChangeState function. We call this with the Device Info Set and Device variables along with what we want to do. We can Enable or Disable the device. First thing we do, is check whether or not the device is already set correctly. Next thing we do is attempt to enable or disable the device. The first way we try to do this is Globally. If, that fails then we try on the specific profile.

I did this after working with enabling and disabling devices and noticed that the device manager disables a device for the profile. In my specific case I needed devices disabled globally. If I just enabled or disabled globally, I'd run into problems when a device is disabled for the profile and not globally. Your use may vary, so feel free to choose one or the other, but if you ever come across a device in device manager that shows a disabled icon associated with disable in the context menu (or both enabled) then that is your problem.

The ChangeDevState function actually does the changing of the state. The code with comments will explain it as best I can.

Working With Devices: Getting Status on a Device

This time we will look at checking if a device is enabled, disabled, or has a problem. From last time we received a device structure that contained a SP_DEVINFO_DATA structure. Within that structure we find a DevInst integer. With that and the use of the CM_Get_DevNode_Status function we can get status information on a device.

Private Declare Auto Function CM_Get_DevNode_Status Lib "cfgmgr32.dll" (ByRef pulStatus As IntPtr, ByRef pulProblemNumber As IntPtr, ByVal dnDevInst As Integer, ByVal ulFlags As Long) As Integer

This function will return the status and any problem with the device given. The last variable must be set to 0. The Status and Problem flags can have numerous meanings, so we have two enumerations to describe all the possible values. I've only encountered a handful of status and problem codes through normal use.

'Status Flags. Devices can have one or more of these. ANDing is used to determine which apply
Public Enum DN_Flags
DN_ROOT_ENUMERATED = &H1 ' Was enumerated by ROOT
DN_DRIVER_LOADED = &H2 ' Has Register_Device_Driver
DN_ENUM_LOADED = &H4 ' Has Register_Enumerator
DN_STARTED = &H8 ' Is currently configured
DN_MANUAL = &H10 ' Manually installed
DN_NEED_TO_ENUM = &H20 ' May need reenumeration
DN_NOT_FIRST_TIME = &H40 ' Has received a config
DN_HARDWARE_ENUM = &H80 ' Enum generates hardware ID
DN_LIAR = &H100 ' Lied about can reconfig once
DN_HAS_MARK = &H200 ' Not CM_Create_DevInst lately
DN_HAS_PROBLEM = &H400 ' Need device installer
DN_FILTERED = &H800 ' Is filtered
DN_MOVED = &H1000 ' Has been moved
DN_DISABLEABLE = &H2000 ' Can be disabled
DN_REMOVABLE = &H4000 ' Can be removed
DN_PRIVATE_PROBLEM = &H8000 ' Has a private problem
DN_MF_PARENT = &H10000 ' Multi function parent
DN_MF_CHILD = &H20000 ' Multi function child
DN_WILL_BE_REMOVED = &H40000 ' DevInst is being removed
DN_NOT_FIRST_TIMEE = &H80000 ' Has received a config enumerate
DN_STOP_FREE_RES = &H100000 ' When child is stopped, free resources
DN_REBAL_CANDIDATE = &H200000 ' Don't skip during rebalance
DN_BAD_PARTIAL = &H400000 ' This devnode's log_confs do not have same resources
DN_NT_ENUMERATOR = &H800000 ' This devnode's is an NT enumerator
DN_NT_DRIVER = &H1000000 ' This devnode's is an NT driver
DN_NEEDS_LOCKING = &H2000000 ' Devnode need lock resume processing
DN_ARM_WAKEUP = &H4000000 ' Devnode can be the wakeup device
DN_APM_ENUMERATOR = &H8000000 ' APM aware enumerator
DN_APM_DRIVER = &H10000000 ' APM aware driver
DN_SILENT_INSTALL = &H20000000 ' Silent install
DN_NO_SHOW_IN_DM = &H40000000 ' No show in device manager
DN_BOOT_LOG_PROB = &H80000000 ' Had a problem during preassignment of boot log conf
End Enum

'Problem associated with device. A device has 0 or 1 problem associated with it.
Public Enum CM_PROB
CM_PROB_NOT_CONFIGURED = &H1 ' no config for device
CM_PROB_DEVLOADER_FAILED = &H2 ' service load failed
CM_PROB_OUT_OF_MEMORY = &H3 ' out of memory
CM_PROB_ENTRY_IS_WRONG_TYPE = &H4 '
CM_PROB_LACKED_ARBITRATOR = &H5 '
CM_PROB_BOOT_CONFIG_CONFLICT = &H6 ' boot config conflict
CM_PROB_FAILED_FILTER = &H7 '
CM_PROB_DEVLOADER_NOT_FOUND = &H8 ' Devloader not found
CM_PROB_INVALID_DATA = &H9 ' Invalid ID
CM_PROB_FAILED_START = &HA '
CM_PROB_LIAR = &HB '
CM_PROB_NORMAL_CONFLICT = &HC ' config conflict
CM_PROB_NOT_VERIFIED = &HD '
CM_PROB_NEED_RESTART = &HE ' requires restart
CM_PROB_REENUMERATION = &HF '
CM_PROB_PARTIAL_LOG_CONF = &H10 '
CM_PROB_UNKNOWN_RESOURCE = &H11 ' unknown res type
CM_PROB_REINSTALL = &H12 '
CM_PROB_REGISTRY = &H13 '
CM_PROB_VXDLDR = &H14 ' WINDOWS 95 ONLY
CM_PROB_WILL_BE_REMOVED = &H15 ' devinst will remove
CM_PROB_DISABLED = &H16 ' devinst is disabled
CM_PROB_DEVLOADER_NOT_READY = &H17 ' Devloader not ready
CM_PROB_DEVICE_NOT_THERE = &H18 ' device doesn't exist
CM_PROB_MOVED = &H19 '
CM_PROB_TOO_EARLY = &H1A '
CM_PROB_NO_VALID_LOG_CONF = &H1B ' no valid log config
CM_PROB_FAILED_INSTALL = &H1C ' install failed
CM_PROB_HARDWARE_DISABLED = &H1D ' device disabled
CM_PROB_CANT_SHARE_IRQ = &H1E ' can't share IRQ
CM_PROB_FAILED_ADD = &H1F ' driver failed add
CM_PROB_DISABLED_SERVICE = &H20 ' service's Start = 4
CM_PROB_TRANSLATION_FAILED = &H21 ' resource translation failed
CM_PROB_NO_SOFTCONFIG = &H22 ' no soft config
CM_PROB_BIOS_TABLE = &H23 ' device missing in BIOS table
CM_PROB_IRQ_TRANSLATION_FAILED = &H24 ' IRQ translator failed
CM_PROB_FAILED_DRIVER_ENTRY = &H25 ' DriverEntry() failed.
CM_PROB_DRIVER_FAILED_PRIOR_UNLOAD = &H26 ' Driver should have unloaded.
CM_PROB_DRIVER_FAILED_LOAD = &H27 ' Driver load unsuccessful.
CM_PROB_DRIVER_SERVICE_KEY_INVALID = &H28 ' Error accessing driver's service key
CM_PROB_LEGACY_SERVICE_NO_DEVICES = &H29 ' Loaded legacy service created no devices
CM_PROB_DUPLICATE_DEVICE = &H2A ' Two devices were discovered with the same name
CM_PROB_FAILED_POST_START = &H2B ' The drivers set the device state to failed
CM_PROB_HALTED = &H2C ' This device was failed post start via usermode
CM_PROB_PHANTOM = &H2D ' The devinst currently exists only in the registry
CM_PROB_SYSTEM_SHUTDOWN = &H2E ' The system is shutting down
CM_PROB_HELD_FOR_EJECT = &H2F ' The device is offline awaiting removal
CM_PROB_DRIVER_BLOCKED = &H30 ' One or more drivers is blocked from loading
CM_PROB_REGISTRY_TOO_LARGE = &H31 ' System hive has grown too large
CM_PROB_SETPROPERTIES_FAILED = &H32 ' Failed to apply one or more registry properties
NUM_CM_PROB = &H33 '
End Enum

'Returns true if the device is enabled
Public Function IsDeviceEnabled(ByVal DevInst As Integer) As Boolean
Dim Result As Integer
Dim Problem, Status As IntPtr
Result = CM_Get_DevNode_Status(Status, Problem, DevInst, 0)
If Result = 13 Then Return Nothing
If Result <> 0 Then Throw New ApplicationException("Return Code From CM_Get_DevNode_Status was not 0")
Return Not Problem.ToInt32 = CM_PROB.CM_PROB_DISABLED
End Function

'Returns true if the Status Flag to hide the device is set.
Public Function IsDeviceHidden(ByVal DevInst As Integer) As Boolean
Dim Result As Integer
Dim Problem, Status As IntPtr
Result = CM_Get_DevNode_Status(Status, Problem, DevInst, 0)
If Result <> 0 Then Throw New ApplicationException("Return Code From CM_Get_DevNode_Status was not 0")
Return (Status.ToInt32 And DN_Flags.DN_NO_SHOW_IN_DM) = DN_Flags.DN_NO_SHOW_IN_DM
End Function

'Gets all the Device Status Flags for a device
Public Function GetDeviceStatus(ByVal DevInst As Integer) As String()
Dim Result As Integer
Dim Problem, Status As IntPtr
Result = CM_Get_DevNode_Status(Status, Problem, DevInst, 0)
If Result = 13 Then Return Nothing
If Result <> 0 Then Throw New ApplicationException("Return Code From CM_Get_DevNode_Status was not 0")
Return GetBitFlags(Status, GetType(DN_Flags))
End Function

'Gets a Device Problem
Public Function GetDeviceProblem(ByVal DevInst As Integer) As String
Dim Result As Integer
Dim Problem, Status As IntPtr
Result = CM_Get_DevNode_Status(Status, Problem, DevInst, 0)
If Result = 13 Then Return Nothing
If Result <> 0 Then Throw New ApplicationException("Return Code From CM_Get_DevNode_Status was not 0")
If Problem.ToInt32 = 0 Then
Return "No Problem"
Else
Return CType(Problem.ToInt32, CM_PROB).ToString
End If
End Function

'Returns an array of strings representing the Status Flags set for a device.
Private Function GetBitFlags(ByVal Bits As IntPtr, ByVal EnumType As Type) As String()
Dim arrData As New ArrayList
For Each TypeName As String In [Enum].GetNames(EnumType)
Dim Number As Int32 = [Enum].Parse(EnumType, TypeName)
If (Bits.ToInt32 And Number) = Number Then
arrData.Add(TypeName)
End If
Next
Dim StrData(arrData.Count) As String
arrData.CopyTo(StrData)
Return StrData
End Function

Here is how you could call this. after you retrieve a device in variable objDevice.

If IsDeviceEnabled(objDevice.DevInfo.DevInst) Then
'Device Enabled
...
Else
'Device Disabled
...
End If

I think that the code should be fairly straightforward to follow.

Working With Devices: Getting Device Names

This post may be a bit longer than previous, because I'll have a bit more code to show than usual. This post will show how to take what we have from previous posts and get the device names for a specific Device Setup or Interface class.

We'll only need the use of two windows api's. Those being SetupDiEnumDeviceInfo and SetupDiGetDeviceRegistryProperty. The first function will return a SP_DEVINFO_DATA type. Instead of just handling this, we'll add the device name itself, in order to be more clear with which device were working with. We retrieve the device name from the second function. We have a few different versions of this. I've done this in order to parse the different types of registry types a bit easier. This may be redefined a bit later. I'll update this post if I find a better solution.

First let me post the code and then I'll try to go over what each function does as best I can.

Private Declare Auto Function SetupDiEnumDeviceInfo Lib "Setupapi.dll" (ByVal DeviceInfoSet As IntPtr, ByVal MemberIndex As Integer, ByRef DeviceInfoData As SP_DEVINFO_DATA) As Boolean

Private Declare Ansi Function SetupDiGetDeviceRegistryPropertyA Lib "Setupapi.dll" (ByVal DeviceInfoSet As IntPtr, ByVal DeviceInfoData As IntPtr, ByVal intProperty As Integer, ByRef PropertyRegDataType As IntPtr, ByVal PropertyBuffer As System.Text.StringBuilder, ByVal PropertyBufferSize As Integer, ByRef RequiredSize As IntPtr) As Boolean
Private Declare Ansi Function SetupDiGetDeviceRegistryPropertyA Lib "Setupapi.dll" (ByVal DeviceInfoSet As IntPtr, ByVal DeviceInfoData As IntPtr, ByVal intProperty As Integer, ByRef PropertyRegDataType As IntPtr, ByVal PropertyBuffer() As Byte, ByVal PropertyBufferSize As Integer, ByRef RequiredSize As IntPtr) As Boolean
Private Declare Ansi Function SetupDiGetDeviceRegistryPropertyA Lib "Setupapi.dll" (ByVal DeviceInfoSet As IntPtr, ByVal DeviceInfoData As IntPtr, ByVal intProperty As Integer, ByRef PropertyRegDataType As IntPtr, ByRef PropertyBuffer As IntPtr, ByVal PropertyBufferSize As Integer, ByRef RequiredSize As IntPtr) As Boolean

'I created this structure to make it easier to pass data back to my form
Public Structure Device
Public Name As String
Public DevInfo As SP_DEVINFO_DATA
End Structure

'Represents an individual device
Public Structure SP_DEVINFO_DATA
Public cdSize As Integer
Public ClassGuid As Guid
Public DevInst As Integer
Public Reserved As UInt32
End Structure

'All the different Registry Keys that can be read for a particular device
Public Enum DeviceRegistryValueNames
SPDRP_DEVICEDESC = &H0 'DeviceDesc (R/W)
SPDRP_HARDWAREID = &H1 'HardwareID (R/W)
SPDRP_COMPATIBLEIDS = &H2 'CompatibleIDs (R/W)
SPDRP_SERVICE = &H4 'Service (R/W)
SPDRP_CLASS = &H7 'Class (R--tied to ClassGUID)
SPDRP_CLASSGUID = &H8 'ClassGUID (R/W)
SPDRP_DRIVER = &H9 'Driver (R/W)
SPDRP_CONFIGFLAGS = &HA 'ConfigFlags (R/W)
SPDRP_MFG = &HB 'Mfg (R/W)
SPDRP_FRIENDLYNAME = &HC 'FriendlyName (R/W)
SPDRP_LOCATION_INFORMATION = &HD 'LocationInformation (R/W)
SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = &HE 'PhysicalDeviceObjectName (R)
SPDRP_CAPABILITIES = &HF 'Capabilities (R)
SPDRP_UI_NUMBER = &H10 'UiNumber (R)
SPDRP_UPPERFILTERS = &H11 'UpperFilters (R/W)
SPDRP_LOWERFILTERS = &H12 'LowerFilters (R/W)
SPDRP_BUSTYPEGUID = &H13 'BusTypeGUID (R)
SPDRP_LEGACYBUSTYPE = &H14 'LegacyBusType (R)
SPDRP_BUSNUMBER = &H15 'BusNumber (R)
SPDRP_ENUMERATOR_NAME = &H16 'Enumerator Name (R)
SPDRP_SECURITY = &H17 'Security (R/W, binary form)
SPDRP_SECURITY_SDS = &H18 'Security (W, SDS form)
SPDRP_DEVTYPE = &H19 'Device Type (R/W)
SPDRP_EXCLUSIVE = &H1A 'Device is exclusive-access (R/W)
SPDRP_CHARACTERISTICS = &H1B 'Device Characteristics (R/W)
SPDRP_ADDRESS = &H1C 'Device Address (R)
SPDRP_UI_NUMBER_DESC_FORMAT = &H1D 'UiNumberDescFormat (R/W)
SPDRP_DEVICE_POWER_DATA = &H1E 'Device Power Data (R)
SPDRP_REMOVAL_POLICY = &H1F 'Removal Policy (R)
SPDRP_REMOVAL_POLICY_HW_DEFAULT = &H20 'Hardware Removal Policy (R)
SPDRP_REMOVAL_POLICY_OVERRIDE = &H21 'Removal Policy Override (RW)
SPDRP_INSTALL_STATE = &H22 'Device Install State (R)
SPDRP_LOCATION_PATHS = &H23 'Device Location Paths (R)
End Enum

'Types of Registry Keys
Private Enum Reg_Types
REG_SZ = 1
REG_BINARY = 3
REG_DWORD = 4
REG_MULTI_SZ = 7
End Enum

'Returns an array of devices from the given Device Info Set
Public Function GetDeviceNames(ByVal DevInfoSet As IntPtr) As Device()
'Variables used
Dim DeviceList As New ArrayList
Dim TempName As String
Dim newDevice As Device
Dim i As Integer = 0
Dim Result As SP_DEVINFO_DATA

Do
Try
'We will loop through until we get a 259 error (No More Items)
Result = GetDeviceInfoData(DevInfoSet, i)
Catch ex As Win32Exception When ex.NativeErrorCode = 259
'Ready to exit now
Result.DevInst = -1
Catch ex As Win32Exception
Throw
End Try
i += 1
If Result.DevInst = -1 Then Exit Do
Try
'Retrieve the Name for our device
TempName = GetDeviceName(DevInfoSet, Result)
If TempName Is Nothing Then TempName = "Name Unavailable"
newDevice.Name = TempName
newDevice.DevInfo = Result

'Adds the device
DeviceList.Add(newDevice)
Catch ex As Win32Exception
If ex.NativeErrorCode <> 13 Then Throw
End Try
Loop Until Result.DevInst = -1

'Creates an array of devices and returns it back
Dim arrDevices(DeviceList.Count - 1) As Device
DeviceList.CopyTo(arrDevices)
Return arrDevices
End Function

'Gets the Deivce info data. Given a Device Info Set (A Device Class), we loop through it by incrementing the MemberIndex Value
'Once we don't have any more we return -1
Private Function GetDeviceInfoData(ByVal DevInfoSet As IntPtr, ByVal MemberIndex As Integer) As SP_DEVINFO_DATA
Dim Result As Boolean
Dim DeviceInfoData As SP_DEVINFO_DATA
DeviceInfoData = New SP_DEVINFO_DATA
DeviceInfoData.DevInst = 0
DeviceInfoData.Reserved = Convert.ToUInt32(0)
DeviceInfoData.cdSize = Marshal.SizeOf(DeviceInfoData)
Result = SetupDiEnumDeviceInfo(DevInfoSet, MemberIndex, DeviceInfoData)
If Not Result Then
If Marshal.GetLastWin32Error <> 0 Then
Throw (New Win32Exception(Marshal.GetLastWin32Error))
End If
DeviceInfoData.DevInst = -1
End If
Return DeviceInfoData
End Function

'Returns the Device name for the device. This is just getting the DeviceDesc.
'We could also check the Friendly Name instead. That setting would be used first if it were available by windows I believe.
Private Function GetDeviceName(ByRef DevInfoSet As IntPtr, ByRef DeviceInfoData As SP_DEVINFO_DATA) As String
Return GetRegistryValue(DevInfoSet, DeviceInfoData, DeviceRegistryValueNames.SPDRP_DEVICEDESC)
End Function

'Gets a registry value for a device
'This function is a bit tricky since we have to figure out what type the Registry Value is and then handle it appropriately
Private Function GetRegistryValue(ByVal DevInfoSet As IntPtr, ByVal DeviceInfoData As SP_DEVINFO_DATA, ByVal ValueName As DeviceRegistryValueNames) As Object

'Creates our variables and creates a pointer to the DeviceInfoData structure
Dim Result As Boolean
Dim DevInfo As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(DeviceInfoData))
Marshal.StructureToPtr(DeviceInfoData, DevInfo, False)


Try
'Retrieves the Value Type
Dim ValueType As Reg_Types = GetRegistryType(DevInfoSet, DevInfo, ValueName)

'Selects the correct course of action.
Select Case ValueType
Case Reg_Types.REG_DWORD
'Just return the value as an integer
Dim TempValue As IntPtr
Result = SetupDiGetDeviceRegistryPropertyA(DevInfoSet, DevInfo, ValueName, IntPtr.Zero, TempValue, 1000, IntPtr.Zero)
Return TempValue.ToInt32
Case Reg_Types.REG_MULTI_SZ, Reg_Types.REG_SZ
'Loop through as a byte Array and assemble it at the end.
Dim intSize As IntPtr
Result = Not SetupDiGetDeviceRegistryPropertyA(DevInfoSet, DevInfo, ValueName, IntPtr.Zero, IntPtr.Zero, 0, intSize)
Dim TempValue(intSize.ToInt32) As Byte
Result = SetupDiGetDeviceRegistryPropertyA(DevInfoSet, DevInfo, ValueName, IntPtr.Zero, TempValue, intSize.ToInt32, intSize)
Dim TempStr As String
For Each bytChr As Byte In TempValue
If bytChr = 0 Then
TempStr &= vbCrLf
Else
TempStr &= Chr(bytChr)
End If
Next
Return TempStr.Trim 'The end has some extra white space
Case Reg_Types.REG_BINARY
'I didn't implement this.
Return "Not Reading Binary Values"
Case Else
'Who knows what happened.
Return Nothing
End Select
Catch When Not Result
'Error
If Marshal.GetLastWin32Error <> 0 Then
Throw (New Win32Exception(Marshal.GetLastWin32Error))
End If
Return Nothing
Catch ex As Win32Exception
Throw
End Try
End Function

'Returns the Type of Registry key
Private Function GetRegistryType(ByVal DevInfoSet As IntPtr, ByRef DevInfo As IntPtr, ByVal ValueName As DeviceRegistryValueNames) As Reg_Types
Dim Result As Boolean
Dim IntType As IntPtr = IntPtr.Zero
Result = SetupDiGetDeviceRegistryPropertyA(DevInfoSet, DevInfo, ValueName, IntType, New System.Text.StringBuilder(""), 0, IntPtr.Zero)
If Result Then
Return IntType.ToInt32
Else
Dim intErrorCode = Marshal.GetLastWin32Error
If intErrorCode = 122 Then Return IntType.ToInt32
If Marshal.GetLastWin32Error <> 0 Then
Throw (New Win32Exception(Marshal.GetLastWin32Error))
End If
Return Nothing
End If
End Function

GetDeviceNames
This function takes the pointer we retrieved from our previous post. We'll return a Device array. We defined this Device object. It is a Structure that contains the device 'friendly name' and the DevInfo object which we retrieve from the windows api.

We'll loop through, starting at zero, retrieving the DevInfo object and then getting the Device Name. We store each of those in an array and then return the results. We know that we are done when the windows api call returns a 259 error code. This means 'No More Items'.

GetDeviceName

This function will return the friendly name for a specific device. The DevInfoSet and DevInfo variables are required and a string function is returned by using the GetRegistryValue function.

GetDeviceInfoData
This function returns the DevInfo variable required by the GetDeviceNames function. We use the SetupDiEnumDeviceInfo function to retrieve this variable.

GetRegistryValue

This function takes in the DevInfoSet and DevInfo variable along with a specific value to retieve and then retrieves the value. For clarity all the values that we can retrieve are included in this post. The function uses the GetRegistryType function to determine the actual registry type that we are trying to retrieve. I did not implement the retrieval of Binary data. This hasn't been a problem for me. If I ever get around to implementing, I'll update this post.

GetRegistryType
This function returns the type of registry value we want to read. This is used by the GetRegistryValue function.

I realize that this post is not only long, but probably includes some more information than is actually needed to simply get the device names. But, I think it is important to put all these functions together since they all only use a combined two windows api calls.

Working With Devices: Actually getting to the devices

Up to this point we've worked with Device Classes. We now should get into the actual devices. Devices are grouped into Device Information Sets. If I understand Microsoft's description of it, Devices are grouped by Device Setup and Device Interface Classes. As mentioned previously Setup Classes are things like Network Adapters, while Interface Classes are things like PCI connections. These groupings can be pulled by using the SetupDiGetClassDevs function.

Private Declare Auto Function SetupDiGetClassDevs Lib "Setupapi.dll" (ByRef ClassGuid As Guid, ByVal Enumerator As UInt32, ByVal hwndParent As IntPtr, ByVal Flags As UInt32) As IntPtr

Out of the four arguments, I am only going to use the first and last. The first argument is the Guid that I want to retrieve. The Guid(s) we retrieved from the SetupDiClassGuidsFromNames can be used here. I think that Device Interface Class Guids could be used as well, I just never had a need for it.

The second argument are the flags we will use. The enumeration is defined below. You can refer to the function definition as to which one you want to use. You probably want to experiment with these to see which one suits your needs. I think that Present and Profile are your best bets if you are unsure.

'Scope to retrieve devices
Public Enum DeviceInfoSet_Scope
DIGCF_DEFAULT = &H1
DIGCF_PRESENT = &H2
DIGCF_ALLCLASSES = &H4
DIGCF_PROFILE = &H8
DIGCF_DEVICEINTERFACE = &H10
End Enum

Here is our code.

'Returns a Device Info Set based on Class Guid
Private Function GetDeviceInfoSet(ByVal ClassGuid As Guid, ByVal Scope As DeviceInfoSet_Scope) As IntPtr
Dim Result As IntPtr = SetupDiGetClassDevs(ClassGuid, Convert.ToUInt32(0), IntPtr.Zero, Convert.ToUInt32(Scope))
Return Result
End Function

Our result is returned as an IntPtr variable type.

It is important to note that once you are done with this object you need to destroy it. You can use the SetupDiDestroyDeviceInfoList to destory the IntPtr's. Here is the definition along with the code.

Private Declare Auto Function SetupDiDestroyDeviceInfoList Lib "Setupapi.dll" (ByVal DeviceInfoSet As IntPtr) As Boolean

'Destroys the device Info set
Public Sub DestroyDeviceInfoSet(ByVal DevInfoSet As IntPtr)
SetupDiDestroyDeviceInfoList(DevInfoSet)
End Sub

So the end result is a pointer to an object that will contain a list of devices.

Working With Devices: Getting Friendly Class Names

Ok, so once again I haven't posted in a long time. I'd like to say that I've decided to post at least once per week. Hopefully that will stay true. Given my past track record, your guess is as good as mine.

So, this time around we are going to get the friendly names for our device classes. Previously we may have retrieved a device class name of 'net'. Looking in the Device Manager we only find 'Network adapters'. That is the friendly name and the name you'll likely want to display.

So here is the code using the SetupDiGetClassDescription function.

Private Declare Auto Function SetupDiGetClassDescription Lib "setupapi.dll" (ByRef ClassGuid As Guid, ByVal ClassDescription As System.Text.StringBuilder, ByVal ClassDescriptionSize As Integer, ByRef RequiredSize As IntPtr) As Boolean

'Returns the Class Description (Friendly name for the Class is a better way to put it)
Public Function GetClassDescription(ByVal ClassName As String) As String

'The First section we get the length that we need

Dim intSize As IntPtr
Dim ClassGuid As Guid = GetClassGuid(ClassName)
Dim Result As Boolean = SetupDiGetClassDescription(ClassGuid, Nothing, 0, intSize)
If Not Result Then
Dim intErrorCode As Integer = Marshal.GetLastWin32Error
If intErrorCode <> 122 Then '122 is insufficient buffer (We expect it to happen)
Throw New Win32Exception(intErrorCode)
End If
End If


'The Second section we instantiate an object of the given size and get the Description

Dim ClassDesc As New System.Text.StringBuilder(intSize.ToInt32)
Result = SetupDiGetClassDescription(ClassGuid, ClassDesc, intSize.ToInt32, intSize)
If Not Result Then
Throw New Win32Exception(Marshal.GetLastWin32Error)
End If
Return ClassDesc.ToString
End Function

The above code seems to be pretty typical for these functions. First we get the size of the Class Description and then we get the actual description and return it.

Working with Devices: Retrieving Class Guids From Class Name

Its been sometime since my last post. I've been attending to my new job and new daughter.

So, the previous post demonstrated how to get the Class Name from the Guid. This time I want to show how to get the Class Guid from the Class Name. You'll be using the SetupDiClassGuidsFromName function. Here is how I declared it.

Private Declare Auto Function SetupDiClassGuidsFromName Lib "Setupapi.dll" (ByVal ClassName As String, ByVal ClassGuidList() As Guid, ByVal ClassGuidListSize As Integer, ByRef RequiredSize As Integer) As Boolean


There are four parameters to pass into this function. The first is the name. Simply enough that is just a string with the non-friendly name of the class. So for example 'Net' is the name for the Network Adapter class. The second parameter is to retrieve the Guids. The third and fourth parameter are used to size our array. The way it works is that we pass 0 to the third parameter meaning our Guid Array is empty and we have an array setup to receive the fourth parameter output. What will happen is the fourth parameter (RequiredSize) will tell us how big our array should be. We then resize our Guid Array and set the third parameter (ClassGuidListSize) to what the required size should be.

Here is the code

'Returns the Class Guid
Function GetClassGuidsFromName(ByVal ClassName As String) As Guid()
Dim intSize, intReqSize As Integer
Dim GuidList() As Guid
If (Not SetupDiClassGuidsFromName(ClassName, GuidList, intSize, intReqSize)) Then
intSize = intReqSize
ReDim GuidList(intSize - 1)
If (SetupDiClassGuidsFromName(ClassName, GuidList, intSize, intReqSize)) Then
Return GuidList
End If
End If
End Function

Calling GetClassGuidsFromName("USB") will return a Guid array with , in my case two items.

Next time we'll get the Class Description. Otherwise known as the friendly name for the Class.


*** UPDATE ***
I previously had posted a different set of code for this post, because of my inability to code to receive multiple Guids. That seems to be resolved with this above code and thus I've updated this post accordingly.

Working with Devices: Retrieving Device Setup Class Names

We now have our GUID for the Setup Class. Maybe we want to get the name of the class. We can do that by using the following function:

Private Declare Ansi Function SetupDiClassNameFromGuidA Lib "setupapi.dll" (ByRef ClassGuid As Guid, ByVal ClassName As System.Text.StringBuilder, ByVal ClassNameSize As Integer, ByRef RequiredSize As IntPtr) As Boolean

I am explicitly calling the Ansi function. I believe that calling just the SetupDiClassNameFromGuid without the 'A' should default to the correct function ('A' or 'W'), but, I couldn't get that working correctly for some reason.

Here is the function that will return the name of the device setup class.



Imports System.Runtime.InteropServices
Imports System.ComponentModel

'Returns a Class name from its Guid

Private Function GetClassName(ByVal ClassGuid As Guid) As String
'We first get the Name's Length

Dim intRequiredSize As IntPtr
Dim Result As Boolean
Result = SetupDiClassNameFromGuidA(ClassGuid, Nothing, 0, intRequiredSize)
If Not Result Then
Dim intErrorCode As Integer = Marshal.GetLastWin32Error()
If intErrorCode <> 122 Then Throw (New Win32Exception(intErrorCode))
End If

'Now we instantiate that StringBuilder with the size and get the name

Dim ClassName As New System.Text.StringBuilder(intRequiredSize.ToInt32)
Result = SetupDiClassNameFromGuidA(ClassGuid, ClassName, intRequiredSize.ToInt32, intRequiredSize)
If Not Result Then Throw (New Win32Exception(Marshal.GetLastWin32Error))
Return ClassName.ToString
End Function

When retrieving strings and such it is fairly common to see that we first call the function without providing a buffer to store the data and then provide a buffer. The reason for this is because we will get back the required size by first calling the function as we did above.

Using the code from the previous post and this code I can show you the output from my computer.

25dbce51-6c8f-4a72-8a6d-b54c2b4fc835 WCEUSBS
36fc9e60-c465-11cf-8056-444553540000 USB
4658ee7e-f050-11d1-b6bd-00c04fa372a7 PnpPrinters
48721b56-6795-11d2-b1a8-0080c72e74a2 Dot4
49ce6ac8-6f86-11d2-b1e5-0080c72e74a2 Dot4Print
4d36e965-e325-11ce-bfc1-08002be10318 CDROM
4d36e966-e325-11ce-bfc1-08002be10318 Computer
4d36e967-e325-11ce-bfc1-08002be10318 DiskDrive
4d36e968-e325-11ce-bfc1-08002be10318 Display
4d36e969-e325-11ce-bfc1-08002be10318 fdc
4d36e96a-e325-11ce-bfc1-08002be10318 hdc
4d36e96b-e325-11ce-bfc1-08002be10318 Keyboard
4d36e96c-e325-11ce-bfc1-08002be10318 MEDIA
4d36e96d-e325-11ce-bfc1-08002be10318 Modem
4d36e96e-e325-11ce-bfc1-08002be10318 Monitor
4d36e96f-e325-11ce-bfc1-08002be10318 Mouse
4d36e970-e325-11ce-bfc1-08002be10318 MTD
4d36e971-e325-11ce-bfc1-08002be10318 MultiFunction
4d36e972-e325-11ce-bfc1-08002be10318 Net
4d36e973-e325-11ce-bfc1-08002be10318 NetClient
4d36e974-e325-11ce-bfc1-08002be10318 NetService
4d36e975-e325-11ce-bfc1-08002be10318 NetTrans
4d36e977-e325-11ce-bfc1-08002be10318 PCMCIA
4d36e978-e325-11ce-bfc1-08002be10318 Ports
4d36e979-e325-11ce-bfc1-08002be10318 Printer
4d36e97b-e325-11ce-bfc1-08002be10318 SCSIAdapter
4d36e97d-e325-11ce-bfc1-08002be10318 System
4d36e97e-e325-11ce-bfc1-08002be10318 Unknown
4d36e980-e325-11ce-bfc1-08002be10318 FloppyDisk
50127dc3-0f36-415e-a6cc-4cb3be910b65 Processor
50906cb8-ba12-11d1-bf5d-0000f805f530 MultiPortSerial
50dd5230-ba8a-11d1-bf5d-0000f805f530 SmartCardReader
533c5b84-ec70-11d2-9505-00c04f79deaf VolumeSnapshot
66f250d6-7801-4a64-b139-eea80a450b24 1394Debug
6bdd1fc1-810f-11d0-bec7-08002be2092f 1394
6bdd1fc5-810f-11d0-bec7-08002be2092f Infrared
6bdd1fc6-810f-11d0-bec7-08002be2092f Image
6d807884-7d21-11cf-801c-08002be10318 TapeDrive
71a27cdd-812a-11d0-bec7-08002be2092f Volume
72631e54-78a4-11d0-bcf7-00aa00b7b32a Battery
745a17a0-74d3-11d0-b6fe-00a0c90f57da HIDClass
7ebefbc0-3200-11d2-b4c2-00a0c9697d07 61883
8ecc055d-047f-11d1-a537-0000f8753ed1 LegacyDriver
a0a588a4-c46f-4b37-b7ea-c82fe89870c6 SDHost
a1aa288f-bd7d-4a38-bee3-7d7cbc3674d8 UsbPcCardReader
c06ff265-ae09-48f0-812c-16753d7cba83 Avc
c459df55-db08-11d1-b009-00a0c9081ff6 Enum1394
ce5939ae-ebde-11d0-b181-0000f8753ec4 MediumChanger
d45b1c18-c8fa-11d1-9f77-0000f805f530 NtApm
d48179be-ec20-11d1-b6b8-00c04fa372a7 SBP2
e0cbf06c-cd8b-4647-bb8a-263b43f0f974 Bluetooth
eec5ad98-8080-425f-922a-dabf3de3f69a WPD
feb8d079-0681-11d4-9531-0060089abc08 USB


Notice that there are two GUIDs that map to the name 'USB'. Device Setup Classes can use the same name for more than one GUID. To tell you the truth though, I'm not sure why. Also notice that some of these aren't standard. For example, 'UsbPcCardReader' is a class installed by the manufacturer of a USB PCMCIA Card reader.

Working with Devices: Enumerating Device Setup Classes

So as promised lets enumerate through the Device Setup Classes. Sometimes I may say Device Class. This may be ambiguous with Device Setup Class and Device Interface Class. Most times I mean Device Setup Class, but I will try to correct myself as I go.

So the easiest way to see the device setup classes on your computer is simply to view it from the Registry. Under HKLM\SYSTEM\CurrentControlSet\Control\Class you'll find several Subkeys. Each subkey represents a setup class by GUID. You can look at each one and see the name for the setup class.

For example look at the GUID {4D36E972-E325-11CE-BFC1-08002bE10318}. You should see that this correlates to the net class. 'net' translates into the friendly name 'Network adapters'. By expanding this key you'll see more subkeys. Each subkey represents a device that falls under the net class.

You may be asking yourself "why do I have so many listed when only a handful show up in device manager?" This is because a key is created at any time a device is added to the computer. Even if it is removed the key will remain. Also there is typically more than one device installed when a physical device is installed. Another reason is that programs can install non-physical devices like VPN Adapters. Finally many devices are simply hidden in the device manager.

So we can get at this through the function CM_Enumerate_Classes

Here is the declare statement that I used.

Private Declare Auto Function CM_Enumerate_Classes Lib "cfgmgr32.dll" (ByVal ClassIndex As Integer, ByRef ClassGuid As Guid, ByVal Reserved As Integer) As Integer

The first argument is an index. We'll start at 0 and then keep incrementing. The next argument is the Guid that is outputted. This is the Guid that is at that specific index. The last argument is reserved and a 0 should be passed.

Here is some code that will set an index at 0 and keep incrementing it until the result is not 0. Technically, I believe an error CR_NO_SUCH_VALUE will be returned. The Guid will be added to an array list and then that list is sorted and returned.

'Returns an array of all the Class Guids on the System
Private Function GetClassGuids() As ArrayList
Dim i As Integer = 0
Dim Result As Integer
Dim ClassGuid As Guid
Dim GuidList As New ArrayList
Do
Result = CM_Enumerate_Classes(i, ClassGuid, 0)
i += 1
If Result = 0 Then GuidList.Add(ClassGuid)
Loop Until Result <> 0
GuidList.Sort()
Return GuidList
End Function


So thats it.

Oh, this code was written in VS.NET 2003 for .NET v1.1

Working with Devices: General Overview

So lets take a look at what is the most common view of devices on windows

On the left you see the Device Manager. To get to this just right click on My Computer and click Manage then click on Device Manager. You can also add it to the mmc console like I did.

It shows a tree view with your computer listed at the root and devices under neath that.

Each Item under your computer is called a Device Setup Class. Your devices are members of a Device Setup Class.

So you can see the Printer Port device is a member of the Ports Setup Class.

Another Class that is used is called the Device Interface Class. This class basically defines how the device is connected to the computer.

Imagine that you have two CD-ROM Drives. One drive is connected through an IDE Cable to your motherboard and the other is connected by USB. This means that one CD-ROM drive would be in the USB Device interface Class and the other would be in a different Device Interface Class (IDE I guess). But, BOTH would be in the CD-ROM Setup Class.

In my limited experience it seems that for interacting with devices for actions such as getting information on them and disabling/enabling then you'll be using the Device Setup Classes. In the case of registering your application for notifications on devices being inserted and removed then you would be using the Device Interface Classes.

I'll also say that the Device Interface Classes aren't defined in one single location. I think that the issue is that the developer should know the Class that he'll be working with and therefore know things like the GUID and whatnot.

By comparison, the Device Setup Classes' GUIDs are quite readily accessible in one single location. That is what we'll be going over on the next post.

For more information on the Device Classes take a look at Microsoft's MSDN Article.

Working with Devices

I've written a couple programs that have used the SetupAPI and CfgMgr API. What made it difficult for me was understanding how to create the correct declare statement and then the proper usage. Normally the usage was given in C++. While I could read most of it, some parts were foreign to me.

So what I thought was that I would start to write on this blog a bit more and post some of the functions along with their use in some sort of sample. I'll try to start off with the functions with the least dependencies (preferably none) and then start adding more functions that utilize previously discussed functions.

Unsigned Driver Problem

Well its been a while since I wrote in this thing. Had a problem earlier this week that I was working on with the rest of the team. Drivers were being preinstalled on a workstation for use by devices that would be plugged in at a later point by a locked-down user. These drivers weren't being used and the user is then being prompted for an Admin account to finish the install.

So we started looking into it. I eventually made my way to the SetupAPI log file @ C:\Windows\SetupApi.log. In it I noticed that where it was showing the driver fail to install it gave in parenthesis about a server side installation. I thought that was odd until I found this Microsoft Article. I noticed that the log had entries stating that the driver wasn't signed.

I went into VB.NET and modified a hash compute program I had. Instead of using an MD5 Hash, I chose Sha1. I then used it to compute the hash on the files and compared them to the tag of the inf file. Sure enough the hash was different. Additionally the hash on the system files was different as well. I then noticed that other drivers that worked had hashes that matched for the inf and ini files, but failed for the sys and dll files. More on that later.

Our team decided that we should just sign them ourself. None of us are certain on how kosher this is, but none the less we proceeded. We found a Microsoft Doc that walked through the basics of signing a driver. What we found was that we needed the Windows Driver Kit. It just so happens that Microsoft has the RTM version of the Vista WDK @ Microsoft Connect.

To summarize how it is done:

  1. Use MakeCert to make a certificate
  2. Import that cert into the Trusted Root and Trusted Publishers sections using CertMgr
  3. Use Signability to create an unsigned Catalog file.
  4. Use Signtool to sign the Catalog file.
Note: all these tools we in the %WDK Directory%\bin\SelfSign\ Your results may differ.


Now all we have to do is import that cert that we created and then pre-install the drivers.


Everything sailed smoothly after that. One thing I couldn't understand was why the sys and dll files came up with different Sha1 hashes then what I would compute them out to be, but they would still be considered valid by the Install process.

I cleared up my confusion by finding this Microsoft Article. It stated the following

"A flat file hash is not used for files, such as drivers, that use the portable executable (PE) file format. Instead, relevant sections such as the PE header, executable data, and authenticated attributes are selectively hashed."

I next found a useful API Call, CryptCatAdminCalcHashFromFileHandle. I used this to make another VB.NET app and verified that the hash was correct for the PE file formats.

Cool Stuff