Windows Service

How to work with Windows services and how to develop them

Why we need services?

Windows services typically required for application which have to maintain some background activities like sending data to the cloud, downloading updates or checking your PC for vulnerabilities.

Service binary should be designed to be controlled by SCM (Service control manager). This is relied some requirements on service binary design.

How SCM interact with service binary?

  1. SCM run binary without parameters
  2. ::StartServiceCtrlDispatcher() should be called at main() and pass callback for service main
int main ()
{
  // register dispatcher
  SERVICE_TABLE_ENTRY serviceTable[] =
  {
    { ServiceNameStr, ServiceMain },
    { NULL, NULL }
  };

  // Connects the main thread of a service process to the service control manager,
  // which causes the thread to be the service control dispatcher thread for the calling process. 
  // This call returns when the service has stopped.
  // The process should simply terminate when the call returns.
  ::StartServiceCtrlDispatcher(serviceTable);
}
  1. SCM will call this callback, service have to register control handler firstly and then start working
// Entry point for the service. It registers the handler function for the service and starts the service.
void WINAPI ServiceMain(DWORD dwArgc, PWSTR *pszArgv)
{
  // 'service' is the object of singleton class, which encapsulate all service logic
  auto service = GetInstance();

  // Register the handler function for the service
  service->ServiceStatusHandle = ::RegisterServiceCtrlHandlerW(service->ServiceName.c_str(), ServiceCtrlHandler);
  if (service->ServiceStatusHandle == nullptr)
  {
    // need to call ::GetLastError() to get the error
    return;
  }

  // Start the service.
  service->Start();
}
  1. Inside control handler service have to process SCM control requests
// The function is called by the SCM whenever a control code is sent to the service
void WINAPI ServiceCtrlHandler(DWORD dwCtrl)
{
  auto service = GetInstance();
  switch (dwCtrl)
  {
    case SERVICE_CONTROL_STOP:          service->Stop();                  break;
    case SERVICE_CONTROL_PAUSE:         service->Pause();                 break;
    case SERVICE_CONTROL_CONTINUE:      service->Continue();              break;
    case SERVICE_CONTROL_SHUTDOWN:      service->Shutdown();              break;
    case SERVICE_CONTROL_INTERROGATE:   service->ReportCurrentStatus();   break;
    default: break;
  }
}
  1. ::SetServiceStatus() should be called to set current status (ex. SERVICE_START_PENDING, SERVICE_RUNNING).

SCM control service live cycle, so service should properly set it status and response in a timely manner.

  • Pending status has to be set to prevent SCM to terminate irresponsible service
  • Some long-term background activities better to plan at dedicated thread but not at the SCM callback.

Install and configure service

Service installation source code

// get handle to Service Control Manager
handleSCManager = ::OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE);

// Install the service into SCM by calling CreateService
handleService = ::CreateServiceW(
  handleSCManager,                              
  LpServiceNameStr,              
  LpDisplayNameStr,              
  SERVICE_ALL_ACCESS,         // Desired access
  SERVICE_WIN32_OWN_PROCESS,  // Service type
  SERVICE_AUTO_START,         // Service start type 
  SERVICE_ERROR_NORMAL,       // Error control type
  ServiceBinaryPathStr,       // Service's binary
  NULL,                       // No load ordering group
  NULL,                       // No tag identifier
  LpDependencies,             // Dependencies in format "dep1\0dep2\0\0"
  LpServiceStartName,         // Service running account
  LpPassword                  // Password of the account
);

Optionally service parameters (like action on failure) can be configured using ::ChangeServiceConfig2()

Uninstall service

Before service deletion we need to verify that service was actually stopped (by retrieving service status from SCM):

// get handle to Service Control Manager
handleSCManager = ::OpenSCManagerW(NULL, NULL, SC_MANAGER_CONNECT);

// Open the service with delete, stop, and query status permissions
handleService = ::OpenServiceW(handleSCManager, LpServiceName, SERVICE_STOP | SERVICE_QUERY_STATUS | DELETE);

// Try to stop the service
SERVICE_STATUS serviceStatus = {};
if (::ControlService(handleService, SERVICE_CONTROL_STOP, &serviceStatus))
{
  Sleep(1000);

  while (::QueryServiceStatus(handleService, &serviceStatus))
  {
    if (serviceStatus.dwCurrentState == SERVICE_STOP_PENDING)
    {
      Sleep(1000);
    }
    else break;
  }
}

// Delete service
::DeleteService(handleService);

Service control (sc.exe)

Service can be also controlled by Windows tool sc.exe.
But some not obvious of this tool should be taken into consideration while using it in tests. For example ‘sc stop service_name’ can return 0 when service was not actually stopped, but just sc.exe reached some internal waiting timeout.
Special wrappers like safeServiceStop.bat can be used as a workaround.

Track and query service status

Callback can be passed to ::SubscribeServiceChangeNotifications() to track service changes.
Inside the callback we can query service status:

// Open SCM handle
ScmHandle = ::OpenSCManagerA(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);

// Open handle to service
ServiceHandle = ::OpenServiceA(ScmHandle, "servicename", SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG);

// get service state, ex. SERVICE_RUNNING
:::QueryServiceStatus(ServiceHandle, &status);

// get service start type, ex. SERVICE_AUTO_START
::QueryServiceConfig(ServiceHandle, serviceConfigPtr, bufferSize, &bytesNeeded);