Windows Services (2/2)

Introduction

On this page we will learn how to write a service in C++. This document is best read together with the official Microsoft documentation available from MSDN (http://msdn.microsoft.com/library/). The relevant documents are in MSDN Home - MSDN Library - Win32 and COM Development - System Services - DLLs, Processes and Threads - Services.

TODO: https://msdn.microsoft.com/en-us/library/windows/desktop/ms685141(v=vs.85).aspx

We will proceed in two steps. First we show how to install and remove a service and then we show how to write a service.

Installation and Removal

Services need to be installed and removed. We will start by writing code that adds and remove a service. The service we will install is not functional and starting it will result in an error. In the next example we will update the code so that the service can be started and stopped but for this example we are only interested in installation and removal. The full example is available from our Codeplex repository at Windows/Services/SingleFileInstallation1.

The application is a traditional console application. The code for the main function is in the Main.cpp file. Here is the part of the main function that takes the argument and calls the install or remove function accordingly.

File: Main.h
#ifndef _TUTORIALS_WINDOWS_SERVICES_DUMMYMINIMALSERVICE_MAIN_H_
#define _TUTORIALS_WINDOWS_SERVICES_DUMMYMINIMALSERVICE_MAIN_H_

// This includes winsvc.h which contains the definition
// for the service functions and structures
#include <windows.h>    

int __cdecl main(int argc, char **argv);
void PrintHelpMessage();

#endif
File: Main.cpp
#include "Main.h"
#include "ServiceSetup.h"
#include <stdio.h>

const wchar_t* serviceName = L"DummyMinimalService";

/*
This application is a regular console application that will install or
remove the "DummyMinimalService" service.

If it is called with the "-install" argument it will install the service and if 
it is called with the "-remove" argument it will remove the service.
*/
int __cdecl main(int argc, char **argv)
{
    // We parse the commandline
    if (argc == 1)
    {
        // There are not enough arguments on the command line
        PrintHelpMessage();
        return -1;
    }
    else if (argc == 2)
    {
        if (!strcmp("-install", argv[1]))
        {
            // Add the service
            TCHAR szPath[512];
            GetModuleFileName(NULL, szPath, 512);
            InstallService(serviceName, serviceName, szPath);
        }
        else if (!strcmp("-remove", argv[1]))
        {
            // Remove the service
            RemoveService(serviceName);
        }
        else
        {
            PrintHelpMessage();
            return -1;
        }
    }
    else
    {
        // There are too many arguments on the command line,
        // we should print the help menu.
        PrintHelpMessage();
        return -1;
    }

    return 0;
}

void PrintHelpMessage()
{
    printf("Usage:\r\n");
    printf("\t%s -install\r\n", serviceName);
    printf("\t%s -remove\r\n", serviceName);
}

The InstallService and RemoveService function are defined in the ServiceSetup.cpp file. We'll start by describing the InstallService function.\newline

void InstallService(LPCTSTR lpServiceName, LPCTSTR lpDisplayName, LPCTSTR lpBinaryPath) { // A handle for the service we want to add SC_HANDLE schService; // A handle for the Service Control Manager, we use the manager to add the service SC_HANDLE schSCManager; // Open the Service Control Manager schSCManager = OpenSCManager( NULL, // Connect to the Service Control Manager on the local machine NULL, // Open the SERVICES_ACTIVE_DATABASE database SC_MANAGER_CREATE_SERVICE // Request permission to create a new service ); if (schSCManager) { // We can create the service since we obtained a handle to the Service Control Manager schService = CreateService( schSCManager, // The handle to the Service Control Manager lpServiceName, // The name of the service lpDisplayName, // The display name of the service NULL, // No permissions needed SERVICE_WIN32_OWN_PROCESS, // The service runs in its own process SERVICE_DEMAND_START, // The service must be started manually SERVICE_ERROR_NORMAL, // The service can fail to start but the boot process // can continue lpBinaryPath, // The location of the binary NULL, NULL, NULL, NULL, // LocalSystem account NULL // No password ); if (schService) { // We must close the Service handle CloseServiceHandle(schService); } else { printf("Error while trying to create service\r\n"); } // We must close the Service Control Manager handle after use CloseServiceHandle(schSCManager); } else { printf("Error while trying to open service manager\r\n"); } }

The code is straightforward. We first get a handle to the Service Control Manager with the OpenSCManager function and request SC_MANAGER_CREATE_SERVICE permission. This will fail if the user executing the program hasn't the right to install service on the system. Once we have a handle to the Service Control Manager we use it to create our service with the CreateService function.

We give the following settings for our service : \begin{itemize} \item SERVICE\_WIN32\_OWN\_PROCESS : the service is the only one in its process. It is possible to have an executable that contains more than one service. \item SERVICE\_DEMAND\_START : the service will not be started automatically by the system. It will have to be started on demand. In our case we don't want to start our service anyway since it is a dummy service that doesn't work. \item SERVICE\_ERROR\_NORMAL : this determines the behaviour if the service fails to start. In our case the failure to start has no impact on the rest of the system so we simply choose the normal mode. \item For the location of the binary we have actually given the location of DummyMinimalService.exe. This may seem a strange choice as this is the installation and removal program. In fact it is convenient to combine the service and its installation and removal code in the same executable. This is what we will show how to do in the next example. \item We have specified NULL for the name of the account under which the service should run. This means that the default LocalSystem account will be used. Note that this account has extensive privileges and it may not be a good idea to run your service with such privileges. \end{itemize} Now we can execute the command DummyMinimalService.exe -install to install the service. After we have installed our dummy service we can open the Services interface in the Control Panel and check that the DummyMinimalService is indeed listed there. The settings show that the DummyMinimalService is not started and that the startup mode is manual. Attempting to start the service returns an error as expected since we haven't implemented the required elements to make our service functional.\newline We can also look in the registry under the\newline HKEY\_LOCAL\_MACHINE$\backslash$SYSTEM$\backslash$CurrentControlSet$\backslash$Services key. A DummyMinimalService key is now defined there. The values of the keys correspond to the arguments of the CreateService function. Figure 2 shows what the keys look like, in particular the ImagePath key indicates where the executable that implements the service is located. This figure clearly shows that there is nothing extraordinary about the Services database, it's just a list of entries in the registry that point to executables stored on the filesystem. Installing a service isn't much more than putting an executable somewhere on the filesystem and writing new values in the registry.\newline \begin{center} \epsfig{file=Registry.eps, width=0.95\linewidth}\newline \noindent Figure 2: Registry key for DummyMinimalService \end{center} Now that our service is installed we can remove it using the DummyMinimalService.exe -remove command. This will call the RemoveService function. As with the InstallService function we first get a handle to the service manager. Before we remove the service with the DeleteService function, we check if the service is running or not. Deleting a service that is running won't fail: it will mark the service for deletion and as soon as the service is stopped it will be actually deleted. However in this example we decided to not delete the service is running. Instead we ask the user to stop the service and try to remove it again. After removing the service you can check that it has disappeared from the registry and the Services interface in the Control Panel. The code for the RemoveService is reproduced below.\newline
#ifndef _TUTORIALS_WINDOWS_SERVICES_DUMMYMINIMALSERVICE_SERVICESETUP_H_
#define _TUTORIALS_WINDOWS_SERVICES_DUMMYMINIMALSERVICE_SERVICESETUP_H_

#include <windows.h>

void InstallService(LPCTSTR lpServiceName, LPCTSTR lpDisplayName, LPCTSTR lpBinaryPath);
void RemoveService(LPCTSTR lpServiceName);

#endif
#include "ServiceSetup.h"
#include <stdio.h>

void InstallService(LPCTSTR lpServiceName, LPCTSTR lpDisplayName, LPCTSTR lpBinaryPath)
{
    // A handle for the service we want to add
    SC_HANDLE schService;
    // A handle for the Service Control Manager, we use the manager to add the service
    SC_HANDLE schSCManager;

    // Open the Service Control Manager
    schSCManager = OpenSCManager(
        NULL,    // Connect to the Service Control Manager on the local machine
        NULL,    // Open the SERVICES_ACTIVE_DATABASE database
        SC_MANAGER_CREATE_SERVICE    // Request permission to create a new service
        );

    if (schSCManager)
    {
        // We can create the service since we obtained a handle to the Service Control Manager
        schService = CreateService(
            schSCManager,    // The handle to the Service Control Manager
            lpServiceName,    // The name of the service
            lpDisplayName,    // The display name of the service
            NULL,            // No permissions needed
            SERVICE_WIN32_OWN_PROCESS,    // The service runs in its own process
            SERVICE_DEMAND_START,        // The service must be started manually
            SERVICE_ERROR_NORMAL,        // The service can fail to start but the boot process
                                        // can continue
            lpBinaryPath,    // The location of the binary
            NULL,
            NULL,
            NULL,
            NULL,            // LocalSystem account
            NULL            // No password
            );

        if (schService)
        {
            // We must close the Service handle
            CloseServiceHandle(schService);
        }
        else
        {
            printf("Error while trying to create service\r\n");
        }

        // We must close the Service Control Manager handle after use
        CloseServiceHandle(schSCManager);
    }
    else
    {
        printf("Error while trying to open service manager\r\n");
    }
}

void RemoveService(LPCTSTR lpServiceName)
{
    // A handle for the service we want to remove
    SC_HANDLE schService;
    // A handle for the Service Control Manager, we use the manager to remove the service
    SC_HANDLE schSCManager;

    // Open the Service Control Manager
    schSCManager = OpenSCManager(
        NULL,    // Connect to the Service Control Manager on the local machine
        NULL,    // Open the SERVICES_ACTIVE_DATABASE database
        SC_MANAGER_CREATE_SERVICE    // Request permission to create a new service
        );

    if (schSCManager)
    {
        // We use the handle to the Service Control Manager to get a handle to
        // the Service
        schService = OpenService(
            schSCManager,    // The handle to the Service Control Manager
            lpServiceName,    // The name of the service
            SERVICE_QUERY_STATUS | DELETE    // Required permissions
            );

        if (schService)
        {
            // Check if service is stopped
            // If the service is not stopped it will only be deleted after it
            // is stopped. However in this example we require the user to stop the
            // service before deleting it so we'll just return an error if the
            // the service is not stopped
            SERVICE_STATUS_PROCESS status;
            DWORD sizeNeeded = 0;
            if (!QueryServiceStatusEx(schService, SC_STATUS_PROCESS_INFO, (LPBYTE)&status,
                sizeof(status), &sizeNeeded))
             {
                printf("Failed to get service status\r\n");
            }

            if (status.dwCurrentState == SERVICE_STOPPED)
            {
                // We delete the service
                if (!DeleteService(schService))
                {
                    printf("Failed to delete service\r\n");
                }
            }
            else
            {
                // The service is running, request user to stop it
                printf("Service must be stopped before deletion\r\n");
            }

            // We must close the Service handle
            CloseServiceHandle(schService);
        }

        // We must close the Service Control Manager handle after use
        CloseServiceHandle(schSCManager);
    }
    else
    {
        printf("Error while trying to open service manager\r\n");
    }
}
{\footnotesize \noindent The RemoveService function\\ \rule{0.9\linewidth}{0.1mm} \begin{verbatim} void RemoveService(LPCTSTR lpServiceName) { // A handle for the service we want to remove SC_HANDLE schService; // A handle for the Service Control Manager, we use the manager to remove the service SC_HANDLE schSCManager; // Open the Service Control Manager schSCManager = OpenSCManager( NULL, // Connect to the Service Control Manager on the local machine NULL, // Open the SERVICES_ACTIVE_DATABASE database SC_MANAGER_CREATE_SERVICE // Request permission to create a new service ); if (schSCManager) { // We use the handle to the Service Control Manager to get a handle to // the Service schService = OpenService( schSCManager, // The handle to the Service Control Manager lpServiceName, // The name of the service SERVICE_QUERY_STATUS | DELETE // Required permissions ); if (schService) { // Check if service is stopped // If the service is not stopped it will only be deleted after it // is stopped. However in this example we require the user to stop the // service before deleting it so we'll just return an error if the // the service is not stopped SERVICE_STATUS_PROCESS status; DWORD sizeNeeded = 0; if (!QueryServiceStatusEx(schService, SC_STATUS_PROCESS_INFO, (LPBYTE)&status, sizeof(status), &sizeNeeded)) { printf("Failed to get service status\r\n"); } if (status.dwCurrentState == SERVICE_STOPPED) { // We delete the service if (!DeleteService(schService)) { printf("Failed to delete service\r\n"); } } else { // The service is running, request user to stop it printf("Service must be stopped before deletion\r\n"); } // We must close the Service handle CloseServiceHandle(schService); } // We must close the Service Control Manager handle after use CloseServiceHandle(schSCManager); } else { printf("Error while trying to open service manager\r\n"); } } \end{verbatim}\rule{0.9\linewidth}{0.1mm}\\ \noindent The RemoveService function }\\ TODO : why create service permission for removing? \section{Writing the service control function} We will now expand the previous example to create a service that can be started and stopped. TODO : the first example. Create a console application project (empty project). Our example combines the service installation and removal into the same executable as the service application. We create a service that requires manual start first. TODO : If it is called with no arguments it will start the service. If it is called with the "-install" argument it will install the service and if it is called with the "-remove" argument it will remove the service. TODO : // The dispatchTable is a table that is passed to the StartServiceCtrlDispatcher. // It lists all the services supported by the process. Each service has a name and an // entry point. SERVICE\_TABLE\_ENTRY dispatchTable[] = { { TEXT(SZSERVICENAME), (LPSERVICE\_MAIN\_FUNCTION)ServiceMain }, { 0, 0 } }; TODO: how to debug a service. TODO: how to get information from the service (it can't have a UI remember), so file output or event manage? \end{document}

blog comments powered by Disqus

Copyright(c) 2006-2017 Xavier Leclercq | Privacy policy

Home
Contact Us
Search