Search code examples
windowsvisual-studiovisual-studio-2017remote-debugging

Visual Studio C++ remote debugging - running CMD as admin on remote machine


I'm using Visual Studio 2017 to debug C++ code on a VM running Windows via VS Remote Debugging. As part of my code, I'm executing commands via std::system, which must run as administrator to be successful.

When running VS as administrator, and debugging the code locally -- all works fine.

But, when debugging remotely on a VM, the commands aren't executed as administrator. I know that for a fact, because some of the commands require that, and the output explicitly states that it's not the case. Is there a way to make it work?

I don't mind using a different API, std::system is just seems as the "default" for command execution.


Solution

  • Thanks to Barrnet's answer, I've looked into ShellExecuteEx and its "runas" verb.

    Here's a full solution, for running a cmd as admin and retrieving the cmd output as a string: (UAC has to be set to minimum, in order to avoid a pop-up)

    #include <Windows.h>
    #include <vector>
    #include <string>
    #include <fstream>
    
    inline void Validate (const bool expression) 
    {
        if (!expression) 
            throw std::exception(); 
    }
    
    class TemporaryFile
    {
    public:
        TemporaryFile()
        {
            std::vector<char> tmpPath(MAX_PATH + 1, 0);
            Validate(0 != GetTempPathA(MAX_PATH, tmpPath.data()));
            std::vector<char> tmpFilename(MAX_PATH + 1, 0);
            Validate(0 != GetTempFileNameA(tmpPath.data(), "tmp", 0, tmpFilename.data()));
            _fullPath = std::string(tmpFilename.data());
        }
    
        ~TemporaryFile()
        {
            DeleteFileA(_fullPath.c_str());
        }
    
        std::string GetPath()
        {
            return _fullPath;
        }
        
    private:
        std::string _fullPath{};
    };
    
    std::string ReadFileContents(const std::string& filename)
    {
        std::ifstream ifs(filename);
        return std::string((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
    }
    
    std::string RunCmdAsAdmin(const std::string& cmd)
    {
        // file to hold cmd output
        TemporaryFile output;
    
        // the cmd will be passed to cmd.exe via '/c' flag, output will be written to tempfile
        const std::string modifiedCmd = "/c " + cmd + " > \"" + output.GetPath() + "\"";
    
        // create and launch cmd (async operation -- a seperate process is launched)
        SHELLEXECUTEINFO shellExecInfo{};
        shellExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
        shellExecInfo.fMask = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS;
        shellExecInfo.lpVerb = "runas";
        shellExecInfo.lpFile = "cmd.exe";
        shellExecInfo.lpParameters = modifiedCmd.c_str();
        shellExecInfo.nShow = SW_SHOWNORMAL;
        Validate(TRUE == ShellExecuteEx(&shellExecInfo));
    
        // wait for cmd to finish running and retrieve output
        static const DWORD MAX_WAIT_MS{ 5000 };
        Validate(WAIT_OBJECT_0 == WaitForSingleObject(shellExecInfo.hProcess, MAX_WAIT_MS));
        CloseHandle(shellExecInfo.hProcess);
        return ReadFileContents(output.GetPath());
    }
    
    int main()
    {
        const auto& cmdOutput = RunCmdAsAdmin("ipconfig"); 
    }