Introduction
In my last article (Enumerate Properties of an Installed Device), I used Setup APIs to enumerate all of the properties of an installed device. But these properties are common for all devices and there were no specific properties that differ with other devices' properties. For example, a sound card has some properties that are different with properties of a network adapter.
Also, during these months, several developers asked me for device specific properties and how can one change them programmatically. I searched over the net but I could not find any related article. Now, this article is my effort to solving the problem.
Reverse Engineering The Device Manager
The first step is searching the MSDN. I searched the MSDN to find any API related to Device Manager; for example, an API that gets some information about a device (e.g., DevNode, Device GUID or etc.) and shows a dialog (may be Device Property Sheet) of properties. But there is no such API. At least, I can't find any, if anyone knows such an API, I would be glad to be informed.
The second step is to study more about the Device Manager. The Device Manager is a DLL (DevMgr.dll) that is used in MMC as a snap-in. Figure 2 shows it:
I used PE File Explorer (a free utility with source code) for exploring PE (Portable Executable) files like *.exe, *.dll, *.ocx, and etc.
With this utility, I found exported functions. That made an idea for me. How can other programs call these exported (and also undocumented) functions to show device property sheets?
Figure 3 shows PEFileExplorer that opens DevMgr.dll.
A big step was done with PEFileExplorer. Now I know which functions are exported by the DLL, and this makes me to search about exported functions.
From MSDN, I found that Rundll.exe and Rundll32.exe allow to invoke a function exported from a DLL. However, Rundll and Rundll32 programs do not allow you to call any exported function from any DLL. For example, you can not use these utility programs to call the Win32 API (Application Programming Interface) calls exported from the system DLLs. The programs only allow you to call functions from a DLL that are explicitly written to be called by them.
The command line for Rundll32.exe is as follows:
Rundll32.exe <name of dll>
<entry point function> <arguments>
With a deep look on the list of exported functions, everyone can understand that the entry point of DevMgr.dll is DeviceProperties_RunDLLA
(ANSI version) and DeviceProperties_RunDLLW
(UNICODE version). As Microsoft Knowledge Base Article - 164787 suggests, Rundll32.exe can handle the entry point function without using A or W. In other words, we are required to call the entry point as follows:
Rundll32.exe devmgr.dll DeviceProperties_RunDLL /DeviceID
root\system\0000
The above syntax means Rundll32.exe should call DeviceProperties_RunDll
(as an entry point of DevMgr.dll) and pass /DeviceID root\system\0000
as argument of the function.
Solution
In the previous article, I showed you how we can obtain DeviceID
of an installed device. Then by calling Rundll32.exe with proper arguments, the property sheet of an installed device will appear.
The following code shows an example:
void CDevicePropertySheetDialogDlg::OnDeviceProperty()
{
DEVNODE dn;
for (int i=0; i<m_Devices.GetItemCount(); i++)
{
if (m_Devices.GetItemState(i, LVIS_SELECTED))
{
dn=(DEVNODE) m_Devices.GetItemData(i);
break;
}
}
CString CommandLine;
for (int j=0; j<DeviceProperty.size(); j++)
{
if (DeviceProperty[j].dn==dn)
{
CommandLine.Format(_T("DevMgr.dll
DeviceProperties_RunDLL /DeviceID \"%s\""),
DeviceProperty[j].Properties[ID_DEVICEID].PropertyValue);
break;
}
}
ShellExecute(m_hWnd, _T("open"), _T("Rundll32.exe"),
CommandLine, NULL, SW_SHOW);
}
I assume that all of the devices listed in a list view control (m_Devices
) and properties of the device (including DeviceID
) are saved in DevicePropert
vector.
Alternative Way
An alternative way is to load DevMgr.dll dynamically with LoadLibrary
API. Here is the changed code to load the device property sheet with LoadLibrary
API:
void CDevicePropertySheetDialogDlg::OnDeviceProperty()
{
DEVNODE dn;
for (int i=0; i<m_Devices.GetItemCount(); i++)
{
if (m_Devices.GetItemState(i, LVIS_SELECTED))
{
dn=(DEVNODE) m_Devices.GetItemData(i);
break;
}
}
CString CommandLine;
for (int j=0; j<DeviceProperty.size(); j++)
{
if (DeviceProperty[j].dn==dn)
{
CommandLine.Format(_T("/MachineName \"\" /DeviceID %s"),
DeviceProperty[j].Properties[ID_DEVICEID].PropertyValue);
break;
}
}
PDEVICEPROPERTIES pDeviceProperties;
HINSTANCE hInst=AfxGetInstanceHandle();
HINSTANCE hDevMgr = LoadLibrary(_TEXT("devmgr.dll"));
if (hDevMgr)
{
pDeviceProperties = (PDEVICEPROPERTIES) GetProcAddress((HMODULE) hDevMgr,
DeviceProperties_RunDLL);
}
if (pDeviceProperties)
{
pDeviceProperties(m_hWnd, hInst, CommandLine.GetBuffer(0), SW_SHOW);
}
}
It should be noticed that DeviceProperties_RunDLL
is defined as below:
#ifdef _UNICODE
#define DeviceProperties_RunDLL "DeviceProperties_RunDLLW"
typedef void (_stdcall *PDEVICEPROPERTIES)(
HWND hwndStub,
HINSTANCE hAppInstance,
LPWSTR lpCmdLine,
int nCmdShow
);
#else
#define DeviceProperties_RunDLL "DeviceProperties_RunDLLA"
typedef void (_stdcall *PDEVICEPROPERTIES)(
HWND hwndStub,
HINSTANCE hAppInstance,
LPSTR lpCmdLine,
int nCmdShow
);
#endif
Thanks to Martin Rubas for his comment.
Enjoy!