0

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?

coderodde
  • 1,269
  • 4
  • 17
  • 34
  • Are you running with admin privileges? – Alan Birtles Jul 03 '23 at 07:26
  • Assuming that you run program outside IDE, antivirus or security settings are culprit, most likely. If you had error status, it'd be returned (one of three possible values for `GetLastError`). As these functins are meant to be run from trustedcontext usually, antiviruses like Kaspersky _would_ block API call, but let it return state of success. Installing unsubsribed device from unsubscribed context of program from unknown origin? It goes "nope" – Swift - Friday Pie Jul 03 '23 at 07:32
  • @AlanBirtles Yes. In fact, I added the MANIFESTUAC option to the Win linker, so that it always asks for admin privileges. – coderodde Jul 03 '23 at 07:40
  • @Swift-FridayPie Is there in Windows somewhere a "white list" to which I could add my program? – coderodde Jul 03 '23 at 07:45
  • 1
    as a side note I think you should not assume in your function `ConvertCharStringToWcharString` that there is a 1:1 between wchar and char. instead call mbstowcs twice, one time to get the new length, second time to convert. also check return value of mbstowcs – AndersK Jul 03 '23 at 07:47
  • @Swift-FridayPie Strange, but sometimes I can successfully toggle between installed/uninstalled states. – coderodde Jul 03 '23 at 08:31
  • Have you tried restarting? [According to my test, it is necessary to restart](https://i.stack.imgur.com/xlMk9.png). – YangXiaoPo-MSFT Jul 04 '23 at 03:28
  • @YangXiaoPo-MSFT Does it apply to installing, uninstalling or both? – coderodde Jul 04 '23 at 05:27
  • When installed, Windows requests to restart. May you try the [DiUninstallDevice](https://learn.microsoft.com/en-us/windows-hardware/drivers/install/how-devices-and-driver-packages-are-uninstalled#uninstalling-the-device) function? – YangXiaoPo-MSFT Jul 06 '23 at 01:30
  • @YangXiaoPo-MSFT It would seem like uninstallation works fine. Getting the device back online fails almost every time I run my program. – coderodde Jul 06 '23 at 05:24
  • @coderodde whitelist? Not arbitrary, though you can do a certain ritual dance and dusable every security feature in Windiws. Otherwise, you need a signature. Driver also has to be signed – Swift - Friday Pie Jul 15 '23 at 17:34
  • 1
    @coderodde I cannot reproduce. You may open an incident at [developer.microsoft.com/en-us/windows/support/?tabs=Contact-us](https://developer.microsoft.com/en-us/windows/support/?tabs=Contact-us) so that our engineer can work with you closely and if the support engineer determines that the issue is the result of a bug the service request will be a no-charge case and you won't be charged. – YangXiaoPo-MSFT Jul 18 '23 at 05:26
  • @YangXiaoPo-MSFT The service request will be a no-charge if the bug is in my code or somewhere down the Windows OS? – coderodde Jul 20 '23 at 09:13

0 Answers0