Search code examples
c++hash7zipcreateprocess

Issues with CreateProcess and incorrect parameters


This may be 2 questions, but they're broadly both revolving around CreateProcess and it not quite functioning right.

I've been working on an application that gathers together files, processes them, and then zips them as a final step, renaming the zipped file with the hash of the directory that I had zipped. To accomplish this, I'm using a standalone copy of 7zip (7za.exe), by using CreateProcess to create / zip the archive, and a separate program called DirHash to produce the name of the archive I'm trying to make.

The problem I'm having is that neither of these programs are working properly. I'm currently running DirHash with the flags -t "temp.txt" -nowait -quiet -overwrite", and it does create a file called temp.txt, however, the file is always empty when running it using CreateProcess. When I use the exact same parameters on the standard command line, it produces the correct output.

The other issue is that 7zip seems to be erroring when trying to zip my directories. When run through my CreateProcess, I get an "Unsupported Command" error, and the files don't get zipped. However, when I use the exact same parameters on the command line, the archive is created successfully.

Here's the relevant code for DirHash, the same code is used for 7za. I have confirmed that the values given to CreateProcess are correct, and the parameters are the same as the ones I use on the command line.

auto hashLocation = searchPath + "\\" + DIRHASH_NAME;
auto params = "\"" + targetDir + "\" MD5" + " -quiet -t \"temp.txt\" -nowait -overwrite";


// I know this part is gross and if there's any suggestions for how to do it better I'm willing to hear it.        
std::wstring intermediate;
intermediate.assign(params.begin(), params.end());
LPWSTR trueParams = &intermediate[0];

std::wstring intermediate_ex;
intermediate_ex.assign(hashLocation.begin(), hashLocation.end());
LPCWSTR trueLocation = intermediate_ex.c_str();

STARTUPINFO si;
PROCESS_INFORMATION pi;

ZeroMemory(&si, sizeof(STARTUPINFO));
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
si.cb = sizeof(STARTUPINFO);


// Ripped from stack overflow
bool success = CreateProcess(
    trueLocation,
    trueParams,
    NULL,
    NULL,
    TRUE,
    0,              // No creation flags
    NULL,           // Use parent's environment block
    NULL,           // Use parent's starting directory 
    &si,            // Pointer to STARTUPINFO structure
    &pi             // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
    );
    // Close process and thread handles. 
WaitForSingleObject(pi.hProcess, INFINITE);
if (!success)
{
    addLog("Failed to run DirHash process.", ErrorLevel::ERROR_MESSAGE);
    return ERROR_STR;
}

CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

Solution

  • When using both the lpApplicationName and lpCommandLine parameters of CreateProcess(), the lpCommandLine should include the EXE path as the 1st token in the command line. This is even stated in the CreateProcess() documentation:

    If both lpApplicationName and lpCommandLine are non-NULL, the null-terminated string pointed to by lpApplicationName specifies the module to execute, and the null-terminated string pointed to by lpCommandLine specifies the command line. The new process can use GetCommandLine to retrieve the entire command line. Console processes written in C can use the argc and argv arguments to parse the command line. Because argv[0] is the module name, C programmers generally repeat the module name as the first token in the command line.

    So, if you are going to include the EXE path in the command line, there is no need to use the lpApplicationName parameter at all:

    If lpApplicationName is NULL, the first white space–delimited token of the command line specifies the module name. If you are using a long file name that contains a space, use quoted strings to indicate where the file name ends and the arguments begin (see the explanation for the lpApplicationName parameter )...

    Also, rather than converting char strings to std::wstring (which you are not doing correctly) just to call CreateProcess() (which is being mapped to CreateProcessW() in this situation), you can use CreateProcessA() instead.

    Try this:

    std::string hashLocation = searchPath + "\\" + DIRHASH_NAME;
    std::string cmd = "\"" + hashLocation + "\" \"" + targetDir + "\" MD5 -quiet -t \"temp.txt\" -nowait -overwrite";
    
    STARTUPINFOA si = {};
    si.cb = sizeof(si);
    
    PROCESS_INFORMATION pi = {};
    
    bool success = CreateProcessA(
        NULL,
        &cmd[0], // or const_cast<char*>(cmd.c_str()), or params.data() in C++17...
        NULL,
        NULL,
        FALSE,
        0,              // No creation flags
        NULL,           // Use parent's environment block
        NULL,           // Use parent's starting directory 
        &si,            // Pointer to STARTUPINFO structure
        &pi             // Pointer to PROCESS_INFORMATION structure
    );
    if (!success)
    {
        addLog("Failed to run DirHash process.", ErrorLevel::ERROR_MESSAGE);
        return ERROR_STR;
    }
    
    // Close process and thread handles. 
    WaitForSingleObject(pi.hProcess, INFINITE);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    

    Or this:

    // change searchPath, DIRHASH_NAME, and targetDir to wide strings... 
    std::wstring hashLocation = searchPath + L"\\" + DIRHASH_NAME;
    std::wstring cmd = L"\"" + hashLocation + L"\" \"" + targetDir + L"\" MD5 -quiet -t \"temp.txt\" -nowait -overwrite";
    
    STARTUPINFOW si = {};
    si.cb = sizeof(si);
    
    PROCESS_INFORMATION pi = {};
    
    bool success = CreateProcessW(
        NULL,
        &cmd[0], // or params.data() in C++17...
        NULL,
        NULL,
        FALSE,
        0,              // No creation flags
        NULL,           // Use parent's environment block
        NULL,           // Use parent's starting directory 
        &si,            // Pointer to STARTUPINFO structure
        &pi             // Pointer to PROCESS_INFORMATION structure
    );
    if (!success)
    {
        addLog("Failed to run DirHash process.", ErrorLevel::ERROR_MESSAGE);
        return ERROR_STR;
    }
    
    // Close process and thread handles. 
    WaitForSingleObject(pi.hProcess, INFINITE);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);