Introduction
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:
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;
};
{
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);
}
print_status();
if (service.GetStatus() == ServiceStatus::Stopped)
{
service.Start();
print_status();
service.WaitForStatus(ServiceStatus::Running);
print_status();
}
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, };
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:
- OpenSCManager to establish a connection to the service control manager on the local computer and open the default service control manager database.
- OpenService to open the service; if this operation fails, the service control manager handle is closed.
- 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 immediately 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 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 built 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.
History
- 6th May, 2016: Initial version