I have manages to use ShellExecute in VC++ in order to launch a document. Now I wish to run a command-line tool that receives some arguments, and to run in the background (a
There is a CodeProject article that shows how, by using ShellExecuteEx
instead of ShellExecute
:
SHELLEXECUTEINFO ShExecInfo = {0};
ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
ShExecInfo.hwnd = NULL;
ShExecInfo.lpVerb = NULL;
ShExecInfo.lpFile = "c:\\MyProgram.exe";
ShExecInfo.lpParameters = "";
ShExecInfo.lpDirectory = NULL;
ShExecInfo.nShow = SW_SHOW;
ShExecInfo.hInstApp = NULL;
ShellExecuteEx(&ShExecInfo);
WaitForSingleObject(ShExecInfo.hProcess, INFINITE);
CloseHandle(ShExecInfo.hProcess);
The crucial point is the flag SEE_MASK_NOCLOSEPROCESS
, which, as MSDN says
Use to indicate that the hProcess member receives the process handle. This handle is typically used to allow an application to find out when a process created with
ShellExecuteEx
terminates
Also, note that:
The calling application is responsible for closing the handle when it is no longer needed.
Using ShellExecuteEx
sometimes doesn't work if COM is used, so the following remarks must be considered.
Because ShellExecuteEx can delegate execution to Shell extensions (data sources, context menu handlers, verb implementations) that are activated using Component Object Model (COM), COM should be initialized before ShellExecuteEx is called. Some Shell extensions require the COM single-threaded apartment (STA) type. In that case, COM should be initialized as shown here:
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE)
There are instances where ShellExecuteEx does not use one of these types of Shell extension and those instances would not require COM to be initialized at all. Nonetheless, it is good practice to always initalize COM before using this function.
More from MSDN here https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecuteexa
You can also use CreateProcess instead of ShellExecute/ShellExecuteEx. This function includes a cmd.exe wrapper option, returning the exit code, and returning stdout. (The includes may not be perfect).
Notes: In my use, I knew that there had to be stdout results, but the PeekedNamePipe function wouldn't always return the bytes count on the first try, hence the loop there. Perhaps, someone can figure this out and post a revision? Also, maybe an alternate version should be produced which returns stderr separately?
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <Shellapi.h>
/*
Note:
The exitCode for a "Cmd Process" is not the exitCode
for a sub process launched from it! That can be retrieved
via the errorlevel variable in the command line like so:
set errorlevel=&[launch command]&echo.&echo exitCode=%errorlevel%&echo.
The stdOut vector will then contain the exitCode on a seperate line
*/
BOOL executeCommandLine( const CStringW &command,
DWORD &exitCode,
const BOOL asCmdProcess=FALSE,
std::vector<CStringW> *stdOutLines=NULL )
{
// Init return values
BOOL bSuccess = FALSE;
exitCode = 0;
if( stdOutLines ) stdOutLines->clear();
// Optionally prepend cmd.exe to command line to execute
CStringW cmdLine( (asCmdProcess ? L"cmd.exe /C " : L"" ) +
command );
// Create a pipe for the redirection of the STDOUT
// of a child process.
HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
bSuccess = CreatePipe( &g_hChildStd_OUT_Rd,
&g_hChildStd_OUT_Wr, &saAttr, 0);
if( !bSuccess ) return bSuccess;
bSuccess = SetHandleInformation( g_hChildStd_OUT_Rd,
HANDLE_FLAG_INHERIT, 0 );
if( !bSuccess ) return bSuccess;
// Setup the child process to use the STDOUT redirection
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );
ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = g_hChildStd_OUT_Wr;
siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// Execute a synchronous child process & get exit code
bSuccess = CreateProcess( NULL,
cmdLine.GetBuffer(), // command line
NULL, // process security attributes
NULL, // primary thread security attributes
TRUE, // handles are inherited
0, // creation flags
NULL, // use parent's environment
NULL, // use parent's current directory
&siStartInfo, // STARTUPINFO pointer
&piProcInfo ); // receives PROCESS_INFORMATION
if( !bSuccess ) return bSuccess;
WaitForSingleObject( piProcInfo.hProcess, (DWORD)(-1L) );
GetExitCodeProcess( piProcInfo.hProcess, &exitCode );
CloseHandle( piProcInfo.hProcess );
CloseHandle( piProcInfo.hThread );
// Return if the caller is not requesting the stdout results
if( !stdOutLines ) return TRUE;
// Read the data written to the pipe
DWORD bytesInPipe = 0;
while( bytesInPipe==0 ){
bSuccess = PeekNamedPipe( g_hChildStd_OUT_Rd, NULL, 0, NULL,
&bytesInPipe, NULL );
if( !bSuccess ) return bSuccess;
}
if( bytesInPipe == 0 ) return TRUE;
DWORD dwRead;
CHAR *pipeContents = new CHAR[ bytesInPipe ];
bSuccess = ReadFile( g_hChildStd_OUT_Rd, pipeContents,
bytesInPipe, &dwRead, NULL);
if( !bSuccess || dwRead == 0 ) return FALSE;
// Split the data into lines and add them to the return vector
std::stringstream stream( pipeContents );
std::string str;
while( getline( stream, str ) )
stdOutLines->push_back( CStringW( str.c_str() ) );
return TRUE;
}