I have this C++ WinAPI program. If we assume that the following source code is compiled into devctrl.exe
, running
devctrl.exe <DEVICE_GUID>
uninstalls the device with GUID class <DEVICE_GUID>
If, however, it is run as
devctrl.exe --install <DEVICE_GUID>
it should reinstall the uninstalled device.
In addition, if the program succeeds, but a reboot is required, an appropriate message is shown before closing the console.
#define _CRT_SECURE_NO_WARNINGS
#include <cstdlib>
#include <string>
#include <windows.h>
#include <setupapi.h>
#include <shlwapi.h>
#include <newdev.h>
#include <iostream>
#include <conio.h>
#include <algorithm>
#include <cctype>
#pragma comment (lib, "Setupapi.lib")
#pragma comment (lib, "shlwapi.lib")
#pragma comment (lib, "newdev.lib")
static const std::string installFlag = "--install";
// The following function is from:
// https://stackoverflow.com/questions/1387064/how-to-get-the-error-message-from-the-error-code-returned-by-getlasterror
static std::string GetLastErrorAsString()
{
//Get the error message ID, if any.
DWORD errorMessageID = GetLastError();
if (errorMessageID == 0) {
return std::string(); //No error message has been recorded
}
LPSTR messageBuffer = nullptr;
//Ask Win32 to give us the string version of that message ID.
//The parameters we pass in, tell Win32 to create the buffer that holds the message for us (because we don't yet know how long the message string will be).
size_t size =
FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
errorMessageID,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR) &messageBuffer,
0,
NULL);
//Copy the error message into a std::string.
std::string message(messageBuffer, size);
//Free the Win32's string's buffer.
LocalFree(messageBuffer);
return message;
}
// The following three trim functions are from:
// https://stackoverflow.com/questions/216823/how-to-trim-an-stdstring
// trim from start (in place)
static void ltrim(std::string& s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
}
// trim from end (in place)
static void rtrim(std::string& s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), s.end());
}
// trim from both ends (in place)
static void trim(std::string& s) {
rtrim(s);
ltrim(s);
}
static wchar_t* ConvertCharStringToWcharString(char* source) {
std::string s = source;
trim(s);
size_t guidLength = s.length();
wchar_t* target = new wchar_t[guidLength + 1];
target[guidLength] = L'\0';
mbstowcs(target, s.c_str(), guidLength);
return target;
}
static void PromptToExit(std::ostream& out) {
out << "Press any key to exit.\n";
_getch();
}
static bool LoadDeviceData(
char* guidString,
HDEVINFO* pDevInfo,
PSP_DEVINFO_DATA pData,
bool install) {
GUID guid;
HRESULT hResult =
CLSIDFromString(
ConvertCharStringToWcharString(guidString),
(LPCLSID) &guid);
if (hResult == CO_E_CLASSSTRING) {
std::cerr << "ERROR: Bad GUID string: "
<< GetLastErrorAsString()
<< "\n";
PromptToExit(std::cerr);
return false;
}
HDEVINFO hDeviceInfo =
SetupDiGetClassDevsExA(
&guid,
NULL,
NULL,
install ? DIGCF_ALLCLASSES : DIGCF_PRESENT,
NULL,
NULL,
NULL);
if (hDeviceInfo == INVALID_HANDLE_VALUE) {
std::cerr << "ERROR: Could not obtain HDEVINFO: "
<< GetLastErrorAsString()
<< "\n";
PromptToExit(std::cerr);
return false;
}
*pDevInfo = hDeviceInfo;
pData->cbSize = sizeof(SP_DEVINFO_DATA);
pData->ClassGuid = guid;
BOOL deviceEnumerated =
SetupDiEnumDeviceInfo(
hDeviceInfo,
0,
pData);
if (!deviceEnumerated) {
std::cerr << "ERROR: Could not enumerate the SP_DEVINFO_DATA: "
<< GetLastErrorAsString()
<< "\n";
PromptToExit(std::cerr);
return false;
}
return true;
}
int main(int argc, char* argv[]) {
if (argc == 1) {
std::cout << "ERROR: No GUID specified.\n";
PromptToExit(std::cerr);
return EXIT_FAILURE;
}
if (argc > 3) {
PathStripPathA(argv[0]);
std::cout << "Usage: " << argv[0] << " [--install] GUID\n";
PromptToExit(std::cerr);
return EXIT_FAILURE;
}
bool install = false;
char* guidParameter = NULL;
if (argc == 2) {
guidParameter = argv[1];
} else if (argc == 3) {
std::string flagParameter = argv[1];
if (flagParameter != installFlag) {
std::cerr << "ERROR: Wrong flag: " << flagParameter << "\n";
PromptToExit(std::cerr);
return EXIT_FAILURE;
}
guidParameter = argv[2];
install = true;
}
HDEVINFO hDevInfo;
SP_DEVINFO_DATA deviceData;
bool ok =
LoadDeviceData(
guidParameter,
&hDevInfo,
&deviceData,
install);
if (!ok) {
return EXIT_FAILURE;
}
if (install) {
BOOL rebootNeeded;
BOOL installStatus =
DiInstallDevice(
NULL,
hDevInfo,
&deviceData,
NULL,
0,
&rebootNeeded);
if (!installStatus) {
std::cerr << "ERROR: Could not install the device: "
<< GetLastErrorAsString()
<< "\n";
return EXIT_FAILURE;
}
else {
if (rebootNeeded) {
std::cout << "You need to reboot your computer "
<< "for changes to take effect.\n";
PromptToExit(std::cout);
}
return EXIT_SUCCESS;
}
} else {
BOOL rebootNeeded;
BOOL removeStatus =
DiUninstallDevice(
NULL,
hDevInfo,
&deviceData,
0,
&rebootNeeded);
if (!removeStatus) {
std::cerr << "ERROR: Could not remove the device: "
<< GetLastErrorAsString()
<< "\n";
return EXIT_FAILURE;
}
else {
if (rebootNeeded) {
std::cout << "You need to reboot your computer "
<< "for changes to take effect.\n";
PromptToExit(std::cout);
}
return EXIT_SUCCESS;
}
}
return EXIT_SUCCESS;
}
The problem is when I run devctrl.exe
(install or uninstall), nothing happens.
So, what am I doing wrong here? Some comments suggest that the device calls to playing with the device are tricked by the antivirus. If that is so, is there a way to "white list" the devctrl.exe
?