I am writing a service based on the tutorial at the following page:
https://www.codeproject.com/Articles/499465/Simple-Windows-Service-in-Cplusplus
I can successfully create the service using:
sc create service_name binPath=<path_name>
When I attempt to start the service, I get the following error:
sc start service_name
[SC] StartService FAILED 1053:
The service did not respond to the start or control request in a timely fashion.
Here is my main():
int main (int argc, char *argv[])
{
OutputDebugString("service_name: entered main");
SERVICE_TABLE_ENTRY ServiceTable[] =
{
{SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain},
{NULL, NULL}
};
if (StartServiceCtrlDispatcher (ServiceTable) == FALSE)
{
return GetLastError();
}
return 0;
}
EDIT: I switched from log files to OutputDebugString()/DebugView
I ran DebugView, and I never receive "entered main". However, if I replace the contents of my worker thread with a return statement, I do start up successfully, and I receive the debug message, so I know that DebugView is working correctly.
Here is where I create my worker thread:
// Start a thread that will perform the main task of the service
HANDLE hThread = CreateThread (NULL, 0, ServiceWorkerThread, NULL, 0, NULL);
if (hThread) {
// Wait until our worker thread exits, so we can set state to SERVICE_STOPPED and return needs to stop
WaitForSingleObject (hThread, INFINITE);
} else {
OutputDebugString("service_name: ServiceMain: CreateThread returned NULL");
}
It seems like my worker thread is responsible for the startup error, but why wouldn't I get debug messages at the top of main()?
If your service was created successfully, you should see it in the list of Windows Services ( Computer Management -> Services and Applications->Services). If it is not present, then it was not registered properly. If it is present, you could try to start it from Windows Service console. Your's _tmain is just not called by the service, no log will be produced.
You need to update the SCM states using SetServiceStatus function.
If initializing your service takes a long time, you should update the state periodically with the SERVICE_START_PENDING state.
When the initialization is finished, you must update the SCM state to SERVICE_RUNNING.
Other states are:
-SERVICE_STOP_PENDING.
-SERVICE_PAUSE_PENDING.
-SERVICE_CONTINUE_PENDING.
-SERVICE_STOPPED.
My function that sends the state to the SCM is as follows:
BOOL SendStatusToSCM
(
DWORD dwCurrentState,
DWORD dwWin32ExitCode,
DWORD dwServiceSpecificExitCode,
DWORD dwCheckPoint,
DWORD dwWaitHint
)
{
BOOL success;
SERVICE_STATUS serviceStatus;
// Preenche os campos do service status
serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
serviceStatus.dwCurrentState = dwCurrentState;
if (dwCurrentState == SERVICE_START_PENDING)
serviceStatus.dwControlsAccepted = 0;
else
serviceStatus.dwControlsAccepted =
SERVICE_ACCEPT_STOP |
SERVICE_ACCEPT_PAUSE_CONTINUE |
SERVICE_ACCEPT_SHUTDOWN;
if (dwServiceSpecificExitCode == 0)
serviceStatus.dwWin32ExitCode =
dwWin32ExitCode;
else
serviceStatus.dwWin32ExitCode =
ERROR_SERVICE_SPECIFIC_ERROR;
serviceStatus.dwServiceSpecificExitCode =
dwServiceSpecificExitCode;
serviceStatus.dwCheckPoint = dwCheckPoint;
serviceStatus.dwWaitHint = dwWaitHint;
// Passa o status para o SCM
success = SetServiceStatus
(
serviceStatusHandle,
&serviceStatus
);
if (!success)
exit( 99 );
return success;
}
With this create below the start service works perfectly and always activates the main():
serv=CreateService (
sc,
noServ, // service name
noDisp, // display name
SERVICE_ALL_ACCESS, // desired access
SERVICE_WIN32_OWN_PROCESS, // service type
SERVICE_AUTO_START, // modo de iniciar o serviço
SERVICE_ERROR_NORMAL, // gravidade da falha do serviço
noExec, // nome do executável
NULL, // nome do grupo ao qual pertence
NULL, // tag id
NULL, // tabela de dependências
NULL, // account name
NULL // account password
);
Related
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);
I've tried mixing the code here and here to run an GUI exe from a service that was initialized through QtService, but whenever I run the code bellow I get an error 5 from the CreateProcessAsUser.
Also, I saw the answer to a similar question here on StackOverflow but couldn't figure out how the DACL is related with the problem, and can't use the answer by Harry Johnson because I wouldn't have the logon info from the users.
So, can someone help me understand why am I receiving the error 5 (Acess Denied) from the code below?
if(initUiWin())
log->write("InitUiWin executed.");
else {
QString errorNumber = QString::number(GetLastError());
log->write("InitUiWin error: " + errorNumber);
}
-
bool initUiWin()
{
// obtain the currently active session id; every logged on
// User in the system has a unique session id
uint dwSessionId = WTSGetActiveConsoleSessionId();
// obtain the process id of the winlogon process that
// is running within the currently active session
QString processName("winlogon.exe");
DWORD winlogonPID = FindProcessId(processName.toStdWString(),dwSessionId);
if( winlogonPID != 0 ) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, winlogonPID);
HANDLE hToken;
OpenProcessToken(hProcess,TOKEN_READ,&hToken);
// Security attibute structure used in DuplicateTokenEx and CreateProcessAsUser
SECURITY_ATTRIBUTES sa;
sa.nLength = static_cast<DWORD>(sizeof(SECURITY_ATTRIBUTES));
// copy the access token of the winlogon process;
// the newly created token will be a primary token
HANDLE hUserTokenDup;
if (!DuplicateTokenEx(hToken,MAXIMUM_ALLOWED,&sa,
SecurityIdentification,TokenPrimary,&hUserTokenDup)) {
CloseHandle(hProcess);
CloseHandle(hToken);
return false;
}
// Get Handle to the interactive window station
HWINSTA hwinsta = NULL;
hwinsta = OpenWindowStation(
_T(L"winsta0"), // the interactive window station
FALSE, // handle is not inheritable
READ_CONTROL | WRITE_DAC); // rights to read/write the DACL
if(hwinsta == NULL)
return false;
// To get the correct default desktop, set the caller's
// window station to the interactive window station.
if (!SetProcessWindowStation(hwinsta))
return false;
// Get a handle to the interactive desktop.
HDESK hdesk = NULL;
hdesk = OpenDesktop(
_T(L"default"), // the interactive window station
0, // no interaction with other desktop processes
FALSE, // handle is not inheritable
READ_CONTROL | // request the rights to read and write the DACL
WRITE_DAC |
DESKTOP_WRITEOBJECTS |
DESKTOP_READOBJECTS);
if(hdesk == NULL)
return false;
// Get the SID for the client's logon session.
PSID pSid = NULL;
if (!GetLogonSID(hUserTokenDup, &pSid))
return false;
// Allow logon SID full access to interactive window station.
if (!AddAceToWindowStation(hwinsta, pSid) )
return false;
// Allow logon SID full access to interactive desktop.
if (!AddAceToDesktop(hdesk, pSid) )
return false;
// Impersonate client to ensure access to executable file.
if (!ImpersonateLoggedOnUser(hUserTokenDup) )
return false;
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = static_cast<DWORD>(sizeof(STARTUPINFO));
// interactive window station parameter; basically this indicates
// that the process created can display a GUI on the desktop
wchar_t auxBuffer[16] = L"winsta0\\default";
si.lpDesktop = auxBuffer;
// flags that specify the priority and creation method of the process
int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_BREAKAWAY_FROM_JOB;
// create a new process in the current User's logon session
bool result = CreateProcessAsUser(hUserTokenDup, // client's access token
L"test-ui-systray.exe", // file to execute
NULL, // command line
&sa, // pointer to process SECURITY_ATTRIBUTES
&sa, // pointer to thread SECURITY_ATTRIBUTES
false, // handles are not inheritable
dwCreationFlags, // creation flags
NULL, // pointer to new environment block
NULL, // name of current directory
&si, // pointer to STARTUPINFO structure
&pi // receives information about new process
);
if (pSid)
FreeLogonSID(&pSid);
if (hdesk)
CloseDesktop(hdesk);
if (hwinsta)
CloseWindowStation(hwinsta);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return result;
}
return false;
}
Putting it here for visibility. HarryJohnston answered in the comments. The problem was the flags in the OpenProcessToken, I just changed
OpenProcessToken(hProcess,TOKEN_READ,&hToken)
to
OpenProcessToken(hProcess,TOKEN_READ|TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY,&hToken)
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.
Suppose there are multiples users currently logged on on Windows. Say, user1 logs on, then switch user and user2 logs on, (without making user1 log off). Suppose there is an app which runs when user logs on. There are two users user1 and user2 logged on, with user2 as the active user, and there are two apps.
My question is: How does the app know whether its corresponding user is active or not? I.e., app in user2 domain determines that its user is active, while app in user1 domain determines its user is currently inactive. Thanks!
You can call WTSGetActiveConsoleSessionId to get the terminal services (aka "fast user switching" aka "remote desktop") session ID that is currently active on the physical console.
You can call WTSQuerySessionInformation with WTS_CURRENT_SESSION for the session identifier and WTSSessionId for WTSInfoClass to get the terminal services session ID for the current process.
If the active session ID and the current process session ID are the same, the user corresponding to the current process has the active session on the physical console.
If what you want to know is whether the session that the current process is running in is active (but not necessarily on the physical console) you can instead use the WTSConnectState option to WTSQuerySessionInformation.
WTSGetActiveConsoleSessionId() may actually return session 0 when run from a windows service. If you need to do this from a Windows service you will need to enumerate all sessions and find the connected session then get the user from that.
The code below does much more than that, including impersonation of that user and running a process as that user all from a windows service, but if you are just interested in the user name please look for the second instance the WTSQuerySessionInformation() function is called.
//Function to run a process as active user from windows service
void ImpersonateActiveUserAndRun(WCHAR* path, WCHAR* args)
{
DWORD session_id = -1;
DWORD session_count = 0;
WTS_SESSION_INFOA *pSession = NULL;
if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSession, &session_count))
{
//log success
}
else
{
//log error
return;
}
for (int i = 0; i < session_count; i++)
{
session_id = pSession[i].SessionId;
WTS_CONNECTSTATE_CLASS wts_connect_state = WTSDisconnected;
WTS_CONNECTSTATE_CLASS* ptr_wts_connect_state = NULL;
DWORD bytes_returned = 0;
if (::WTSQuerySessionInformation(
WTS_CURRENT_SERVER_HANDLE,
session_id,
WTSConnectState,
reinterpret_cast<LPTSTR*>(&ptr_wts_connect_state),
&bytes_returned))
{
wts_connect_state = *ptr_wts_connect_state;
::WTSFreeMemory(ptr_wts_connect_state);
if (wts_connect_state != WTSActive) continue;
}
else
{
//log error
continue;
}
HANDLE hImpersonationToken;
if (!WTSQueryUserToken(session_id, &hImpersonationToken))
{
//log error
continue;
}
//Get real token from impersonation token
DWORD neededSize1 = 0;
HANDLE *realToken = new HANDLE;
if (GetTokenInformation(hImpersonationToken, (::TOKEN_INFORMATION_CLASS) TokenLinkedToken, realToken, sizeof(HANDLE), &neededSize1))
{
CloseHandle(hImpersonationToken);
hImpersonationToken = *realToken;
}
else
{
//log error
continue;
}
HANDLE hUserToken;
if (!DuplicateTokenEx(hImpersonationToken,
//0,
//MAXIMUM_ALLOWED,
TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS | MAXIMUM_ALLOWED,
NULL,
SecurityImpersonation,
TokenPrimary,
&hUserToken))
{
//log error
continue;
}
// Get user name of this process
//LPTSTR pUserName = NULL;
WCHAR* pUserName;
DWORD user_name_len = 0;
if (WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session_id, WTSUserName, &pUserName, &user_name_len))
{
//log username contained in pUserName WCHAR string
}
//Free memory
if (pUserName) WTSFreeMemory(pUserName);
ImpersonateLoggedOnUser(hUserToken);
STARTUPINFOW StartupInfo;
GetStartupInfoW(&StartupInfo);
StartupInfo.cb = sizeof(STARTUPINFOW);
//StartupInfo.lpDesktop = "winsta0\\default";
PROCESS_INFORMATION processInfo;
SECURITY_ATTRIBUTES Security1;
Security1.nLength = sizeof SECURITY_ATTRIBUTES;
SECURITY_ATTRIBUTES Security2;
Security2.nLength = sizeof SECURITY_ATTRIBUTES;
void* lpEnvironment = NULL;
// Get all necessary environment variables of logged in user
// to pass them to the new process
BOOL resultEnv = CreateEnvironmentBlock(&lpEnvironment, hUserToken, FALSE);
if (!resultEnv)
{
//log error
continue;
}
WCHAR PP[1024]; //path and parameters
ZeroMemory(PP, 1024 * sizeof WCHAR);
wcscpy(PP, path);
wcscat(PP, L" ");
wcscat(PP, args);
// Start the process on behalf of the current user
BOOL result = CreateProcessAsUserW(hUserToken,
NULL,
PP,
//&Security1,
//&Security2,
NULL,
NULL,
FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE,
//lpEnvironment,
NULL,
//"C:\\ProgramData\\some_dir",
NULL,
&StartupInfo,
&processInfo);
if (!result)
{
//log error
}
else
{
//log success
}
DestroyEnvironmentBlock(lpEnvironment);
CloseHandle(hImpersonationToken);
CloseHandle(hUserToken);
CloseHandle(realToken);
RevertToSelf();
}
WTSFreeMemory(pSession);
}
I am having trouble starting my service on my pc. My code is based on this article http://www.gamedev.net/reference/articles/article1899.asp
When i call installService from my int main(int argc, char *argv[]), it is registered successfully (i can see it in msconfig and services.msc). However it has not started. I manually start the service via services.msv and i get the error "Error 2: system cannot find the file specified". Why is this? i registered the services no more then a min ago, my external HD is still on (where this is currently stored. i'll move a nondev version to c:/ when its ready) What am i doing wrong and is there another tutorial i can look at (i only found the one linked via google)
#define srvName "MyTestService_01312009"
void installService(char*path)
{
SC_HANDLE handle = ::OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
SC_HANDLE service = ::CreateService(
handle,
srvName,
"MyTestService_01312009b",
GENERIC_READ | GENERIC_EXECUTE,
SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START,
SERVICE_ERROR_IGNORE,
path,
NULL,
NULL,
NULL,
NULL,
NULL
);
}
void uninstallService()
{
SC_HANDLE handle = ::OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );//?
SC_HANDLE service = ::OpenService( handle, srvName, DELETE );
if( service != NULL )
{
// remove the service!
::DeleteService( service );
}
}
SERVICE_STATUS_HANDLE hStatus;
SERVICE_STATUS status;
/*
if( ::StartServiceCtrlDispatcher( dispatchTable ) == 0 )
{
// if this fails, it's probably because someone started us from
// the command line. Print a message telling them the "usage"
}
*/
void WINAPI ServiceCtrlHandler( DWORD control )
{
switch( control )
{
case SERVICE_CONTROL_SHUTDOWN:
case SERVICE_CONTROL_STOP:
// do shutdown stuff here
status.dwCurrentState = SERVICE_STOPPED;
status.dwWin32ExitCode = 0;
status.dwCheckPoint = 0;
status.dwWaitHint = 0;
break;
case SERVICE_CONTROL_INTERROGATE:
// just set the current state to whatever it is...
break;
}
::SetServiceStatus( hStatus, &status );
}
void WINAPI ServiceDispatch( DWORD numArgs, char **args )
{
// we have to initialize the service-specific stuff
memset( &status, 0, sizeof(SERVICE_STATUS) );
status.dwServiceType = SERVICE_WIN32;
status.dwCurrentState = SERVICE_START_PENDING;
status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
hStatus = ::RegisterServiceCtrlHandler( srvName, &ServiceCtrlHandler );
// more initialization stuff here
FILE *f = fopen("c:/testSrv.bin", "wb");
::SetServiceStatus( hStatus, &status );
}
SERVICE_TABLE_ENTRY dispatchTable[] =
{
{ srvName, &ServiceDispatch },
{ NULL, NULL }
};
Maybe you can use Process Monitor to find out what's wrong.
Start it, and look for NAME NOT FOUND results that occur in connection with the service start.