Click here to Skip to main content
13,771,389 members
Click here to Skip to main content
Add your own
alternative version

Stats

21K views
121 downloads
59 bookmarked
Posted 6 May 2016
Licenced CPOL

Interact with Windows services in C++

, 6 May 2016
Rate this:
Please Sign up or sign in to vote.
Interact, control and configure Windows services using C++ components built on top of the Windows services API

This article shows how to interact, control and configure Windows services from a native application written in C++. The purpose of the article is to present some of the Windows API for interacting with services and build reusable wrapping components in C++ along the way. However, this will not be a comprehensive walk through all the Windows service APIs.

For a complete Windows API reference see Service Functions.

General Procedure

In order to interact with an existing service you must follow these general steps:

  • Call OpenSCManager to establish a connection to the service control manager on a specified computer and open the specified service control manager database. With this call you must specify the desire access. To enumerate services you must specify SC_MANAGER_ENUMERATE_SERVICE. To open services, query, change statuses, etc. you need SC_MANAGER_CONNECT. For additional operations such as creating services or locking the service database you need to specify other access right codes. However, in this case the process must run elevated with administrator privileges, otherwise the call will fail.
  • Call OpenService to open an existing service.
  • Make calls to functions that query, control or configure the service (such as QueryServiceStatusEx to get the service status, QueryServiceConfig to get the service configuration data, ChangeServiceConfig to change the service configuration data, or EnumDependentServices to enumerate dependent services).
  • Call CloseServiceHandle to close the handles for the service control manager (the handle returned by OpenSCManager) and the service (the handle returned by OpenService).

Examples using the C++ API

The attached source code contains types and functions written in C++ that wrap the C Windows API, making it easier to use in a C++ application. These components will enable us to write code like the following.

Example for enumerating all services on the local computer:

auto services = ServiceEnumerator::EnumerateServices();
for (auto const & s : services)
{
  std::wcout << "Name:    " << s.ServiceName << std::endl

             << "Display: " << s.DisplayName << std::endl

             << "Status:  " << ServiceStatusToString(static_cast<ServiceStatus>(s.Status.dwCurrentState)) << std::endl

             << "--------------------------" << std::endl;

}

Example for opening a service on the local computer, reading and updating its configuration, reading and changing its status:

// open the service
auto service = ServiceController{ L"LanmanWorkstation" };

auto print_status = [&service]() {
  std::wcout << "Status:              " << ServiceStatusToString(service.GetStatus()) << std::endl; 

};



auto print_config = [](ServiceConfig const config) {

  std::wcout << "---------------------" << std::endl;

  std::wcout << "Start name:          " << config.GetStartName() << std::endl;

  std::wcout << "Display name:        " << config.GetDisplayName() << std::endl;

  std::wcout << "Description:         " << config.GetDescription() << std::endl;

  std::wcout << "Type:                " << ServiceTypeToString(config.GetType()) << std::endl;

  std::wcout << "Start type:          " << ServiceStartTypeToString(config.GetStartType()) << std::endl;

  std::wcout << "Error control:       " << ServiceErrorControlToString(config.GetErrorControl()) << std::endl;

  std::wcout << "Binary path:         " << config.GetBinaryPathName() << std::endl;

  std::wcout << "Load ordering group: " << config.GetLoadOrderingGroup() << std::endl;

  std::wcout << "Tag ID:              " << config.GetTagId() << std::endl;

  std::wcout << "Dependencies:        ";

  for (auto const & d : config.GetDependencies()) std::wcout << d << ", ";

  std::wcout << std::endl;

  std::wcout << "---------------------" << std::endl;

};



// read the service configuration, temporary change its description and then restore the old one

{

  auto config = service.GetServiceConfig();



  print_config(config);



  auto oldDescription = config.GetDescription();



  auto newDescription = _T("This is a sample description.");



  config.ChangeDescription(newDescription);



  config.Refresh();



  print_config(config);



  config.ChangeDescription(oldDescription);



  config.Refresh();



  print_config(config);

}



// check the service status

print_status();



// start the service if the service is currently stopped

if (service.GetStatus() == ServiceStatus::Stopped)

{

  service.Start();



  print_status();



  service.WaitForStatus(ServiceStatus::Running);



  print_status();

}



// if the service and running and it supports pause and continue then first pause and then resume the service

if (service.GetStatus() == ServiceStatus::Running && service.CanPauseContinue())

{

  service.Pause();



  print_status();



  service.WaitForStatus(ServiceStatus::Paused);



  print_status();



  service.Continue();



  print_status();



  service.WaitForStatus(ServiceStatus::Running);



  print_status();

}

Throughout the rest of the article I will present the C++ API and explain how the C Windows API is used in these wrappers.

Service constants

Windows APIs use numerical values as parameters for types, statuses, states, configuration settings and others. These are defined as macros in headers winsvc.h and winnt.h. In the C++ code however I prefer using constants or enumerations. Therefore I have defined enum classes for all these values that I need to use with the service APIs. These are available in the ServiceContants.h header.

enum class ServiceStatus 
{
   Unknown     = 0,
   Stopped     = SERVICE_STOPPED,
   Starting    = SERVICE_START_PENDING,
   Stopping    = SERVICE_STOP_PENDING,
   Running     = SERVICE_RUNNING,
   Continuing  = SERVICE_CONTINUE_PENDING,
   Pausing     = SERVICE_PAUSE_PENDING,
   Paused      = SERVICE_PAUSED
};

enum class ServiceControls
{
   Stop                                = SERVICE_ACCEPT_STOP,
   PauseAndContinue                    = SERVICE_ACCEPT_PAUSE_CONTINUE,
   ChangeParams                        = SERVICE_ACCEPT_PARAMCHANGE,
   ChangeBindings                      = SERVICE_ACCEPT_NETBINDCHANGE,
   PreShutdown                         = SERVICE_ACCEPT_PRESHUTDOWN,
   ShutdownNotification                = SERVICE_ACCEPT_SHUTDOWN,
   HardwareProfileChangedNotification  = SERVICE_ACCEPT_HARDWAREPROFILECHANGE,
   PowerChangedNotification            = SERVICE_ACCEPT_POWEREVENT,
   SessionChangedNotification          = SERVICE_ACCEPT_SESSIONCHANGE,
   TriggerEventNotification            = SERVICE_ACCEPT_TRIGGEREVENT,
   TimeChangeNotification              = SERVICE_ACCEPT_TIMECHANGE,
   UserModeNotification                = 0x00000800, //SERVICE_ACCEPT_USERMODEREBOOT
};

enum class ServiceType
{
   KernelDriver       = SERVICE_KERNEL_DRIVER,
   FileSystemDriver   = SERVICE_FILE_SYSTEM_DRIVER,
   Adapter            = SERVICE_ADAPTER,
   RecognizerDriver   = SERVICE_RECOGNIZER_DRIVER,
   Win32OwnProcess    = SERVICE_WIN32_OWN_PROCESS,
   Win32ShareProcess  = SERVICE_WIN32_SHARE_PROCESS,
   InteractiveDriver  = SERVICE_INTERACTIVE_PROCESS,
   Driver             = SERVICE_DRIVER,
   Win32              = SERVICE_WIN32,
   All                = SERVICE_TYPE_ALL
};

enum class ServiceStartType
{
   Boot     = SERVICE_BOOT_START,
   System   = SERVICE_SYSTEM_START,
   Auto     = SERVICE_AUTO_START,
   Demand   = SERVICE_DEMAND_START,
   Disabled = SERVICE_DISABLED,
};

enum class ServiceErrorControl
{
   Ignore   = SERVICE_ERROR_IGNORE,
   Normal   = SERVICE_ERROR_NORMAL,
   Severe   = SERVICE_ERROR_SEVERE,
   Critical = SERVICE_ERROR_CRITICAL,
};

enum class ServiceState
{
   Active   = SERVICE_ACTIVE,
   Inactive = SERVICE_INACTIVE,
   All      = SERVICE_STATE_ALL
};

The header also defines a string type called ServiceString that is either std::string or std::wstring, depending on the character set used to build the native project. This is the type used for string in the C++ service components.

#ifdef UNICODE
#define ServiceString   std::wstring
#else
#define ServiceString   std::string
#endif

Service handle

Functions such as OpenSCManager and OpenService (but also CreateService) return a SC_HANDLE that is a pointer value. However, the handle must be closed after the code that requires the handle finishes execution. To simplify the correct closing of service handles in all cases the handle will be wrapped in a class that calls CloseServiceHandle when the object is destroyed, in a RAII fashion. Such an implementation is available in the ServiceHandle.h header.

class ServiceHandle
{
   SC_HANDLE _handle = nullptr;

   void Close()
   {
      if (_handle != nullptr)
         ::CloseServiceHandle(_handle);
   }
   
public:
   ServiceHandle(SC_HANDLE const handle = nullptr) noexcept :_handle(handle) {}

   ServiceHandle(ServiceHandle&& other) noexcept : _handle(std::move(other._handle)) {}

   ServiceHandle& operator=(SC_HANDLE const handle)
   {
      if (_handle != handle)
      {
         Close();

         _handle = handle;
      }

      return *this;
   }

   ServiceHandle& operator=(ServiceHandle&& other)
   {
      if (this != &other)
      {
         _handle = std::move(other._handle);
         other._handle = nullptr;
      }

      return *this;
   }

   operator SC_HANDLE() const noexcept { return _handle; }

   explicit operator bool() const noexcept { return _handle != nullptr; }
   
   ~ServiceHandle() 
   {
      Close();
   }
};

ServiceController class

The ServiceController class represents a Windows service and allows you to query and change the service status, query and change configuration data and others. This is a minimal implementation and can be extended with additional functionality as needed. The implementation is available in the header ServiceController.h.

The constructor of the class takes the service name and optionally desired access. If the access is specified the value is SERVICE_ALL_ACCESS that means access rights for all service operations. The following calls are performed in the constructor:

  1. OpenSCManager to establish a connection to the service control manager on the local computer and open the default service control manager database.
  2. OpenService to open the service; if this operation fails the service control manager handle is closed.
  3. QueryServiceStatusEx to retrieve information about the accepted controls, such as whether the service can be stopped, paused and continue or it can receive notifications for various events.
class ServiceController
{
public:
   ServiceController(ServiceString name, DWORD access = SERVICE_ALL_ACCESS)
   {
      srvName = name;
      scHandle = ::OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT);
      if (scHandle)
      {
         srvHandle = ::OpenService(scHandle, name.c_str(), access);
         if (!srvHandle)
         {
            scHandle = nullptr;
         }
         else
         {
            auto bytesNeeded = DWORD{ 0 };
            auto ssp = SERVICE_STATUS_PROCESS{ 0 };

            auto result = ::QueryServiceStatusEx(
               srvHandle,
               SC_STATUS_PROCESS_INFO,
               reinterpret_cast<LPBYTE>(&ssp),
               sizeof(ssp),
               &bytesNeeded);

            if (result != 0)
            {
               auto setControl = [ssp](std::map<ServiceControls, bool>& controls, ServiceControls const control) 
               { 
                  controls[control] = (ssp.dwControlsAccepted & static_cast<int>(control)) != 0; 
               };

               setControl(acceptedControls, ServiceControls::Stop);
               setControl(acceptedControls, ServiceControls::PauseAndContinue);
               setControl(acceptedControls, ServiceControls::ChangeParams);
               setControl(acceptedControls, ServiceControls::ChangeBindings);
               setControl(acceptedControls, ServiceControls::PreShutdown);
               setControl(acceptedControls, ServiceControls::ShutdownNotification);
               setControl(acceptedControls, ServiceControls::HardwareProfileChangedNotification);
               setControl(acceptedControls, ServiceControls::PowerChangedNotification);
               setControl(acceptedControls, ServiceControls::SessionChangedNotification);
               setControl(acceptedControls, ServiceControls::TriggerEventNotification);
               setControl(acceptedControls, ServiceControls::TimeChangeNotification);
               setControl(acceptedControls, ServiceControls::UserModeNotification);
            }
         }
      }
   }
   
private:
   ServiceHandle scHandle;
   ServiceHandle srvHandle;
   ServiceString srvName;

   std::map<ServiceControls, bool> acceptedControls = 
   {
      { ServiceControls::Stop, false},
      { ServiceControls::PauseAndContinue, false },
      { ServiceControls::ChangeParams, false },
      { ServiceControls::ChangeBindings, false },
      { ServiceControls::PreShutdown, false },
      { ServiceControls::ShutdownNotification, false },
      { ServiceControls::HardwareProfileChangedNotification, false },
      { ServiceControls::PowerChangedNotification, false },
      { ServiceControls::SessionChangedNotification, false },
      { ServiceControls::TriggerEventNotification, false },
      { ServiceControls::TimeChangeNotification, false },
      { ServiceControls::UserModeNotification, false },
   };   
};

You can check what control codes the service accepts and processes in its handler function by using the CanAcceptControl() method. It takes a value defined by the ServiceControls enumeration and returns a bool indicating whether the control code is accepted and processed or not. Several additional methods are defined using this method.

bool CanAcceptControl(ServiceControls const control) const
{
  auto it = acceptedControls.find(control);
  return it != std::end(acceptedControls) ? it->second : false;
}

bool CanPauseContinue() const          { return CanAcceptControl(ServiceControls::PauseAndContinue); }
bool CanShutdown() const               { return CanAcceptControl(ServiceControls::ShutdownNotification); }
bool CanStop() const                   { return CanAcceptControl(ServiceControls::Stop); }

Checking status

Function QueryServiceStatusEx is also used to check the status of the service (such as stopped, running, starting, etc.). This function returns information in a SERVICE_STATUS_PROCESS structure. The dwCurrentState member represents the current status as defined by the ServiceStatus enumeration.

ServiceStatus GetStatus()
{
  auto status = ServiceStatus::Unknown;

  if (srvHandle)
  {
     auto bytesNeeded = DWORD{ 0 };
     auto ssp = SERVICE_STATUS_PROCESS {0};

     auto result = ::QueryServiceStatusEx(
        srvHandle,
        SC_STATUS_PROCESS_INFO,
        reinterpret_cast<LPBYTE>(&ssp),
        sizeof(ssp),
        &bytesNeeded);

     if (result != 0)
     {
        status = static_cast<ServiceStatus>(ssp.dwCurrentState)
     }
  }

  return status;
}

Changing status

Services can be started and stopped and some services can be paused and continued. The ServiceController class provides methods for all these four operations.

To start the service we must call StartService. This function takes the service handle and optionally arguments to be passed to the ServiceMain function of the service. In this implementation no parameters are passed.

bool Start()
{
  auto success = false;

  if (srvHandle)
  {
     auto result = ::StartService(srvHandle, 0, nullptr);
     success = result != 0;
  }

  return success;
}

The Stop(), Pause() and Continue() methods are implementing using the ControlService function. This function takes the service handle, a control code that represents a notification to be sent to the service and an object of type SERVICE_STATUS that receives the latest service status information.

bool Stop()
{
  auto success = false;

  if (srvHandle)
  {
     success = StopDependentServices();

     if (success)
     {
        auto ssp = SERVICE_STATUS_PROCESS{ 0 };
        success = ChangeServiceStatus(srvHandle, SERVICE_CONTROL_STOP, ssp);
     }
  }

  return success;
}

bool Pause()
{
  auto success = false;

  if (srvHandle)
  {
     auto ssp = SERVICE_STATUS_PROCESS{ 0 };
     success = ChangeServiceStatus(srvHandle, SERVICE_CONTROL_PAUSE, ssp);
  }

  return success;
}

bool Continue()
{
  auto success = false;

  if (srvHandle)
  {
     auto ssp = SERVICE_STATUS_PROCESS{ 0 };
     success = ChangeServiceStatus(srvHandle, SERVICE_CONTROL_CONTINUE, ssp);
  }

  return success;
}

static bool ChangeServiceStatus(SC_HANDLE const handle, DWORD const controlCode, SERVICE_STATUS_PROCESS& ssp)
{
  auto success = false;

  if (handle)
  {
     auto result = ::ControlService(
        handle,
        controlCode,
        reinterpret_cast<LPSERVICE_STATUS>(&ssp));

     success = result != 0;
  }

  return success;
}

The implementation of the Stop() method is a little bit different though than of Pause() and Continue() because the service might have dependent services and those also need to be stopped when the service is stopped. Stopping the dependent services is implemented in the StopDependentServices() private function. This function does the following:

  • Enumerates the dependent services by calling EnumDependentServices. Like all the other APIs it is first called with a null output buffer to get the size of the buffer and then called again with a properly sized buffer. This function returns an array of ENUM_SERVICE_STATUS and the number of elements.
  • For each dependent service we need to open the service, stop the service and wait for the service to actually stop.

Should any of the dependent services fail to properly stop the rest of the routine will not execute and the main service will not be stopped. Notice that when waiting for the service we have a timeout set for 30 seconds by default. If the service does not stop within this interval the routine fails.

bool StopDependentServices()
{
  auto ess = ENUM_SERVICE_STATUS{ 0 };
  auto bytesNeeded = DWORD{ 0 };
  auto count = DWORD{ 0 };

  if (!::EnumDependentServices(
     srvHandle,
     SERVICE_ACTIVE,
     nullptr,
     0,
     &bytesNeeded,
     &count))
  {
     if (GetLastError() != ERROR_MORE_DATA)
        return false;

     std::vector<unsigned char> buffer(bytesNeeded, 0);

     if (!::EnumDependentServices(
        srvHandle,
        SERVICE_ACTIVE,
        reinterpret_cast<LPENUM_SERVICE_STATUS>(buffer.data()),
        bytesNeeded,
        &bytesNeeded,
        &count))
     {
        return false;
     }

     for (auto i = DWORD{ 0 }; i < count; ++i)

     {

        auto ess = static_cast<ENUM_SERVICE_STATUS>(*(reinterpret_cast<LPENUM_SERVICE_STATUS>(buffer.data() + i)));

        ServiceHandle handle = ::OpenService(
           scHandle,
           ess.lpServiceName,
           SERVICE_STOP | SERVICE_QUERY_STATUS);

        if (!handle)
           return false;

        auto ssp = SERVICE_STATUS_PROCESS{ 0 };

        if (!ChangeServiceStatus(handle, SERVICE_CONTROL_STOP, ssp))
           return false;

        if (!WaitForStatus(handle, ssp, ServiceStatus::Stopped))
           return false;
     }
  }

  return true;
}

Waiting for the status to change

When you start, stop, pause or continue a service the change of status might not happen instantly. For instance, starting a service could take several seconds. The Start(), Stop(), Pause() and Continue() methods return immediately, indicating only that the change request has been completed successfully, not that the status was indeed changed. (Notice that Stop() returns immediatelly after requesting the stopping of the service, but does wait for all the dependent services to stop first.)

To check the status has changed you need to periodically query the service status until it changes to the desired state. The ServiceController class provides a method that does that called WaitForStatus(). The method takes as parameters the desired status and a timeout (set by default to 30 seconds). It first queries the service status and if the service is not in the desired state then it puts the current thread to sleep for a while and then repeats the operation. The loop ends either when the state has been changed to the desired state or when the timeout elapses. Notice that the sleep interval is 1/10th of the service's wait hint, but not smaller than 1 second and not larger than 10 seconds.

bool WaitForStatus(ServiceStatus desiredStatus, std::chrono::milliseconds const timeout = 30000ms)
{
  auto success = false;

  if (srvHandle)
  {
     auto ssp = SERVICE_STATUS_PROCESS{ 0 };

     auto bytesNeeded = DWORD{ 0 };

     if (::QueryServiceStatusEx(
        srvHandle,
        SC_STATUS_PROCESS_INFO,
        reinterpret_cast<LPBYTE>(&ssp),
        sizeof(ssp),
        &bytesNeeded))
     {
        success = WaitForStatus(srvHandle, ssp, desiredStatus, timeout);
     }
  }

  return success;
}

static std::chrono::milliseconds GetWaitTime(DWORD const waitHint)
{
  auto waitTime = waitHint / 10;

  if (waitTime < 1000)

     waitTime = 1000;

  else if (waitTime > 10000)
     waitTime = 10000;

  return std::chrono::milliseconds(waitTime);
}

static bool WaitForStatus(SC_HANDLE const handle, 
                          SERVICE_STATUS_PROCESS& ssp, 
                          ServiceStatus const desireStatus, 
                          std::chrono::milliseconds const timeout = 30000ms)
{
  auto success = ssp.dwCurrentState == static_cast<DWORD>(desireStatus);

  if (!success && handle)
  {
     auto start = std::chrono::high_resolution_clock::now();
     auto waitTime = GetWaitTime(ssp.dwWaitHint);

     while (ssp.dwCurrentState != static_cast<DWORD>(desireStatus))
     {
        std::this_thread::sleep_for(waitTime);

        auto bytesNeeded = DWORD{ 0 };

        if (!::QueryServiceStatusEx(
           handle,
           SC_STATUS_PROCESS_INFO,
           reinterpret_cast<LPBYTE>(&ssp),
           sizeof(ssp),
           &bytesNeeded))
           break;

        if (ssp.dwCurrentState == static_cast<DWORD>(desireStatus))
        {
           success = true;
           break;
        }

        if (std::chrono::high_resolution_clock::now() - start > timeout)
           break;
     }
  }

  return success;
}      

Deleting the service

To delete a service we must call the DeleteService function. This function however only marks the service for deletion from the service control manager database. The service is deleted only when all open handles to the service have been closed and the service is not running. If the service is running at the time of the call, the database entry is removed when the system is restarted.

bool Delete()
{
  auto success = false;

  if (srvHandle)
  {
     success = ::DeleteService(srvHandle) != 0;

     if (success)
        srvHandle = nullptr;
  }

  return success;
}

ServiceConfig class

The ServiceConfig class represents the configuration parameters of a service. It enables you to read and update some of these configuration parameters and it can be extended if needed to include additional parameters. It is available in the header ServiceConfig.h.

The parameters that are automatically read are: the service type, the startup type, the error control for startup failure, the path name, the load ordering group name, the tag ID, the dependent services, the start name, the display name and the description.

The parameters that can be modified in the current implementation are the start type, the error control and the description. For other parameters the class must be extended with appropriate methods.

An instance of the class can be created using the static method ServiceConfig::Create that takes a handle to an existing service. After creating an object this function called its Refresh() method to read the configuration parameters.

The Refresh() method does the following:

  • Calls QueryServiceConfig to retrieve the configuration parameters for the referred service.
  • Calls QueryServiceConfig2 to retrieve optional configuration parameters for the referred service, but in this implementation it is only the description of the service.

To modify the configuration parameters several methods are available:

  • ChangeStartType() changes the service start type (such as auto, demand, disabled, etc.) and ChangeStartErrorControl() changes the service error control on startup failure. These methods both use ChangeServiceConfig to change the configuration parameters. Notice that when calling this function you must specify SERVICE_NO_CHANGE for service type, start type, and error control if the current values of these parameters are not supposed to be changed.
  • ChangeDescription() changes the description of the service. This method calls ChangeServiceConfig2 for changing the optional configuration parameters, in this case the description.
class ServiceConfig
{
   SC_HANDLE srvHandle;
   ServiceType type;
   ServiceStartType startType;
   ServiceErrorControl errorControl;
   ServiceString pathName;
   ServiceString loadOrderGroup;
   DWORD tagId;
   std::vector<ServiceString> dependencies;
   ServiceString startName;
   ServiceString displayName;
   ServiceString description;

public:
   ServiceType                      GetType() const               { return type; }
   ServiceStartType                 GetStartType() const          { return startType; }
   ServiceErrorControl              GetErrorControl() const       { return errorControl; }
   ServiceString                    GetBinaryPathName() const     { return pathName; }
   ServiceString                    GetLoadOrderingGroup() const  { return loadOrderGroup; }
   DWORD                            GetTagId() const              { return tagId; }
   std::vector<ServiceString> const GetDependencies() const       { return dependencies; }
   ServiceString                    GetStartName() const          { return startName; }
   ServiceString                    GetDisplayName() const        { return displayName; }
   ServiceString                    GetDescription() const        { return description; }

public:
   static ServiceConfig Create(SC_HANDLE const handle)
   {
      auto config = ServiceConfig{};
      config.srvHandle = handle;

      config.Refresh();

      return config;
   }

   bool ChangeStartType(ServiceStartType const type)
   {
      auto result = false;

      if (ChangeServiceConfig(
         srvHandle,
         SERVICE_NO_CHANGE,
         static_cast<DWORD>(type),
         SERVICE_NO_CHANGE,
         nullptr,
         nullptr,
         nullptr,
         nullptr,
         nullptr,
         nullptr,
         nullptr))
      {
         startType = type;
         result = true;
      }

      return result;
   }

   bool ChangeStartErrorControl(ServiceErrorControl const control)
   {
      auto result = false;

      if (ChangeServiceConfig(
         srvHandle,
         SERVICE_NO_CHANGE,
         SERVICE_NO_CHANGE,
         static_cast<DWORD>(control),         
         nullptr,
         nullptr,
         nullptr,
         nullptr,
         nullptr,
         nullptr,
         nullptr))
      {
         errorControl = control;
         result = true;
      }

      return result;
   }

   bool ChangeDescription(ServiceString const newDescription)
   {
      auto result = false;

      auto sd = SERVICE_DESCRIPTION {};
      sd.lpDescription = const_cast<LPTSTR>(newDescription.c_str());

      if (ChangeServiceConfig2(srvHandle, SERVICE_CONFIG_DESCRIPTION, &sd))
      {
         description = newDescription;
         result = true;
      }

      return result;
   }

   void Refresh()
   {
      auto bytesNeeded = DWORD{ 0 };

      if (!QueryServiceConfig(
         srvHandle,
         nullptr,
         0,
         &bytesNeeded))
      {
         if (ERROR_INSUFFICIENT_BUFFER == ::GetLastError())
         {
            std::vector<unsigned char> buffer(bytesNeeded, 0);

            auto lpsc = reinterpret_cast<LPQUERY_SERVICE_CONFIG>(buffer.data());
            if (QueryServiceConfig(
               srvHandle,
               lpsc,
               bytesNeeded,
               &bytesNeeded))
            {
               type = (ServiceType)lpsc->dwServiceType;
               startType = (ServiceStartType)lpsc->dwStartType;
               errorControl = (ServiceErrorControl)lpsc->dwErrorControl;
               pathName = lpsc->lpBinaryPathName;
               loadOrderGroup = lpsc->lpLoadOrderGroup;
               tagId = lpsc->dwTagId;
               dependencies = SplitDoubleNullTerminatedString(lpsc->lpDependencies);
               startName = lpsc->lpServiceStartName;
               displayName = lpsc->lpDisplayName;
            }
         }
      }

      bytesNeeded = 0;
      if (!QueryServiceConfig2(
         srvHandle,
         SERVICE_CONFIG_DESCRIPTION,
         nullptr,
         0,
         &bytesNeeded))
      {
         if (ERROR_INSUFFICIENT_BUFFER == ::GetLastError())
         {
            std::vector<unsigned char> buffer(bytesNeeded, 0);

            if (QueryServiceConfig2(
               srvHandle,
               SERVICE_CONFIG_DESCRIPTION,
               reinterpret_cast<LPBYTE>(buffer.data()),
               bytesNeeded,
               &bytesNeeded))
            {
               auto lpsd = reinterpret_cast<LPSERVICE_DESCRIPTION>(buffer.data());

               description = lpsd->lpDescription;
            }
         }
      }
   }

private:
   static std::vector<ServiceString> SplitDoubleNullTerminatedString(LPCTSTR text)
   {
      std::vector<ServiceString> texts;
      LPCTSTR ptr = text;
      do 
      {
         texts.push_back(ptr);
         ptr += texts.back().size() + 1;
      } while (*ptr != _T('\0'));

      return texts;
   }
};

To retrieve the configuration parameters of a service you should use the GetServiceConfig() method of the ServiceController class. It returns an instance of the ServiceConfig class.

ServiceConfig GetServiceConfig()
{
  return ServiceConfig::Create(srvHandle);
}

Notice: if you explicitly call the ServiceConfig::Create() method you must make sure the SC_HANDLE is properly closed when you no longer need to interact with the service.

ServiceEnumerator class

The ServiceEnumerator class is basically a small utility class for enumerating existing services. It contains a single static method called EnumerateServices that takes several parameters for the service type and state, the machine and service control database name and the load-order group name. With the default values for these parameters the function enumerates all services, regardless of their type and state, on the local machine.

To enumerate the services this function:

  • Calls OpenSCManager with SC_MANAGER_ENUMERATE_SERVICE for desired access.
  • Calls EnumServicesStatusEx with SC_ENUM_PROCESS_INFO for info level to retrieve the name and service status information for the services. The function is called twice, first with a null buffer for the output data to retrieve the actual size required for it and a second time with a properly sized buffer. However, this function has a limit of 256 KB on the output buffer size, which means it may not return all the services in a single call. As a result the function must be actually called in a loop until no more data is returned. To keep a track of where a new call has to start from, the function has a special parameter called lpResumeHandle. This is an in-out parameter. On input it specifies the starting point of the enumeration and must be set to 0 on the first call, on output it is set to 0 if the function succeeded, or the index of the next service entry if the function failed with ERROR_MORE_DATA.
struct ServiceStatusProcess
{
   ServiceString           ServiceName;
   ServiceString           DisplayName;
   SERVICE_STATUS_PROCESS  Status;
};

class ServiceEnumerator
{
public:
   static std::vector<ServiceStatusProcess> EnumerateServices(
      ServiceType const type = ServiceType::All, 
      ServiceState const state = ServiceState::All, 
      ServiceString const * machine = nullptr,
      ServiceString const * dbname = nullptr,
      ServiceString const * groupName = nullptr)
   {
      std::vector<ServiceStatusProcess> ssps;

      auto scHandle = ServiceHandle
      { 
         ::OpenSCManager(
            machine == nullptr ? nullptr : machine->c_str(), 
            dbname == nullptr ? nullptr : dbname->c_str(), 
            SC_MANAGER_ENUMERATE_SERVICE) 
      };
      auto bytesNeeded = DWORD{ 0 };
      auto servicesReturnedCount = DWORD{ 0 };
      auto resumeHandle = DWORD{ 0 };

      do
      {
         if (!EnumServicesStatusEx(
            scHandle,
            SC_ENUM_PROCESS_INFO,
            static_cast<DWORD>(type),
            static_cast<DWORD>(state),
            nullptr,
            0,
            &bytesNeeded,
            &servicesReturnedCount,
            &resumeHandle,
            groupName == nullptr ? nullptr : groupName->c_str()))
         {
            if (ERROR_MORE_DATA == ::GetLastError())
            {
               std::vector<unsigned char> buffer(bytesNeeded, 0);

               if (EnumServicesStatusEx(
                  scHandle,
                  SC_ENUM_PROCESS_INFO,
                  static_cast<DWORD>(type),
                  static_cast<DWORD>(state),
                  reinterpret_cast<LPBYTE>(buffer.data()),
                  bytesNeeded,
                  &bytesNeeded,
                  &servicesReturnedCount,
                  nullptr,
                  groupName == nullptr ? nullptr : groupName->c_str()))
               {
                  auto essp = reinterpret_cast<LPENUM_SERVICE_STATUS_PROCESS>(buffer.data());

                  for (auto i = DWORD{ 0 }; i < servicesReturnedCount; ++i)

                  {

                     auto ssp = ServiceStatusProcess{};

                     ssp.ServiceName = essp[i].lpServiceName;

                     ssp.DisplayName = essp[i].lpDisplayName;

                     ssp.Status = essp[i].ServiceStatusProcess;



                     ssps.push_back(ssp);

                  }

               }

               else break;

            }

            else break;

         }

      } while (resumeHandle != 0);



      return ssps;

   }

};

Conclusions

In this article we have build several C++ components for managing Windows services to simplify the use of the Windows service APIs in a C++ application. Along the way we have also seen what are the Windows service APIs and how they should be used. For a complete reference on the API see Service Functions in MSDN.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Marius Bancila
Architect Visma Software
Romania Romania
Marius Bancila is the author of Modern C++ Programming Cookbook and The Modern C++ Challenge. He used to be a Microsoft MVP for VC++ and later Visual Studio and Development Technologies for 11 years. He works as a system architect for Visma, a Norwegian-based company. He is mainly focused on building desktop applications with VC++ and VC#. He keeps a blog at http://www.mariusbancila.ro/blog, focused on Windows programming. He is the co-founder of codexpert.ro, a community for Romanian C++ programmers. You can follow Marius on Twitter at @mariusbancila.

You may also be interested in...

Pro

Comments and Discussions

 
QuestionService Assignment Operator Pin
Blake Miller20-Jun-16 9:10
memberBlake Miller20-Jun-16 9:10 
AnswerRe: Service Assignment Operator Pin
EvanB21-Jun-16 15:39
memberEvanB21-Jun-16 15:39 
AnswerRe: Service Assignment Operator Pin
EvanB21-Jun-16 15:55
memberEvanB21-Jun-16 15:55 
GeneralMy vote of 5 Pin
John Schroedl9-May-16 7:13
professionalJohn Schroedl9-May-16 7:13 
GeneralMy vote of 5 Pin
Alexander Navalov7-May-16 1:35
professionalAlexander Navalov7-May-16 1:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01 | 2.8.181119.1 | Last Updated 6 May 2016
Article Copyright 2016 by Marius Bancila
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid