Figure 1   SCM Database Access Options

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 /////////////////////////////////