Processes, Symlinks, & Windows 10

When starting external executables from C++ code, if we start them using SHELLEXECUTEINFO on Windows 10, we cannot maintain a process handle across symlinks like we can on Windows 8.

Starting the process using:

SHELLEXECUTEINFO execInfo;
execInfo.fMask = SEE_MASK_NOCLOSEPROCESS|SEE_MASK_NO_CONSOLE;
//set lpVerb, lpFile, lpParameters, lpDirectory as needed
if(!ShellExecuteEx(&execInfo)) {
    ShowError();
}
//use execInfo.hProcess

We should have a value for execInfo.hProcess for the executable the symlink points to as we do in Windows 8. On Windows 10, the executable will start but the process link is not established. Therefore, it will not be possible to read data back from or wait for the completion of the called executable.

We can get a process handle if we use mklink /H to create a hardlink to the executable instead of a symlink, or use other functions (such as CreateProcess) to start the executable.

Create Process Example:

/** \brief Executes a given system command
\details For more info:
http://www.codeproject.com/Tips/333559/CreateProcess-and-wait-for-result
and
http://stackoverflow.com/questions/13996502/how-to-save-executed-output-cmd-line-in-file-in-c

Alternatives to CreateProcess:
- system: for shell scripts, doesn't like exe's, unicode, const input:
system("dir");
- WinExec
- ShellExecute

\param command    Command to run
\param wait       Wait for completion of the command if true
\param file
\return Command ran successfully?
*/
bool ExecuteCommand(std::wstring command, bool wait, HANDLE* file) {
    verbose("Running: %S", command.c_str());
    LPWSTR c = (LPWSTR)safe_copy(command.c_str());
    bool successful = true;

    PROCESS_INFORMATION procInfo = {0};
    STARTUPINFO startupInfo = {0};
    startupInfo.cb = sizeof(startupInfo);
    startupInfo.dwFlags |= STARTF_USESTDHANDLES;
    startupInfo.hStdInput = NULL;
    startupInfo.hStdOutput = *file;
    startupInfo.hStdError = *file;
    if(!CreateProcessW(NULL, c, NULL, NULL, TRUE, NULL, NULL, NULL, &startupInfo, &procInfo)) {
        errmsg("Command Failed: %S", command.c_str());
        successful = false;

        /* Write Error Message:
        LPSTR pszAPI = "CreateProcess";
        LPVOID lpvMessageBuffer;
        FormatMessageA(
            FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
            NULL, GetLastError(),
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPSTR)&lpvMessageBuffer, 0, NULL);

        std::string ptrMessage = fmt("ERROR: API    = %d.\n   error code = %d.\n   message    = %d.\n", pszAPI, GetLastError(), (LPSTR)lpvMessageBuffer);
        errmsg("%s", ptrMessage.c_str());
        LocalFree(lpvMessageBuffer);*/
    }
    else if(wait) {
        WaitForSingleObject(procInfo.hProcess, INFINITE);
        DWORD exitCode;
        int r = GetExitCodeProcess(procInfo.hProcess, &exitCode);

        CloseHandle(procInfo.hProcess);
        CloseHandle(procInfo.hThread);

        if(!r) { //no return code
            successful = false;
        }
    }
    free(c);
    return successful;
}

Leave a Reply

Your email address will not be published. Required fields are marked *

*