1// NTService.cpp
2//
3// Implementation of CNTService
4
5// Visual C++ Definitions
6#include "stdafx.h"
7#include "winreg.h"
8#include "winsvc.h"
9
10// Standard C Definitions
11#include "stdio.h"
12
13// Application-specific Definitions
14#include "NTService.h"
15
16CNTService* CNTService::m_pThis = NULL;
17
18
19CNTService::CNTService(LPCTSTR pServiceName, LPCSTR pDisplayName)
20{
21	OSVERSIONINFO osInfo;
22
23    // copy the address of the current object so we can access it from
24    // the static member callback functions.
25    // WARNING: This limits the application to only one CNTService object.
26    m_pThis = this;
27
28    // Set the default service name and version
29	memset(m_szServiceName, 0, sizeof(m_szServiceName));
30	memset(m_szDisplayName, 0, sizeof(m_szDisplayName));
31    strncpy(m_szServiceName, pServiceName, sizeof(m_szServiceName) - 1);
32    strncpy(m_szDisplayName, pDisplayName, sizeof(m_szDisplayName) - 1);
33    m_hEventSource = NULL;
34
35    // set up the initial service status
36    m_hServiceStatus = NULL;
37    m_Status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
38    m_Status.dwCurrentState = SERVICE_STOPPED;
39    m_Status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
40    m_Status.dwWin32ExitCode = 0;
41    m_Status.dwServiceSpecificExitCode = 0;
42    m_Status.dwCheckPoint = 0;
43    m_Status.dwWaitHint = 0;
44    m_bIsRunning = FALSE;
45
46	// Determine if we're running on a Windows 9x platform, including Windows Me.
47	// If so, we won't be able to support Windows NT services.
48	osInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
49	if (GetVersionEx(&osInfo))
50		m_bIsWindows9x = (osInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS);
51}
52
53CNTService::~CNTService()
54{
55    DebugMsg("CNTService::~CNTService()");
56
57	if (!m_bIsWindows9x)
58		if (m_hEventSource)
59			DeregisterEventSource(m_hEventSource);
60}
61
62////////////////////////////////////////////////////////////////////////////////////////
63// Default command line argument parsing
64
65// Returns TRUE if it found an arg it recognised, FALSE if not
66// Note: processing some arguments causes output to stdout to be generated.
67BOOL CNTService::ParseStandardArgs(int argc, char* argv[])
68{
69	if (argc <= 1)
70		return FALSE;
71
72	if (m_bIsWindows9x)
73		return TRUE;
74
75	if (_stricmp(argv[1], "-version") == 0)
76	{
77		// Report version information
78		printf("%s 1.2.5\n", m_szServiceName);
79		printf("The service is %s installed.\n", IsInstalled() ? "currently" : "not");
80		return TRUE;
81	}
82	else if (_stricmp(argv[1], "-install") == 0)
83	{
84        // Install the service
85		if (IsInstalled())
86			printf("%s is already installed.\n", m_szServiceName);
87		else if (Install())
88			printf("%s installed.\n", m_szServiceName);
89		else
90			printf("%s failed to install. Error %d\n", m_szServiceName, GetLastError());
91
92		return TRUE;
93	}
94	else if (_stricmp(argv[1], "-remove") == 0)
95	{
96		// Uninstall the service
97		if (!IsInstalled())
98			printf("%s is not installed\n", m_szServiceName);
99		else if (Uninstall())
100		{
101			// Get the executable file path
102			char szFilePath[_MAX_PATH];
103			::GetModuleFileName(NULL, szFilePath, sizeof(szFilePath));
104			printf("%s removed. (You must delete the file (%s) yourself.)\n", m_szServiceName, szFilePath);
105		}
106		else printf("Could not remove %s. Error %d\n", m_szServiceName, GetLastError());
107
108		return TRUE;
109	}
110
111	// Return FALSE here because the arguments were invalid
112    return FALSE;
113}
114
115////////////////////////////////////////////////////////////////////////////////////////
116// Install/uninstall routines
117
118// Test if the service is currently installed
119BOOL CNTService::IsInstalled()
120{
121	BOOL bResult = FALSE;
122
123	if (!m_bIsWindows9x)
124	{
125		// Open the Service Control Manager
126		SC_HANDLE hSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
127		if (hSCM)
128		{
129			// Try to open the service
130			SC_HANDLE hService = ::OpenService(hSCM, m_szServiceName, SERVICE_QUERY_CONFIG);
131			if (hService)
132			{
133				bResult = TRUE;
134				::CloseServiceHandle(hService);
135			}
136
137			::CloseServiceHandle(hSCM);
138		}
139	}
140
141	return bResult;
142}
143
144BOOL CNTService::Install()
145{
146    HKEY hKey = NULL;
147	DWORD dwData;
148	char szFilePath[_MAX_PATH], szKey[256];
149
150	// Don't attempt to install on Windows 9x.
151	if (m_bIsWindows9x)
152		return TRUE;
153
154	// Open the Service Control Manager
155	SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
156	if (!hSCM)
157		return FALSE;
158
159	// Get the executable file path
160	GetModuleFileName(NULL, szFilePath, sizeof(szFilePath));
161
162	// Create the service
163	SC_HANDLE hService = CreateService(hSCM, m_szServiceName, m_szDisplayName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
164									   SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, szFilePath, NULL, NULL, NULL, NULL, NULL);
165	if (!hService)
166	{
167		CloseServiceHandle(hSCM);
168		return FALSE;
169	}
170
171	// make registry entries to support logging messages
172	// Add the source name as a subkey under the Application
173	// key in the EventLog service portion of the registry.
174	strcpy(szKey, "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\");
175	strcat(szKey, m_szServiceName);
176	if (RegCreateKey(HKEY_LOCAL_MACHINE, szKey, &hKey) != ERROR_SUCCESS)
177	{
178		CloseServiceHandle(hService);
179		CloseServiceHandle(hSCM);
180		return FALSE;
181	}
182
183	// Add the Event ID message-file name to the 'EventMessageFile' subkey.
184	RegSetValueEx(hKey, "EventMessageFile", 0, REG_EXPAND_SZ, (CONST BYTE *) szFilePath, strlen(szFilePath) + 1);
185
186	// Set the supported types flags.
187	dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE;
188	RegSetValueEx(hKey, "TypesSupported", 0, REG_DWORD, (CONST BYTE *) &dwData, sizeof(DWORD));
189	RegCloseKey(hKey);
190
191	LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_INSTALLED, m_szServiceName);
192	CloseServiceHandle(hService);
193	CloseServiceHandle(hSCM);
194	return TRUE;
195}
196
197BOOL CNTService::Uninstall()
198{
199	BOOL bResult = FALSE;
200
201	if (!m_bIsWindows9x)
202	{
203		// Open the Service Control Manager
204		SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
205		if (!hSCM)
206			return FALSE;
207
208		SC_HANDLE hService = OpenService(hSCM, m_szServiceName, DELETE);
209		if (hService)
210		{
211			if (DeleteService(hService))
212			{
213				LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_REMOVED, m_szServiceName);
214				bResult = TRUE;
215			}
216			else LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_NOTREMOVED, m_szServiceName);
217
218			CloseServiceHandle(hService);
219		}
220
221		CloseServiceHandle(hSCM);
222	}
223
224	return bResult;
225}
226
227///////////////////////////////////////////////////////////////////////////////////////
228// Logging functions
229
230// This function makes an entry into the application event log
231void CNTService::LogEvent(WORD wType, DWORD dwID, const char* pszS1, const char* pszS2, const char* pszS3)
232{
233	const char* ps[3];
234	int iStr = 0;
235
236	if (!m_bIsWindows9x)
237	{
238		ps[0] = pszS1;
239		ps[1] = pszS2;
240		ps[2] = pszS3;
241
242		for (int i = 0; i < 3; i++)
243			if (ps[i] != NULL)
244				iStr++;
245
246		// Check the event source has been registered and if
247		// not then register it now
248		if (!m_hEventSource)
249			m_hEventSource = RegisterEventSource(NULL, m_szServiceName);
250
251		if (m_hEventSource)
252			ReportEvent(m_hEventSource, wType, 0, dwID, NULL, iStr, 0, ps, NULL);
253	}
254}
255
256//////////////////////////////////////////////////////////////////////////////////////////////
257// Service startup and registration
258
259BOOL CNTService::StartService()
260{
261	SERVICE_TABLE_ENTRY st[] =
262	{
263		{ m_szServiceName, ServiceMain },
264		{ NULL, NULL }
265	};
266
267	// There is no need to start the service on Windows 9x.
268	if (m_bIsWindows9x)
269	{
270		ServiceMain(0, NULL);
271		return TRUE;
272	}
273
274	BOOL bSuccess = StartServiceCtrlDispatcher(st);
275	DWORD dwError = GetLastError();
276	DebugMsg("Returned from StartServiceCtrlDispatcher() with bSuccess=%d,dwError=%ld", bSuccess, dwError);
277	return bSuccess;
278}
279
280// static member function (callback)
281void CNTService::ServiceMain(DWORD dwArgc, LPTSTR* lpszArgv)
282{
283	// Get a pointer to the C++ object
284	CNTService* pService = m_pThis;
285
286	pService->DebugMsg("Entering CNTService::ServiceMain()");
287
288	// Register the control request handler if we're not on Windows 9x.
289	if (!pService->m_bIsWindows9x)
290	{
291		pService->m_Status.dwCurrentState = SERVICE_START_PENDING;
292		pService->m_hServiceStatus = RegisterServiceCtrlHandler(pService->m_szServiceName, Handler);
293		if (pService->m_hServiceStatus == NULL)
294		{
295			pService->LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_CTRLHANDLERNOTINSTALLED);
296			return;
297		}
298	}
299
300	// Start the initialization
301	if (pService->Initialize())
302	{
303		pService->m_bIsRunning = TRUE;
304		pService->m_Status.dwWin32ExitCode = 0;
305		pService->m_Status.dwCheckPoint = 0;
306		pService->m_Status.dwWaitHint = 0;
307
308		pService->Run();
309	}
310
311	// Tell the service manager we are stopped
312	pService->SetStatus(SERVICE_STOPPED);
313	pService->DebugMsg("Leaving CNTService::ServiceMain()");
314}
315
316///////////////////////////////////////////////////////////////////////////////////////////
317// status functions
318
319void CNTService::SetStatus(DWORD dwState)
320{
321	static DWORD dwCheckPoint = 1;
322
323	if (!m_bIsWindows9x)
324	{
325		if (m_Status.dwCurrentState == dwState)
326			m_Status.dwCheckPoint = dwCheckPoint++;
327
328		m_Status.dwCurrentState = dwState;
329		SetServiceStatus(m_hServiceStatus, &m_Status);
330	}
331}
332
333///////////////////////////////////////////////////////////////////////////////////////////
334// Service initialization
335
336BOOL CNTService::Initialize()
337{
338	DebugMsg("Entering CNTService::Initialize()");
339
340	// Start the initialization
341	SetStatus(SERVICE_START_PENDING);
342
343	// Perform the actual initialization
344	BOOL bResult = OnInit();
345
346	// Set final state
347	m_Status.dwWin32ExitCode = GetLastError();
348	m_Status.dwCheckPoint = 0;
349	m_Status.dwWaitHint = 0;
350	if (!bResult)
351	{
352		LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_FAILEDINIT);
353		SetStatus(SERVICE_STOPPED);
354		return FALSE;
355	}
356
357	LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_STARTED);
358	SetStatus(SERVICE_RUNNING);
359
360	DebugMsg("Leaving CNTService::Initialize()");
361	return TRUE;
362}
363
364///////////////////////////////////////////////////////////////////////////////////////////////
365// main function to do the real work of the service
366
367// This function performs the main work of the service.
368// When this function returns the service has stopped.
369BOOL CNTService::Run()
370{
371	DebugMsg("Entering CNTService::Run()");
372
373	while (m_bIsRunning)
374	{
375		DebugMsg("Sleeping...");
376		Sleep(5000);
377	}
378
379	DebugMsg("Leaving CNTService::Run()");
380	return FALSE;
381}
382
383//////////////////////////////////////////////////////////////////////////////////////
384// Control request handlers
385
386// static member function (callback) to handle commands from the
387// service control manager
388void CNTService::Handler(DWORD dwOpcode)
389{
390	// Get a pointer to the object
391	CNTService* pService = m_pThis;
392
393	switch (dwOpcode)
394	{
395		case SERVICE_CONTROL_STOP:
396			pService->SetStatus(SERVICE_STOP_PENDING);
397			pService->m_bIsRunning = FALSE;
398			pService->OnStop();
399			pService->SetStatus(SERVICE_STOPPED);
400			pService->LogEvent(EVENTLOG_INFORMATION_TYPE, EVMSG_STOPPED);
401			break;
402
403		case SERVICE_CONTROL_PAUSE:
404			pService->SetStatus(SERVICE_PAUSE_PENDING);
405			pService->OnPause();
406			pService->SetStatus(SERVICE_PAUSED);
407			break;
408
409		case SERVICE_CONTROL_CONTINUE:
410			pService->SetStatus(SERVICE_CONTINUE_PENDING);
411			pService->OnContinue();
412			pService->SetStatus(SERVICE_RUNNING);
413			break;
414
415		case SERVICE_CONTROL_INTERROGATE:
416			pService->OnInterrogate();
417			break;
418
419		case SERVICE_CONTROL_SHUTDOWN:
420			pService->OnShutdown();
421			break;
422
423		default:
424			if (dwOpcode >= SERVICE_CONTROL_USER)
425			{
426				if (!pService->OnUserControl(dwOpcode))
427					pService->LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_BADREQUEST);
428			}
429			else pService->LogEvent(EVENTLOG_ERROR_TYPE, EVMSG_BADREQUEST);
430			break;
431	}
432
433	// Report current status
434	SetServiceStatus(pService->m_hServiceStatus, &pService->m_Status);
435}
436
437// Called when the service is first initialized
438BOOL CNTService::OnInit()
439{
440	DebugMsg("CNTService::OnInit()");
441	return TRUE;
442}
443
444// Called when the service control manager wants to stop the service
445void CNTService::OnStop()
446{
447	DebugMsg("CNTService::OnStop()");
448}
449
450// called when the service is interrogated
451void CNTService::OnInterrogate()
452{
453	DebugMsg("CNTService::OnInterrogate()");
454}
455
456// called when the service is paused
457void CNTService::OnPause()
458{
459	DebugMsg("CNTService::OnPause()");
460}
461
462// called when the service is continued
463void CNTService::OnContinue()
464{
465	DebugMsg("CNTService::OnContinue()");
466}
467
468// called when the service is shut down
469void CNTService::OnShutdown()
470{
471	DebugMsg("CNTService::OnShutdown()");
472}
473
474// called when the service gets a user control message
475BOOL CNTService::OnUserControl(DWORD dwOpcode)
476{
477	DebugMsg("CNTService::OnUserControl(%8.8lXH)", dwOpcode);
478	return FALSE;
479}
480
481
482////////////////////////////////////////////////////////////////////////////////////////////
483// Debugging support
484
485void CNTService::DebugMsg(const char* pszFormat, ...)
486{
487	char buf[1024];
488	sprintf(buf, "[%s](%lu): ", m_szServiceName, GetCurrentThreadId());
489	va_list arglist;
490	va_start(arglist, pszFormat);
491	vsprintf(&buf[strlen(buf)], pszFormat, arglist);
492	va_end(arglist);
493	strcat(buf, "\n");
494	OutputDebugString(buf);
495}
496