I have written a Windows Service program in C++. One of the tasks it performs is to run a very large business app written in PowerBuilder. It performs a bunch of database processes and transfers data back and forth with SharePoint. The PowerBuilder app gets to a certain point where it aborts and the likely cause is because services have a limited 'desktop heap' compared to normal desktop apps.
As a test we tried using a generic service app called AlwaysUp to run the PowerBuilder app and somehow it has no problems at all. I am stumped as to why AlwaysUp has no issues. Here is the code I use to launch the PowerBuilder app:
// report running status
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(hStatus, &ServiceStatus);
// run the application
event = CreateEvent(0, FALSE, FALSE, 0);
if ( CreateProcess( NULL, CommandLine, NULL, NULL, FALSE,
DETACHED_PROCESS | dwPriority, NULL,
szModule, &si, &pi ) == 0 ) {
// CreateProcess Failed
return 0;
}
// wait for either the process to end or stop event
WaitHandles[0] = pi.hProcess;
WaitHandles[1] = event;
do {
dwError = WaitForMultipleObjects(2, WaitHandles, FALSE, 60000);
switch ( dwError ) {
case WAIT_OBJECT_0:
// process ended
bRunning = FALSE;
break;
case WAIT_OBJECT_0+1:
// event triggered
bRunning = FALSE;
break;
case WAIT_TIMEOUT:
// timeout
break;
default:
// error
bRunning = FALSE;
break;
}
} while ( bRunning );
// close the handles
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(event);
Related
Our C++ app launches a separate .exe ( which may start its own sub-processes) using CreateProcess as below.
BOOL started = ::CreateProcess(NULL, // application
p, // parameters
NULL, // process security
NULL, // thread security
TRUE, // inherit handles flag
0, // flags
NULL, // inherit environment
dirLP, // inherit directory
&startup, // STARTUPINFO
&procinfo); // PROCESS_INFORMATIO
In case we need cancel the "job" we use CreateToolhelp32Snapshot to iterate through the process list to find any child processes of the one we launched.
static BOOL TerminateProcessTree (HANDLE parentProcess,UINT exitCode)
{
BOOL result=TRUE;
HANDLE hProcessSnap = NULL;
PROCESSENTRY32 pe32 = {0};
// Take a snapshot of all processes in the system.
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE)
return (FALSE);
pe32.dwSize = sizeof(PROCESSENTRY32);
// Walk the snapshot of the processes
DWORD parentID=GetProcessId(parentProcess);
if(parentID==0){
PrintLastError("GetProcessId");
return FALSE;
}
if (Process32First(hProcessSnap, &pe32)) {
do{
if(pe32.th32ParentProcessID==parentID){
HANDLE hProcess = OpenProcess (PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION, FALSE, pe32.th32ProcessID);
if(hProcess!=NULL){
BOOL terminateChildren=TerminateProcessTree(hProcess,exitCode);
BOOL terminatedChild = TerminateProcess(hProcess, exitCode);
if (!terminatedChild){
PrintLastError("TerminateProcess");
}
CloseHandle(hProcess);
if(!terminatedChild || !terminateChildren){
result=FALSE;
break;
}
} else{
PrintLastError("OpenProcess");
}
}
}while (Process32Next(hProcessSnap, &pe32));
}
CloseHandle (hProcessSnap);
DWORD checkCode=0;
BOOL terminated;
if(GetExitCodeProcess(parentProcess,&checkCode) && checkCode==STILL_ACTIVE){
terminated=TerminateProcess(parentProcess,exitCode);
if (!terminated){
PrintLastError("TerminateProcess");
result= FALSE;
}
}
return result;
}
As noted, this works fine on Windows 7. Windows 10 fails with "Access Denied" on the first call "TerminateProcess". Clearly something has changed in the Windows security model when it comes to processes.
The robust way to deal with controlling a process tree of objects is to use Job objects as noted in the comment threads.
The one thing to keep in mind is that a process can only belong to a single job, and by default any EXE that the OS determines needs appcompat help is put into a job object automatically managed by the "Program Compatibility Assistant". This makes using a Job object to manage arbitrary 3rd party EXEs a little bit more complicated (i.e. AssignProcessToJobObject fails for these processes).
If you are using CreateProcess you can make use of the flag CREATE_BREAKAWAY_FROM_JOB. See this blog post. This works fine as long as the target EXE has the same rights as the calling object.
For a Standard User EXE to run an EXE that might need Administrator rights (i.e. they contain a manifest which marks it as requireAdministrator), you have to use ShellExecute or ShellExecuteEx as calling CreateProcess in this case will fail. If your target EXEs are all using the proper manifest elements then it won't be put into a PCA Job object.. You can use the trick of passing SEE_MASK_FLAG_NO_UI which will have the side-effect of avoiding the PCA job behavior. If you are launching arbitrary 3rd party EXEs, you should use ShellExecuteEx and not CreateProcess.
bool SpawnProcess( const WCHAR* szExePath, const WCHAR* szExeArgs )
{
if( !szExePath )
return false;
// NOTE: szExeArgs can be nullptr.
// Get working directory from executable path.
WCHAR szDirectory[MAX_PATH] = {0};
wcscpy_s( szDirectory, szExePath );
PathRemoveFileSpec( szDirectory );
// ShellExecute or ShellExecuteEx must be used instead of CreateProcess
// to permit the shell to display a UAC prompt asking for consent to
// elevate when the target executable's manifest specifies a run level
// of "requireAdministrator".
//
// You can only use CreateProcess if you know that the application you
// are spawning will be at the same run level as the current process.
// Otherwise, you will receive ERROR_ACCESS_DENIED if the elevation
// consent could not be obtained.
SHELLEXECUTEINFO info = {};
info.cbSize = sizeof( info );
info.lpVerb = L"open";
info.fMask = SEE_MASK_FLAG_NO_UI;
info.lpFile = szExePath;
info.lpParameters = szExeArgs;
info.lpDirectory = szDirectory;
info.nShow = SW_SHOW;
if( !ShellExecuteEx( &info ) )
return false;
return true;
}
Note that if you want to wait until this process exits, you can use:
bool SpawnProcessAndWait( const WCHAR *szExePath, const WCHAR *szExeArgs, DWORD *pdwExitCode )
{
if( !szExePath )
return false;
// NOTE: szExeArgs and pExitCode can be nullptr.
// Get working directory from executable path.
WCHAR szDirectory[MAX_PATH] = {0};
wcscpy_s( szDirectory, szExePath );
PathRemoveFileSpec( szDirectory );
// See SpawnProcess for information why ShellExecute or ShellExecuteEx
// must be used instead of CreateProcess.
SHELLEXECUTEINFO info = {};
info.cbSize = sizeof( info );
info.lpVerb = L"open";
info.fMask = SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS;
info.lpFile = szExePath;
info.lpParameters = szExeArgs;
info.lpDirectory = szDirectory;
info.nShow = SW_SHOW;
if( !ShellExecuteEx( &info ) )
return false;
// Wait for process to finish.
WaitForSingleObject( info.hProcess, INFINITE );
// Return exit code from process, if requested by caller.
if( pdwExitCode )
GetExitCodeProcess( info.hProcess, pdwExitCode );
CloseHandle( info.hProcess );
return true;
}
You haven't noted if your "master" application here is using administrator or Standard User rights. Ideally it is using Standard User rights per the User Account Control guidelines dating back to Windows Vista. If your app is running as Standard User, then it's likely that your code no longer works due to security improvements to keep non-Administrator malware from doing this kind of thing to other processes.
For general appcompat questions about Windows 10, be sure to read the Windows and Windows Server compatibility cookbook and look back at the Windows 8.0 and Windows 8.1 material if you are jumping directly from Windows 7 to Windows 10.
I am using vs2012 based application developed using C++.
If application is running and doing some processing and user triggers restart/shutdown or hibernate, then restart/shutdown should be suspended until application processing is done.
Once application processing is done, windows should resume to restart/shutdown.
I would highly appreciate if some references or example code to the same can be available
Thanks
I did something like this
bool shutdownSystemWin(EShutdownActions action)
{
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
// Get a token for this process.
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
return false;
// Get the LUID for the shutdown privilege.
LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME,
&tkp.Privileges[0].Luid);
tkp.PrivilegeCount = 1; // one privilege to set
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// Get the shutdown privilege for this process.
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0,
(PTOKEN_PRIVILEGES)NULL, 0);
if (GetLastError() != ERROR_SUCCESS)
return false;
// Shut down the system and force all applications to close.
UINT flags = EWX_FORCE;
switch (action)
{
case EShutdownActions::Shutdown:
flags |= EWX_SHUTDOWN;
break;
case EShutdownActions::PowerOff:
flags |= EWX_POWEROFF;
break;
case EShutdownActions::SuspendToRAM:
return SetSuspendState(FALSE, FALSE, FALSE);
case EShutdownActions::Hibernate:
return SetSuspendState(TRUE, FALSE, FALSE);
break;
case EShutdownActions::Reboot:
flags |= EWX_REBOOT;
break;
case EShutdownActions::LogOff:
flags |= EWX_LOGOFF;
break;
}
if (!ExitWindowsEx(flags,
SHTDN_REASON_MAJOR_OPERATINGSYSTEM |
SHTDN_REASON_MINOR_UPGRADE |
SHTDN_REASON_FLAG_PLANNED))
return false;
//shutdown was successful
return true;
}
Use Case
I have a 64 bit server process which through IPC (COM+ RPC) gains access to the PID of a 32 bit Client Process. The Server Process Creates a new Window with a parent Window Handler. I need to display the New Window inside the Client's Window instead of a standalone popup on top of the Desktop Window.
Process Adopted
In order to Display the New Window on the parent Window, I first tried to
Get the Handle to the Top Window
Duplicate the Window Handle using DuplicateHandle
Create the new Window with the new Duplicate Window Handle
Code
In Order to Duplicate the Window Handle I adopted the following Code. Note this is not the actual code, but for brevity changed the non relevant parts. Also Note, the SetPriviledge Function was adopted from Enabling and Disabling Privileges in C++
bool Duplicate(HWND hWnd)
{
HANDLE pToken = NULL;
HANDLE hProcess = NULL;
HANDLE hDuplicateHandle = NULL;
DWORD pid = 0;
bool bReturn = true;
GetWindowThreadProcessId(hWnd, &pid);
if(!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid)))
{
std::cout<<"Cannot Open Process:"<<GetLastError()<<std::endl;
bReturn = false;
}
if(bReturn && !OpenProcessToken(
hProcess,
TOKEN_ALL_ACCESS,
&pToken ))
{
std::cout<<"Cannot Open Token:"<<GetLastError()<<std::endl;
bReturn = false;
}
//The SetPriviledge function was adopted from
//http://msdn.microsoft.com/en-us/library/windows/desktop/aa446619(v=vs.85).aspx
if (bReturn && !SetPrivilege(
pToken,
SE_DEBUG_NAME,
true ))
{
std::cout<<"Error Setting Priveledge:error"<<GetLastError()<<std::endl;
bReturn = false;
}
if(bReturn && !DuplicateHandle(
hProcess,
hWnd,
GetCurrentProcess(),
&hDuplicateHandle,
NULL,
NULL,
DUPLICATE_SAME_ACCESS))
{
std::cout<<"Error Duplicating Handle: "<<GetLastError()<<std::endl;
std::cout<<"Source Handle is "<<hWnd<<" And the Duplicate Handle is "<<hDuplicateHandle<<std::endl;
bReturn = false;
}
if (hProcess)
{
CloseHandle(hProcess);
}
return bReturn;
}
O/P From the Above Code
Error Duplicating Handle: 6
Source Handle is 00150C1C And the Duplicate Handle is 00000000
Press any key to continue . . .
i.e. The Code fails with Error Code 6: ERROR_INVALID_HANDLE
Goal
To Make the above code work so that I can Duplicate a Remote Windows handle of a Local Process. Alternatively, determine if the above is the correct process.
I'm trying to start GUI application from windows service. But when I call CreateEnvironmentBlock() function, It hangs there for a while then crashes displaying dialog box "SampleService.exe stopped working and was closed. A problem caused the application to stop working correctly. windows will notify you if a solution is available." Following is my code.
DWORD dwSessionId = 0; // Session ID
HANDLE hToken = NULL; // Active session token
HANDLE hDupToken = NULL; // Duplicate session token
WCHAR szErr[1024] = {0};
STARTUPINFO* startupInfo;
PROCESS_INFORMATION processInformation;
PWTS_SESSION_INFO pSessionInfo = 0;
DWORD dwCount = 0;
LPVOID lpEnvironment = NULL; // Environtment block
OutputDebugString(_T("My Sample Service: startApplication: Entry"));
// Get the list of all terminal sessions
WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessionInfo, &dwCount);
int dataSize = sizeof(WTS_SESSION_INFO);
// look over obtained list in search of the active session
for (DWORD i = 0; i < dwCount; ++i)
{
WTS_SESSION_INFO si = pSessionInfo[i];
if (WTSActive == si.State)
{
// If the current session is active – store its ID
dwSessionId = si.SessionId;
break;
}
}
OutputDebugString(_T("My Sample Service: startApplication: freewtsmemory"));
WTSFreeMemory(pSessionInfo);
OutputDebugString(_T("My Sample Service: startApplication: WTSQueryUserToken"));
// Get token of the logged in user by the active session ID
BOOL bRet = WTSQueryUserToken(dwSessionId, &hToken);
if (!bRet)
{
swprintf(szErr, _T("WTSQueryUserToken Error: %d"), GetLastError());
OutputDebugString(szErr);
return false;
}
OutputDebugString(_T("My Sample Service: startApplication: duplicatetokenex"));
// Get duplicate token from the active logged in user's token
bRet = DuplicateTokenEx(hToken, // Active session token
TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, // Desired access
NULL, // Token attributes
SecurityImpersonation, // Impersonation level
TokenPrimary, // Token type
&hDupToken); // New/Duplicate token
if (!bRet)
{
swprintf(szErr, _T("DuplicateTokenEx Error: %d"), GetLastError());
OutputDebugString(szErr);
return false;
}
// Get all necessary environment variables of logged in user
// to pass them to the process
OutputDebugString(_T("My Sample Service: startApplication: createenvironmentblock"));
try{
bRet = CreateEnvironmentBlock(&lpEnvironment, hDupToken, FALSE);
}
catch( const exception &e)
{
swprintf(szErr, _T("CreateEnvironmentBlock Exception: %s"), e);
OutputDebugString(szErr);
return false;
}
if(!bRet)
{
swprintf(szErr, _T("CreateEnvironmentBlock Error: %d"), GetLastError());
OutputDebugString(szErr);
return false;
}
// Initialize Startup and Process info
startupInfo->cb = sizeof(STARTUPINFO);
OutputDebugString(_T("My Sample Service: startApplication: createprocess"));
// Start the process on behalf of the current user
BOOL returnCode = CreateProcessAsUser(hDupToken,
NULL,
L"C:\\KM\\TEST.exe",
NULL,
NULL,
FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
lpEnvironment,
NULL,
startupInfo,
&processInformation);
if( !returnCode)
{
swprintf(szErr, _T("CreateProcessAsUser Error: %d"), GetLastError());
OutputDebugString(szErr);
return false;
}
CloseHandle(hDupToken);
return true;
It shows "My Sample Service: startApplication: createenvironmentblock" in debugview and stopped service. please help me out regarding this issue. please note i m using windows vista.
Regards,
KM.
You need to initialise pointers before you can use them in a defined fashion.
STARTUPINFO* startupInfo;
...
startupInfo->cb = sizeof(STARTUPINFO);
This mistake might have been more obvious to spot if your variables were declared closer to where they are used. If you follow some rule that variables can only be declared at the start of a function, you might want to consider making more functions.
And, for what it's worth, when troubleshooting these sorts of issues you can always attach Visual Studio's debugger to the service process instead of relying on OutputDebugString. Just make sure the service process is the last thing built by Visual Studio and process, symbol files and source code should all be aligned.
ok...burned out on this one...scratching my head all day. I have a very simple, single purpose c++ DLL (StartApplication.dll) used to start an application.
Works fine in WinXP, but not in win7
Uses CreateProcess() with DEBUG_PROCESS (so i can wait for the program to finish before terminating).
If I monitor processes in Task Manager, I can see the process start, but no window is created and nothing further happens
If I change to NORMAL_PRIORITY_CLASS, the program will execute as it should (but i lose the ability to wait around before terminating as I can only do this while debugging)
Error code gives me STATUS_ACCESS_VIOLATION
I have UAC turned off and setting the executable to run as Administrator and with WinXP compatibility does nothing
Heres the code. Any thoughts would be greatly appreciated
//...blah blah...handful of stuff preceding this to set up command line and
//directories etc....not of use here probably...
SECURITY_ATTRIBUTES sa = {0}; sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
STARTUPINFO si = {0}; si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdOutput = (NULL == stdOutFileName)? INVALID_HANDLE_VALUE :
::CreateFile(stdOutFileName, GENERIC_WRITE, FILE_SHARE_READ, &sa
, CREATE_ALWAYS, 0, NULL);
si.hStdError = (NULL == stdErrFileName)? INVALID_HANDLE_VALUE :
::CreateFile(stdErrFileName, GENERIC_WRITE, FILE_SHARE_READ, &sa
, CREATE_ALWAYS, 0, NULL)
PROCESS_INFORMATION pi = {0};
if (::CreateProcess(useApplicationName? applicationName : NULL, processCommandLine
, NULL, NULL, TRUE, /*NORMAL_PRIORITY_CLASS*/DEBUG_PROCESS, NULL, currentDirectory, &si, &pi))
{
BOOL cont = TRUE;
while (cont)
{
DWORD continueStatus = DBG_CONTINUE;
DEBUG_EVENT debugEvent = {0};
if (!::WaitForDebugEvent(&debugEvent, INFINITE))
{
errorCode = ErrorCode_Other;
::TerminateProcess(pi.hProcess, 0);
break;
}
else
{
switch (debugEvent.dwDebugEventCode)
{
case EXCEPTION_DEBUG_EVENT:
switch (debugEvent.u.Exception.ExceptionRecord.ExceptionCode)
{
case EXCEPTION_ACCESS_VIOLATION:
case EXCEPTION_DATATYPE_MISALIGNMENT:
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
case EXCEPTION_FLT_DENORMAL_OPERAND:
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
case EXCEPTION_FLT_INEXACT_RESULT:
case EXCEPTION_FLT_INVALID_OPERATION:
case EXCEPTION_FLT_OVERFLOW:
case EXCEPTION_FLT_STACK_CHECK:
case EXCEPTION_FLT_UNDERFLOW:
case EXCEPTION_INT_DIVIDE_BY_ZERO:
case EXCEPTION_INT_OVERFLOW:
case EXCEPTION_PRIV_INSTRUCTION:
case EXCEPTION_IN_PAGE_ERROR:
case EXCEPTION_ILLEGAL_INSTRUCTION:
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
case EXCEPTION_STACK_OVERFLOW:
case EXCEPTION_INVALID_DISPOSITION:
case EXCEPTION_INVALID_HANDLE:
errorCode = ErrorCode_ApplicationException;
*exceptionCode = debugEvent.u.Exception.
ExceptionRecord.ExceptionCode;
::TerminateProcess(pi.hProcess, 0);
break;
default:
;
}
break;
case EXIT_PROCESS_DEBUG_EVENT:
cont = FALSE;
break;
default:
;
}
::ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId
, continueStatus);
}
}
::GetExitCodeProcess(pi.hProcess, reinterpret_cast<LPDWORD>(exitCode));
::CloseHandle(pi.hProcess);
::CloseHandle(pi.hThread);
}
if (INVALID_HANDLE_VALUE != si.hStdOutput)
::CloseHandle(si.hStdOutput);
if (INVALID_HANDLE_VALUE != si.hStdError)
::CloseHandle(si.hStdError);
return errorCode;
}
Your code terminates the whole process after the first exception occures.
Normally you should let the program take care of exceptions, only if the exception wasn't handled the process will terminate.
To see if an exception was fatal (ie. not handled the first time around) check u.Exception.dwFirstChance:
If it's 0, set your error codes accordingly and terminate.
otherwise, the exception occured for the first time, you should call ContinueDebugEvent with DBG_EXCEPTION_NOT_HANDLED to pass the exception to the process.
EDIT
If you only want to watch exceptions and not handle them from inside the debugger, you should always continue with DBG_EXCEPTION_NOT_HANDLED.
There's one catch:
Right before the main thread starts, Windows raises an INT3 exception which needs to be passed to the process (DBG_CONTINUE).
Pseudo code:
bool FirstInt3 = true;
while (cont)
{
DWORD continueStatus = DBG_EXCEPTION_NOT_HANDLED;
// ....
switch(...)
{
case EXCEPTION_DEBUG_EVENT:
if(!FirstChance)
{
// Fatal exception
// Log Exception that terminated the program here
// Don't do anything else, Windows automatically terminates the process
// You will get an EXIT_PROCESS_DEBUG_EVENT on the next event
}
switch (debugEvent.u.Exception.ExceptionRecord.ExceptionCode)
{
case EXCEPTION_BREAKPOINT:
if(FirstInt3)
{
FirstInt3 = false;
continueStatus = DBG_CONTINUE;
break;
}
default:
// Log ExceptionCode here
break;
}
break;
}
If I've understand you correctly, you're using DEBUG_PROCESS just so that you can wait until the process exits. That's major overkill.
To wait until the process has exited, use WaitForSingleObject (or another wait function as appropriate) on pi.hProcess.
#pezcode is right - don't just end the process on receiving first exception from the debuggee, let it run normally. Debugger (your code) will anyway receive all exceptions occurring in the debuggee (first chance exceptions).
I would suggest you to first minimize the debugger-loop logic, and also try not to attach and handles to process. Keep it simple first, then move ahead.