Access | Description |
SC_MANAGER_ALL_ACCESS | The ability to do everything listed in this table. |
SC_MANAGER_CONNECT | Allows connecting to the SCM. This access is always implied even if not explicitly specified. |
SC_MANAGER_CREATE_SERVICE | The ability to call CreateService to add a service to the SCM database. |
SC_MANAGER_ENUMERATE_SERVICE | The ability to call EnumServicesStatus to get the list of services in the SCM database and each service's status. |
SC_MANAGER_LOCK | The ability to call LockServiceDatabase to stop the SCM from starting any more services. |
SC_MANAGER_QUERY_LOCK_STATUS | The ability to call QueryServiceLockStatus to find out which user (if any) has locked the SCM database. |
Figure 2 Service Access Options
Access | Description |
SERVICE_ALL_ACCESS | Includes STANDARD_RIGHTS_REQUIRED in addition to all of the access types listed in this table. |
SERVICE_START | Enables calling of the StartService function to start the service. |
SERVICE_STOP | Enables calling of the ControlService function to stop the service. |
SERVICE_PAUSE_CONTINUE | Enables calling of the ControlService function to pause or continue the service. |
SERVICE_INTERROGATE | Enables calling of the ControlService function to ask the service to report its status immediately. |
SERVICE_USER_DEFINE_CONTROL | Enables calling of the ControlService function to specify a user-defined control code. |
SERVICE_QUERY_STATUS | Enables calling of the QueryServiceStatus function to ask the SCM about the status of the service. |
SERVICE_ENUMERATE_DEPENDENTS | Enables calling of the EnumDependentServices function to enumerate all the services dependent on the service. |
SERVICE_CHANGE_CONFIG | Enables calling of the ChangeServiceConfig function to change the service configuration. |
SERVICE_QUERY_CONFIG | Enables calling of the QueryServiceConfig function to query the service configuration. |
DELETE | Enables calling of the DeleteService function to delete the service. |
Figure 3 System Services Group Order
Group Name | Part of System |
System Bus Extender | PCMCIA support |
SCSI miniport | SCSI device driver |
Port | None |
Primary disk | Floppy drives and hard disks |
SCSI class | SCSI drives |
SCSI CDROM class | CD-ROM drives |
Filter | CD Audio |
boot file system | Fast FAT drive access |
Base | System beep |
Pointer Port | Mouse support |
Keyboard Port | Keyboard (i8042prt) support |
Pointer Class | More mouse support |
Keyboard Class | More keyboard support |
Video Init | Video support |
Video | Video chip support |
Video Save | More video support |
file system | CD-ROM and NTFS file system support |
Event log | Event log support |
Streams Drivers | None |
PNP_TDI | NetBT and TCP/IP support |
NDIS | Netword support |
NDISWAN | None |
TDI | AFD Networking Support & DHCP |
NetBIOSGroup | NetBIOS support |
SpoolerGroup | Spool support |
NetDDEGroup | Network DDE support |
Parallel arbitrator | Parallel port support |
extended base | Modem, serial, and parallel support |
RemoteValidation | Net logon support |
PCI Configuration | PCI configuration support |
Figure 4 A ServiceInstall Function
void ServiceInstall(LPCTSTR pszInternalName, LPCTSTR pszDisplayName,
DWORD dwServiceType, DWORD dwStartType, DWORD dwErrorControl) {
// Open the SCM database to add a service
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
// Get the full pathname of our service's executable
char szModulePathname[_MAX_PATH];
GetModuleFileName(NULL, szModulePathname, sizeof(szModulePathname));
// Add this service to the SCM database
SC_HANDLE hService = CreateService(
hSCM, pszInternalName, pszDisplayName, 0, dwServiceType,
dwStartType, dwErrorControl, szModulePathname,
NULL, NULL, NULL, NULL, NULL);
// Close the newly created service and the SCM
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
}
Figure 5 A ServiceRemove Function
void ServiceRemove(LPCTSTR pszInternalName) {
// Open the SCM database
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
// Open the service for delete access
SC_HANDLE hService = OpenService(hSCM, pszInternalName, DELETE);
// Mark the service for deletion.
// NOTE: The service is not deleted until all handles
// to it are closed and the service stops running.
DeleteService(hService);
// Close the service and the SCM
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
}
Figure 6 A StopService Function
void StopService(LPCTSTR pszInternalName) {
// Open the SCM and the desired service.
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
SC_HANDLE hService = OpenService(hSCM, pszInternalName, SERVICE_STOP);
// Tell the service to stop.
SERVICE_STATUS ss;
ControlService(hService, SERVICE_CONTROL_STOP, &ss);
// Wait up to 15 seconds for the service to stop.
WaitForServiceToReachState(hService, SERVICE_STOP, &ss, 15000);
// Close the service and the SCM.
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
}
Figure 7 A WaitForServiceToReachState Function
BOOL WaitForServiceToReachState(SC_HANDLE hService, DWORD dwDesiredState,
SERVICE_STATUS* pss, DWORD dwMilliseconds) {
DWORD dwLastState, dwLastCheckPoint;
BOOL fFirstTime = TRUE; // Don't compare state & checkpoint the first time through
BOOL fServiceOk = TRUE;
DWORD dwTimeout = GetTickCount() + dwMilliseconds;
// Loop until the service reaches the desired state,
// an error occurs, or we timeout
while (TRUE) {
// Get current state of service
fServiceOk = ::QueryServiceStatus(hService, pss);
// If we can't query the service, we're done
if (!fServiceOk) break;
// If the service reaches the desired state, we're done
if (pss->dwCurrentState == dwDesiredState) break;
// If we timed-out, we're done
if ((dwMilliseconds != INFINITE) && (dwTimeout > GetTickCount())) {
SetLastError(ERROR_TIMEOUT);
break;
}
// If this is our first time, save the service's state & checkpoint
if (fFirstTime) {
dwLastState = pss->dwCurrentState;
dwLastCheckPoint = pss->dwCheckPoint;
fFirstTime = FALSE;
} else {
// If not first time & state has changed, save state & checkpoint
if (dwLastState != pss->dwCurrentState) {
dwLastState = pss->dwCurrentState;
dwLastCheckPoint = pss->dwCheckPoint;
} else {
// State hasn't change, check that checkpoint is increasing
if (pss->dwCheckPoint > dwLastCheckPoint) {
// Checkpoint has increased, save checkpoint
dwLastCheckPoint = pss->dwCheckPoint;
} else {
// Checkpoint hasn't increased, service failed, we're done!
fServiceOk = FALSE;
break;
}
}
}
// We're not done, wait the specified period of time
Sleep(pss->dwWaitHint);
}
// Note: The last SERVICE_STATUS is returned to the caller so
// that the caller can check the service state and error codes.
return(fServiceOk);
}
Figure 9 MSJTimeSrv.c
/*****************************************************************************
Module : MSJTimeSrv.c
Notices: Written 1997 by Jeffrey Richter
Description: Minimal Service Template
*****************************************************************************/
#define STRICT
#define UNICODE
#include <Windows.h>
//////////////////////////////////////////////////////////////////////////////
#define dimof(A) (sizeof(A) / sizeof(A[0]))
//////////////////////////////////////////////////////////////////////////////
WCHAR g_szAppName[] = L"MSJ Time Service";
//////////////////////////////////////////////////////////////////////////////
HANDLE g_hIOCP = NULL;
// The completion port wakes for 1 of 2 reasons:
enum COMPKEY {
CK_SERVICECONTROL, // A service control code
CK_PIPE // A client connects to our pipe
};
//////////////////////////////////////////////////////////////////////////////
void WINAPI TimeServiceHandler(DWORD fdwControl) {
// The Handler thread is very simple and executes very quickly because
// it just passes the control code off to the ServiceMain thread.
PostQueuedCompletionStatus(g_hIOCP, fdwControl, CK_SERVICECONTROL, NULL);
}
//////////////////////////////////////////////////////////////////////////////
#define SERVICE_CONTROL_RUN 0x00000000
DWORD dwSrvCtrlToPend[256] = { // 255 is max user-defined code
/* 0: SERVICE_CONTROL_RUN */ SERVICE_START_PENDING,
/* 1: SERVICE_CONTROL_STOP */ SERVICE_STOP_PENDING,
/* 2: SERVICE_CONTROL_PAUSE */ SERVICE_PAUSE_PENDING,
/* 3: SERVICE_CONTROL_CONTINUE */ SERVICE_CONTINUE_PENDING,
/* 4: SERVICE_CONTROL_INTERROGATE */ 0,
/* 5: SERVICE_CONTROL_SHUTDOWN */ SERVICE_STOP_PENDING,
/* 6 - 255: User-defined codes */ 0
};
DWORD dwSrvPendToState[] = {
/* 0: Undefined */ 0,
/* 1: SERVICE_STOPPED */ 0,
/* 2: SERVICE_START_PENDING */ SERVICE_RUNNING,
/* 3: SERVICE_STOP_PENDING */ SERVICE_STOPPED,
/* 4: SERVICE_RUNNING */ 0,
/* 5: SERVICE_CONTINUE_PENDING */ SERVICE_RUNNING,
/* 6: SERVICE_PAUSE_PENDING */ SERVICE_PAUSED,
/* 7: SERVICE_PAUSED */ 0
};
//////////////////////////////////////////////////////////////////////////////
void WINAPI TimeServiceMain(DWORD dwArgc, LPTSTR *lpszArgv) {
DWORD dwCompKey = CK_SERVICECONTROL;
DWORD fdwControl = SERVICE_CONTROL_RUN;
DWORD dwBytesTransferred;
SYSTEMTIME st;
HANDLE hpipe;
OVERLAPPED o, *po;
SERVICE_STATUS ss;
SERVICE_STATUS_HANDLE hSS;
// Create the completion port and save its handle in a global
// variable so that the Handler function can access it.
g_hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, CK_PIPE, 0);
// Give SCM the address of this service's Handler
// NOTE: hSS does not have to be closed.
hSS = RegisterServiceCtrlHandler(g_szAppName, TimeServiceHandler);
// Do what the service should do.
// Initialize the members that never change
ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
ss.dwControlsAccepted = SERVICE_ACCEPT_STOP |
SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN;
do {
switch (dwCompKey) {
case CK_SERVICECONTROL:
// We got a new control code
ss.dwWin32ExitCode = NO_ERROR;
ss.dwServiceSpecificExitCode = 0;
ss.dwCheckPoint = 0;
ss.dwWaitHint = 0;
if (fdwControl == SERVICE_CONTROL_INTERROGATE) {
SetServiceStatus(hSS, &ss);
break;
}
// Determine which PENDING state to return
if (dwSrvCtrlToPend[fdwControl] != 0) {
ss.dwCurrentState = dwSrvCtrlToPend[fdwControl];
ss.dwCheckPoint = 0;
ss.dwWaitHint = 500; // half a second
SetServiceStatus(hSS, &ss);
}
switch (fdwControl) {
case SERVICE_CONTROL_RUN:
case SERVICE_CONTROL_CONTINUE:
// While running, create a pipe that clients can connect to.
hpipe = CreateNamedPipe(L"\\\\.\\pipe\\MSJTime",
PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_BYTE, 1, sizeof(st), sizeof(st), 1000, NULL);
// Associate the pipe with the completion port
CreateIoCompletionPort(hpipe, g_hIOCP, CK_PIPE, 0);
// Pend an asynchronous connect against the pipe
ZeroMemory(&o, sizeof(o));
ConnectNamedPipe(hpipe, &o);
break;
case SERVICE_CONTROL_PAUSE:
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
// When not running, close the pipe so clients can't connect
CloseHandle(hpipe);
break;
case 128: // User-defined control (demonstration purposes)
MessageBox(NULL, L"Got control code 128", g_szAppName, MB_OK);
break;
}
// Determine which complete state to return
if (dwSrvPendToState[ss.dwCurrentState] != 0) {
ss.dwCurrentState = dwSrvPendToState[ss.dwCurrentState];
ss.dwCheckPoint = ss.dwWaitHint = 0;
SetServiceStatus(hSS, &ss);
}
break;
case CK_PIPE:
// We got a client request: Send our current time to the client
GetSystemTime(&st);
WriteFile(hpipe, &st, sizeof(st), &dwBytesTransferred, NULL);
FlushFileBuffers(hpipe);
DisconnectNamedPipe(hpipe);
// Allow another client to connect
ZeroMemory(&o, sizeof(o));
ConnectNamedPipe(hpipe, &o);
}
if (ss.dwCurrentState != SERVICE_STOPPED) {
// Sleep until a control code comes in or a client connects
GetQueuedCompletionStatus(g_hIOCP, &dwBytesTransferred,
&dwCompKey, &po, INFINITE);
fdwControl = dwBytesTransferred;
}
} while (ss.dwCurrentState != SERVICE_STOPPED);
// Cleanup and stop this service
CloseHandle(g_hIOCP);
}
//////////////////////////////////////////////////////////////////////////////
void InstallService() {
TCHAR szModulePathname[_MAX_PATH];
SC_HANDLE hService;
// Open the SCM on this machine.
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
// Get our full pathname
GetModuleFileName(NULL, szModulePathname, dimof(szModulePathname));
// Add this service to the SCM's database.
hService = CreateService(hSCM, g_szAppName, g_szAppName, 0,
SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE,
szModulePathname, NULL, NULL, NULL, NULL, NULL);
// Close the service and the SCM
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
}
//////////////////////////////////////////////////////////////////////////////
void RemoveService() {
// Open the SCM on this machine.
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
// Open this service for DELETE access
SC_HANDLE hService = OpenService(hSCM, g_szAppName, DELETE);
// Remove this service from the SCM's database.
DeleteService(hService);
// Close the service and the SCM
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
}
//////////////////////////////////////////////////////////////////////////////
int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstExePrev,
LPSTR pszCmdLine, int nCmdShow) {
int nArgc = __argc;
#ifdef UNICODE
LPCTSTR *ppArgv = (LPCTSTR*) CommandLineToArgvW(GetCommandLine(), &nArgc);
#else
LPCTSTR *ppArgv = (LPCTSTR*) __argv;
#endif
BOOL fStartService = (nArgc < 2), fDebug = FALSE;
int i;
for (i = 1; i < nArgc; i++) {
if ((ppArgv[i][0] == __TEXT('-')) || (ppArgv[i][0] == __TEXT('/'))) {
// Command line switch
if (lstrcmpi(&ppArgv[i][1], __TEXT("install")) == 0)
InstallService();
if (lstrcmpi(&ppArgv[i][1], __TEXT("remove")) == 0)
RemoveService();
if (lstrcmpi(&ppArgv[i][1], __TEXT("debug")) == 0)
fDebug = TRUE;
}
}
#ifdef UNICODE
HeapFree(GetProcessHeap(), 0, (PVOID) ppArgv);
#endif
if (fDebug) {
// Running as EXE not as service, just run the service for debugging
TimeServiceMain(0, NULL);
}
if (fStartService) {
SERVICE_TABLE_ENTRY ServiceTable[] = {
{ g_szAppName, TimeServiceMain },
{ NULL, NULL } // End of list
};
StartServiceCtrlDispatcher(ServiceTable);
}
return(0);
}
//////////////////////////////// End Of File /////////////////////////////////
Figure 10 MSJTimeClient.c
/*****************************************************************************
Module : MSJTimeClient.c
Notices: Written 1997 by Jeffrey Richter
Description: Client to request machine system time
*****************************************************************************/
#define STRICT
#include <Windows.h>
#include <WindowsX.h>
#include "Resource.h"
//////////////////////////////////////////////////////////////////////////////
#define dimof(A) (sizeof(A) / sizeof(A[0]))
// The normal HANDLE_MSG macro in WINDOWSX.H does not work properly for
// dialog boxes because DlgProc's return a BOOL instead of an LRESULT (like
// WndProcs). This chHANDLE_DLGMSG macro corrects the problem:
#define chHANDLE_DLGMSG(hwnd, message, fn) \
case (message): return (SetDlgMsgResult(hwnd, uMsg, \
HANDLE_##message((hwnd), (wParam), (lParam), (fn))))
//////////////////////////////////////////////////////////////////////////////
BOOL MSJTimeClient_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam) {
// Assume that the server is on the same machine as the client
SetDlgItemText(hwnd, IDC_SERVER, __TEXT("."));
return(TRUE);
}
//////////////////////////////////////////////////////////////////////////////
void MSJTimeClient_OnCommand(HWND hwnd, int id, HWND hwndCtl,
UINT codeNotify) {
SYSTEMTIME st;
TCHAR sz[500];
DWORD cbRead = 0;
HANDLE hpipe;
BOOL fOk;
switch (id) {
case IDCANCEL:
EndDialog(hwnd, id);
break;
case IDOK:
// Construct the pathname of the pipe
sz[0] = sz[1] = __TEXT('\\');
GetDlgItemText(hwnd, IDC_SERVER, &sz[2], dimof(sz) - 2);
lstrcat(sz, __TEXT("\\pipe\\MSJTime"));
// Attempt to connect to the pipe
fOk = WaitNamedPipe(sz, NMPWAIT_USE_DEFAULT_WAIT);
if (fOk) {
// Get a handle to use to talk to the pipe
hpipe = CreateFile(sz, GENERIC_READ, 0, NULL,
OPEN_EXISTING, 0, NULL);
fOk = (hpipe != INVALID_HANDLE_VALUE);
}
if (fOk) {
// Valid handle, read time from pipe
ReadFile(hpipe, &st, sizeof(st), &cbRead, NULL);
CloseHandle(hpipe);
// Convert UTC time to client machine's local time and display it
SystemTimeToTzSpecificLocalTime(NULL, &st, &st);
GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &st,
NULL, sz, dimof(sz));
SetDlgItemText(hwnd, IDC_DATE, sz);
GetTimeFormat(LOCALE_USER_DEFAULT, LOCALE_NOUSEROVERRIDE, &st,
NULL, sz, dimof(sz));
SetDlgItemText(hwnd, IDC_TIME, sz);
} else {
LPCTSTR pszError = (GetLastError() == ERROR_FILE_NOT_FOUND)
? __TEXT("Service not found") : __TEXT("Service busy");
SetDlgItemText(hwnd, IDC_DATE, pszError);
SetDlgItemText(hwnd, IDC_TIME, pszError);
}
break;
}
}
//////////////////////////////////////////////////////////////////////////////
BOOL WINAPI MSJTimeClient_DlgProc(HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, MSJTimeClient_OnInitDialog);
chHANDLE_DLGMSG(hwnd, WM_COMMAND, MSJTimeClient_OnCommand);
}
return(FALSE);
}
//////////////////////////////////////////////////////////////////////////////
int WINAPI WinMain (HINSTANCE hinstExe, HINSTANCE hinstExePrev,
LPSTR pszCmdLine, int nCmdShow) {
return(DialogBox(hinstExe, MAKEINTRESOURCE(IDD_MSJTIMECLIENT), NULL,
MSJTimeClient_DlgProc));
}
//////////////////////////////// End Of File /////////////////////////////////