Introduction
Windows has a rich collection of APIs to get useful information about installed devices. In this article, I will show how you can enumerate devices on a machine using Setup API.
The program has a simple GUI: only a list control that shows all of the installed devices. All the information about a device (such as name and its icon) is grabbed from Windows Setup API.
Setup API
The Setup application programming interface (API) provides a set of functions that your setup application can call to perform installation operations or get several information about installed devices, their class and also their GUID (a unique identifier for every device).
The application requires the following APIs (description of these APIs was taken from MSDN):
WINSETUPAPI BOOL WINAPI SetupDiGetClassImageList(
OUT CLASSIMAGELIST_DATA ClassImageListData);
The SetupDiGetClassImageList
function builds an image list that contains bitmaps for every installed class and returns the list in a data structure.
WINSETUPAPI BOOL WINAPI SetupDiDestroyClassImageList(
IN PSP_CLASSIMAGELIST_DATA ClassImageListData);
The SetupDiDestroyClassImageList
function destroys a class image list that was built by a call to SetupDiGetClassImageList
.
WINSETUPAPI BOOL WINAPI SetupDiGetClassImageIndex(
IN PSP_CLASSIMAGELIST_DATA ClassImageListData,
IN LPGUID ClassGuid,
OUT PINT ImageIndex);
The SetupDiGetClassImageIndex
function retrieves the index within the class image list of a specified class.
CMAPI CONFIGRET WINAPI CM_Connect_Machine(
IN PCTSTR UNCServerName,
OUT PHMACHINE phMachine);
CMAPI CONFIGRET WINAPI CM_Disconnect_Machine(
IN HMACHINE hMachine);
The CM_Connect_Machine
function creates a connection to a local or remote machine. The CM_Disconnect_Machine
function removes a connection to a local or remote machine.
CMAPI CONFIGRET WINAPI CM_Locate_DevNode_Ex(
OUT PDEVINST pdnDevInst,
IN DEVINSTID pDeviceID, OPTIONAL
IN ULONG ulFlags,
IN HMACHINE hMachine);
The CM_Locate_DevNode_Ex
function obtains a device instance handle to the device node that is associated with a specified device instance identifier, on a local or a remote machine.
pDeviceID
represents a device instance identifier. If this value is NULL
, or if it points to a zero-length string, the function supplies a device instance handle to the device node at the top of the device tree. The PnP Manager maintains a device tree that keeps track of the devices in the system. The following figure shows the device tree for a sample system configuration. The device tree contains information about the devices present on the system. The PnP Manager builds this tree when the machine boots, using information from drivers and other components, and updates the tree as devices are added or removed. Each node of the device tree is called a device node, or devnode
. A devnode
consists of the device objects for the device's drivers, plus internal information maintained by the system. Therefore, there is a devnode
for each device stack.
CMAPI CONFIGRET WINAPI CM_Get_Child_Ex(
OUT PDEVINST pdnDevInst,
IN DEVINST dnDevInst,
IN ULONG ulFlags,
IN HMACHINE hMachine);
The CM_Get_Child_Ex
function is used to retrieve a device instance handle to the first child node of a specified device node, in a local or a remote machine's device tree.
CMAPI CONFIGRET WINAPI CM_Get_Sibling_Ex(
OUT PDEVINST pdnDevInst,
IN DEVINST DevInst,
IN ULONG ulFlags,
IN HMACHINE hMachine);
The CM_Get_Sibling_Ex
function obtains a device instance handle to the next sibling node of a specified device node, in a local or a remote machine's device tree.
Solution
According to the above functions, we should first establish a connection to our computer (local or remote), then get the top node of the device tree (Root device). After this, we should enumerate the installed devices by a simple loop. The application uses three function: EnumDevices()
, RetrieveSubNodes()
and GetDeviceName()
. Following is the source of them:
void EnumDevices()
{
TCHAR LocalComputer[MAX_PATH];
DWORD Size = MAX_PATH - 2;
GetComputerName(LocalComputer + 2, &Size);
LocalComputer[0] = _T('\\');
LocalComputer[1] = _T('\\');
CONFIGRET cr;
cr = CM_Connect_Machine(LocalComputer, &m_hMachine);
if (cr != CR_SUCCESS)
{
TCHAR Text[MAX_PATH];
wsprintf(Text, _T("Machine Connection failed, cr= %lx(hex)\n"), cr);
::MessageBox(m_hWnd, Text, _T("Error"), MB_ICONSTOP | MB_OK);
return;
}
m_ImageListData.cbSize = sizeof(m_ImageListData);
SetupDiGetClassImageList(&m_ImageListData);
m_ImageList.Attach(m_ImageListData.ImageList);
m_Devices.SetImageList(&m_ImageList, LVSIL_SMALL);
m_Devices.SetImageList(&m_ImageList, LVSIL_NORMAL);
DEVNODE dnRoot;
CM_Locate_DevNode_Ex(&dnRoot, NULL, 0, m_hMachine);
DEVNODE dnFirst;
CM_Get_Child_Ex(&dnFirst, dnRoot, 0, m_hMachine);
RetrieveSubNodes(dnRoot, NULL, dnFirst);
CM_Disconnect_Machine(m_hMachine);
}
void RetrieveSubNodes(DEVINST parent, DEVINST sibling, DEVNODE dn)
{
DEVNODE dnSibling, dnChild;
do
{
CONFIGRET cr = CM_Get_Sibling_Ex(&dnSibling, dn, 0, m_hMachine);
if (cr != CR_SUCCESS)
dnSibling = NULL;
TCHAR GuidString[MAX_GUID_STRING_LEN];
ULONG Size = sizeof(GuidString);
cr = CM_Get_DevNode_Registry_Property_Ex(dn, CM_DRP_CLASSGUID, NULL,
GuidString, &Size, 0, m_hMachine);
if (cr == CR_SUCCESS)
{
GUID Guid;
GuidString[MAX_GUID_STRING_LEN - 2] = _T('\0');
UuidFromString(&GuidString[1], &Guid);
int Index;
if (SetupDiGetClassImageIndex(&m_ImageListData, &Guid, &Index))
{
CString DeviceName=GetDeviceName(dn);
m_Devices.InsertItem(m_Devices.GetItemCount(), DeviceName, Index);
}
}
cr = CM_Get_Child_Ex(&dnChild, dn, 0, m_hMachine);
if (cr == CR_SUCCESS)
{
RetrieveSubNodes(dn, NULL, dnChild);
}
dn = dnSibling;
} while (dn != NULL);
}
CString GetDeviceName(DEVNODE DevNode)
{
CString strType;
CString strValue;
CString DisplayName;
LPTSTR Buffer;
int BufferSize = MAX_PATH + MAX_DEVICE_ID_LEN;
ULONG BufferLen = BufferSize * sizeof(TCHAR);
Buffer = strValue.GetBuffer(BufferSize);
if (CR_SUCCESS == CM_Get_DevNode_Registry_Property_Ex(DevNode,
CM_DRP_FRIENDLYNAME, NULL,
Buffer, &BufferLen, 0, m_hMachine))
{
DisplayName = Buffer;
}
else
{
BufferLen = BufferSize * sizeof(TCHAR);
if (CR_SUCCESS == CM_Get_DevNode_Registry_Property_Ex(DevNode,
CM_DRP_DEVICEDESC, NULL,
Buffer, &BufferLen, 0, m_hMachine))
{
DisplayName = Buffer;
}
else
{
DisplayName=_T("Unknown Device");
}
}
return DisplayName;
}
m_Devices
is a list control, m_ImageList
is an image list of device icons and m_hMachine
is a handle of the connected machine.
Enjoy!