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.

No comments: