⬆️ ⬇️

Implementation of services in MSWin

According to the working need sometimes have to write system services for Microsoft Windows.



On Habré there is already an article Creating your own Windows Service , but in my opinion, this article is nothing more than a brief overview that can be found in MSDN. It does not consider, for example, possible options for the behavior of the service in case of an error, or writing to the message logs.

I will try, using the experience of writing such applications, to present the maximum possible amount of information.



We will not consider options when the service functions as a device driver (and, accordingly, is located in the file with the .sys extension), this is too specialized.

')

Typically, a service is simply an application run by SCM (Service Control Manager) and controlled by it. For a programmer, this means that to start you need to provide some additional action.

In addition, the correct service should not directly interact with the user. What does this mean in practice? By launching any application - we can see either the console window or the GUI of this application. System services running under Windows 2000 / Windows XP / Windows 2003 Server operating systems are also allowed; this service is called interactive . But, at the same time, Starting from Windows Vista - a ban is imposed on interactive services. It turns out that, developing a service, it is more correct to abandon interactivity.

At the same time, MSDN offers the following options for user interaction:

1) Displaying the dialog with the WTSSendMessage () function

the behavior is very flexible, you can display a dialog and wait for the user's reaction, you can simply inform the user and continue the application, etc., but the service needs to either work with terminal sessions or rely on the fact that the user active in the current terminal session has the necessary knowledge and rights to respond to a pop-up window (many people who are ignorant of IT are afraid of this behavior, this should also be taken into account when designing)

2) Creating a separate application in the current user session using CreateProcessAsUser ()

the most correct option, if only because the user is given a simple tool for interacting with the service, but it must be understood that the interaction must be organized either through sockets (the most common option), or through IPC, COM, and others, which somewhat complicates the common code but simplifies the service itself.

It is also possible not to create a separate executable file, but to use the same service file, launched with a specific key, which somewhat simplifies the distribution and updating of the program.

3) Calling MessageBox () with the MB_SERVICE_NOTIFICATION parameter

the worst option, the service (or the thread that caused it) will “hang” while the user responds.



When installing the service in the system or deleting, you should also take into account that only the Administrator, or a user equal to him, has the right to interact with SCM.



In addition, the working folder of the service is System32 of your system, and there may be no rights to write to the folder in which the service is installed, so you should use the system’s Eventlog for logging (see below).



Before giving the code, I note that when developing services for NT systems, I personally prefer to write the full names of functions without trusting them to be expanded by macros, so instead of OpenSCManager I usually write OpenSCManagerW .



And so, first things first.



The entry point into the service is either the console function main () or WinMain (). Since ideologically it is not a console project, I use the second function.

In the function itself, you should make a command line parser and provide for the handling of install , uninstall , run and stop commands , you can do it as you like. Running the executable file without parameters will be considered the launch of the service via SCM. This is the most correct behavior. If someone tries to run a file without keys, the service simply does not start, and by processing the necessary commands, you can easily control the behavior of your service without remembering the console commands and parameters.

Additionally, you will need a couple of functions to check whether the service is running and whether it is installed in the system:



Copy Source | Copy HTML<br/> /* <br/> */ <br/> bool is_install()<br/>{<br/> bool Result = false ;<br/> SC_HANDLE l_srv_manager = NULL;<br/> SC_HANDLE l_srv_process = NULL;<br/> <br/> l_srv_manager = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);<br/> if (l_srv_manager)<br/> {<br/> l_srv_process = OpenServiceW(<br/> l_srv_manager,<br/> g_str_srv_name.c_str(),<br/> SERVICE_ALL_ACCESS<br/> );<br/> if (l_srv_process)<br/> {<br/> Result = true ;<br/> CloseServiceHandle(l_srv_process);<br/> }<br/> CloseServiceHandle(l_srv_manager);<br/> }<br/> else <br/> /* <br/> */ <br/> MessageBoxW(<br/> 0 ,<br/> L "Cannot connect to Service Manager\ntry run with Administrator right !" ,<br/> L "ERROR" ,<br/> MB_ICONERROR<br/> );<br/> return Result;<br/>}<br/> //------------------------------------------------------------------------------ <br/>


Copy Source | Copy HTML<br/> /* <br/> */ <br/> bool is_run()<br/>{<br/> if (!is_install())<br/> return false ;<br/> <br/> bool Result = false ;<br/> SC_HANDLE l_srv_manager = NULL;<br/> SC_HANDLE l_srv_process = NULL;<br/> <br/> l_srv_manager = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);<br/> if (l_srv_manager)<br/> {<br/> l_srv_process = OpenServiceW(<br/> l_srv_manager,<br/> g_str_srv_name.c_str(),<br/> SERVICE_ALL_ACCESS<br/> );<br/> if (l_srv_process)<br/> {<br/> SERVICE_STATUS_PROCESS l_srv_status;<br/> DWORD l_dw_temp;<br/> if (QueryServiceStatusEx(<br/> l_srv_process,<br/> SC_STATUS_PROCESS_INFO,<br/> reinterpret_cast< LPBYTE > (&l_srv_status),<br/> sizeof (SERVICE_STATUS_PROCESS),<br/> &l_dw_temp<br/> ) == TRUE<br/> )<br/> {<br/> if (l_srv_status.dwCurrentState == SERVICE_RUNNING)<br/> Result = true ;<br/> }<br/> CloseServiceHandle(l_srv_process);<br/> }<br/> CloseServiceHandle(l_srv_manager);<br/> }<br/> else <br/> /* <br/> */ <br/> MessageBoxW(<br/> 0 ,<br/> L "Cannot connect to Service Manager\ntry run with Administrator right !" ,<br/> L "ERROR" ,<br/> MB_ICONERROR<br/> );<br/> return Result;<br/>}<br/> //------------------------------------------------------------------------------ <br/>


hereinafter g_str_srv_name is the std :: wstring string containing the name of the service.



Copy Source | Copy HTML<br/> /* <br/> */ <br/> int srv_install()<br/>{<br/> if (is_install())<br/> return srv_start();<br/> <br/> int Result = - 1 ;<br/> SC_HANDLE l_srv_manager = NULL;<br/> SC_HANDLE l_srv_process = NULL;<br/> <br/> l_srv_manager = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);<br/> if (l_srv_manager)<br/> {<br/> std::wstring l_wstr = get_path();<br/> <br/> l_srv_process = CreateServiceW(<br/> l_srv_manager,<br/> g_str_srv_name.c_str(),<br/> g_str_srv_name.c_str(),<br/> SERVICE_ALL_ACCESS,<br/> SERVICE_WIN32_OWN_PROCESS,<br/> SERVICE_AUTO_START,<br/> SERVICE_ERROR_NORMAL,<br/> (l_wstr + get_name()).c_str(),<br/> NULL,<br/> NULL,<br/> NULL,<br/> NULL,<br/> NULL<br/> );<br/> if (l_srv_process)<br/> {<br/> registry_editor_t l_reg_edit;<br/> <br/> Result = 0 ;<br/> <br/> HKEY l_kservice = NULL;<br/> <br/> SERVICE_DESCRIPTIONW l_srv_descr;<br/> SERVICE_FAILURE_ACTIONSW l_srv_action_f;<br/> SC_ACTION l_srv_action [] =<br/> {<br/> { SC_ACTION_RESTART, 500 },<br/> { SC_ACTION_RESTART, 500 },<br/> { SC_ACTION_RESTART, 500 }<br/> };<br/> <br/> l_srv_descr.lpDescription = const_cast< wchar_t *> (g_str_srv_descr.c_str());<br/> l_srv_action_f.dwResetPeriod = 120 ;<br/> l_srv_action_f.lpRebootMsg = NULL;<br/> l_srv_action_f.lpCommand = NULL;<br/> l_srv_action_f.cActions = 3 ;<br/> l_srv_action_f.lpsaActions = l_srv_action ;<br/> <br/> ChangeServiceConfig2W(<br/> l_srv_process,<br/> SERVICE_CONFIG_DESCRIPTION,<br/> &l_srv_descr<br/> );<br/> ChangeServiceConfig2W(<br/> l_srv_process,<br/> SERVICE_CONFIG_FAILURE_ACTIONS,<br/> &l_srv_action_f<br/> );<br/> <br/> // HKEY_LOCAL_MACHINE\Software\MyService <br/> l_kservice = l_reg_edit.create(<br/> registry_editor_t::root::local_mashine,<br/> L "Software\\MyService" <br/> );<br/> if (l_kservice)<br/> {<br/> l_reg_edit.write(l_kservice, L "Path" , l_wstr);<br/> l_reg_edit.close(l_kservice);<br/> }<br/> <br/> // HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\* <br/> DWORD l_support = EVENTLOG_ERROR_TYPE |<br/> EVENTLOG_WARNING_TYPE |<br/> EVENTLOG_INFORMATION_TYPE;<br/> HKEY l_kevent_log = l_reg_edit.create(<br/> registry_editor_t::root::local_mashine,<br/> (L "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\MySvrMessages" ).c_str()<br/> );<br/> if (l_kevent_log)<br/> {<br/> l_reg_edit.write(<br/> l_kevent_log,<br/> L "EventMessageFile" ,<br/> l_wstr + L "messages.dll" <br/> );<br/> l_reg_edit.write(<br/> l_kevent_log,<br/> L "TypesSupported" ,<br/> l_support<br/> );<br/> l_reg_edit.write(<br/> l_kevent_log,<br/> L "CategoryMessageFile" ,<br/> l_wstr + L "messages.dll" <br/> );<br/> l_support = 3 ;<br/> l_reg_edit.write(<br/> l_kevent_log,<br/> L "CategoryCount" ,<br/> l_support<br/> );<br/> }<br/> <br/> // start service <br/> StartServiceW(l_srv_process, 0 , NULL);<br/> <br/> CloseServiceHandle(l_srv_process);<br/> }<br/> else <br/> {<br/> /* <br/> */ <br/> std::wstring l_wstr = L "Installation service failed\n" ;<br/> l_wstr += get_error();<br/> MessageBoxW( 0 , l_wstr.c_str(), L "ERROR" , MB_ICONERROR);<br/> }<br/> <br/> CloseServiceHandle(l_srv_manager);<br/> }<br/> else <br/> /* <br/> */ <br/> MessageBoxW(<br/> 0 ,<br/> L "Cannot connect to Service Manager\ntry run with Administrator right !" ,<br/> L "ERROR" ,<br/> MB_ICONERROR<br/> );<br/> return Result;<br/>}<br/> //------------------------------------------------------------------------------ <br/>


Let's look at what has been done here:

1) get_path () / get_name () are functions for getting the name of the folder where the executable file of the service and the name of this executable file are located. Self-written, so the implementation is arbitrary.

2) the value of the SERVICE_WIN32_OWN_PROCESS flag is suitable for the vast majority of services. The exception is not considered device drivers and file systems. The second possible, within the scope of this article, the SERVICE_WIN32_SHARE_PROCESS flag means that the service process has a common memory area with other services, this is required if you have several services in one executable file. The total memory area will allow you to save memory, and simplify the interaction of services, but it will make the debugging much more difficult, so I advise you to use this feature only if you clearly understand why you need it and what else - well, no matter how.

3) SERVICE_AUTO_START - the service will start automatically when the system starts. Please note that the moment of starting the service in this case is the initialization of system services, i.e. before the user logs in. This point should be considered if the service is working on the server. Other possible options within the framework of the tooltip, SERVICE_DEMAND_START - start manually and SERVICE_DISABLED - do not start at all.

4) The SERVICE_FAILURE_ACTIONSW structure describes the behavior of SCM in case of an emergency termination of a service.

* dwResetPeriod - period for resetting data on the service behavior, specified in seconds. The INFINITE value indicates that the data will not be overwritten.

* cActions - the number of elements in the array describing the SCM reaction to the emergency behavior of the service, usually this number is 3 (three). According to the points “First failure”, “Second failure” and “Subsequent failures”:



Accordingly, filling the l_srv_action [] array with the fields {SC_ACTION_RESTART, 500} means that the service will automatically restart every 500 milliseconds after a crash.



All information filled in these fields will be saved in the registry branch

HKEY_LOCAL_MACHINE \ System \ CurrentControlSet \ Services \ MyService

In addition, you should add message handling services, for this we add a special branch to the registry HKEY_LOCAL_MACHINE \ System \ CurrentControlSet \ Services \ EventLog . In this thread, you must specify a file in the resources of which the MMC can find a description for logged errors. In principle, you can add these resources to the executable file, but it is easier to create a separate dynamic library (in my case, messages.dll). How to create this library - I will write below.

Well, that's all with the installation.

The removal, start and stop of the service can be overlooked in the article given in the title, or there is nothing special in the code of these procedures.



As a result, with the installation, removal and other commands, we sort of figured out, so you can continue on.

With the entry point into the service is well explained in the previous article, I will add just a few clarifications:

Call StartServiceCtrlDispatcherW () Your service should no later than 30 seconds after entering WinMain (), otherwise the SCM will decide that the process being started is “frozen” and will unload it.



Further, the most correct behavior of the service will first be the declaration of status as “starting”, and then changing it to “started”, however, nothing prevents to set the status of the service as “started” right away.

To do this in ServiceMain, you must perform the following steps:

Copy Source | Copy HTML<br/> /* <br/> */ <br/> void service_t::main()<br/>{<br/> m_handle = RegisterServiceCtrlHandlerExW(<br/> g_str_srv_name.c_str(),<br/> &service_handler,<br/> NULL<br/> );<br/> if (!m_handle)<br/> {<br/> system_logger_t::instance()->trace_info(<br/> L "register service handler failed" <br/> );<br/> _set_stop();<br/> return ;<br/> }<br/> // <br/> m_status.dwCurrentState = SERVICE_START_PENDING;<br/> m_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;<br/> m_status.dwServiceSpecificExitCode = ERROR_NOT_READY;<br/> m_status.dwWaitHint = 5000 ;<br/> if (SetServiceStatus(m_handle, &m_status) == FALSE)<br/> {<br/> std::wstring l_error = get_error();<br/> system_logger_t::instance()->trace_info(l_error);<br/> _set_stop();<br/> return ;<br/> }<br/> // <br/> SetUnhandledExceptionFilter(exception_filter);<br/> SetErrorMode(SEM_FAILCRITICALERRORS);<br/> // init <br/> // ---- <br/> m_status.dwControlsAccepted = SERVICE_ACCEPT_STOP |<br/> SERVICE_ACCEPT_SESSIONCHANGE |<br/> SERVICE_ACCEPT_SHUTDOWN;<br/> m_status.dwWin32ExitCode = NO_ERROR;<br/> m_status.dwCurrentState = SERVICE_RUNNING;<br/> m_status.dwWaitHint = 0 ;<br/> if (SetServiceStatus(m_handle, &m_status) == FALSE)<br/> {<br/> std::wstring l_error = get_error();<br/> trace_msg(l_error);<br/> system_logger_t::instance()->trace_info(l_error);<br/> return ;<br/> }<br/> m_run = true ;<br/> system_logger_t::instance()->trace_info(<br/> L "service start success" <br/> );<br/> while ( true )<br/> {<br/> if (!m_run)<br/> {<br/> break ;<br/> }<br/> else <br/> Sleep( 10 );<br/> }<br/> _set_stop();<br/>}<br/> //------------------------------------------------------------------------------ <br/> /* <br/> */ <br/> void service_t::_set_stop()<br/>{<br/> m_status.dwCurrentState = SERVICE_STOPPED;<br/> SetServiceStatus(m_handle, &m_status);<br/> system_logger_t::instance()->trace_info(<br/> L "MyService stop" <br/> );<br/>}<br/> //------------------------------------------------------------------------------ <br/>


Here the service informs the SCM that it takes 5 seconds for it to initialize, which means that before the 5 seconds have elapsed it is necessary to inform the SCM of the completion of the initialization. Otherwise, the service will be unloaded as “hung”.

In the above code, it is assumed that during the initialization of the service class, the structure fields were filled in as follows:

memset(&m_status, 0, sizeof(SERVICE_STATUS));

m_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;


In this case, the value of the dwServiceType field must correspond to the type of service we specified during installation, otherwise SCM will regard this as an incorrect behavior and will not allow the service to start.

Hiding error messages and intercepting exceptions will allow you to independently handle errors in the program code, as well as, at times, allow you to avoid suspending the service. Additionally, it will save the user from having to send error reports to developers from Microsoft.

Naturally, it will be inserted into the body of the main loop of the Sleep service (10) in order to allow other programs to function normally (this is beyond the scope of this article).

My message handler looks like this:

Copy Source | Copy HTML<br/> /* <br/> */ <br/>DWORD service_t::handler(DWORD dwControl,<br/> DWORD dwEnterType,<br/> LPVOID lpEventData,<br/> LPVOID lpContext)<br/>{<br/> switch (dwControl)<br/> {<br/> case SERVICE_CONTROL_STOP:<br/> case SERVICE_CONTROL_SHUTDOWN:<br/> system_logger_t::instance()->trace_info(<br/> L "service try to stop" <br/> );<br/> m_status.dwCurrentState = SERVICE_STOP_PENDING;<br/> m_status.dwWaitHint = 10000 ;<br/> SetServiceStatus(m_handle, &m_status);<br/> m_run = false ;<br/> break ;<br/> case SERVICE_CONTROL_SESSIONCHANGE:<br/> break ;<br/> default :<br/> SetServiceStatus(m_handle, &m_status);<br/> }<br/> return NO_ERROR;<br/>}<br/> //------------------------------------------------------------------------------ <br/>




Actually, the last thing that is lit up on the Internet is the least, how to correctly add a message from the service to the system log. How to register a library in the registry is already written above, now about how to create this library.

1) create a file with the .mc extension of the following content:

Copy Source | Copy HTML<br/>MessageIdTypedef = DWORD<br/>SeverityNames =<br/> (<br/> Success = 0x0 : STATUS_SEVERITY_SUCCESS<br/> Informational = 0x1 : STATUS_SEVERITY_INFORMATIONAL<br/> Warning = 0x2 : STATUS_SEVERITY_WARNING<br/> Error = 0x3 : STATUS_SEVERITY_ERROR<br/> )<br/> <br/>FacilityNames =<br/> (<br/> System = 0x0 : FACILITY_SYSTEM<br/> Runtime = 0x2 : FACILITY_RUNTIME<br/> Io = 0x3 : FACILITY_IO_ERROR_CODE<br/> )<br/> <br/>LanguageNames =<br/> (<br/> English = 0x409 : MSG00409<br/> )<br/> <br/> ;// messages definition<br/>MessageId = 0x1<br/>Severity = Success<br/>Facility = System<br/>SymbolicName = SRV_MSG_SYSTEM_SUCCESS<br/>Language = English<br/>Operation %1 success<br/>. <br/>


Four levels of message importance are reserved and they should not change, but the object index (FacilityNames) is arbitrary. Completion of the description - a point in the new line, it is not worth forgetting.

The MessageID must be unique for each described message type (for example, 1, 2, 3, etc.)

This file must be “compiled” into a resource file; for this, the MSVS package contains mc.exe, which creates .h and .rc files from .mc

The header file generated by the utility must be connected to the project, and the resource file must be collected in the library (or connected to the executable file).



rc -r messages.rc

link -dll -noentry -out:messages.dll messages.res




An interesting feature is that in order to view messages in the correct format, a restart of the Eventlog service is required. Otherwise, you will see something like this:

( 1 ) ( MySrvMessages ). , DLL . /AUXSOURCE= , - . : service starting.



A nice opportunity is that the message log service can also keep its own, and not add it to a common one for all applications. For this, when registering the message description library, you must specify not the HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Services \ EventLog \ Application branch, but HKEY_LOCAL_MACHINE \ SYSTEM \ CurrentControlSet \ Services \ EventLog \ MyService .

But there is an interesting nuance.

After creating a registry branch in MMC, the corresponding folder will appear in the “Events”, but in some cases, messages from this branch are not processed correctly even after Eventlog is restarted. In such “hard cases”, a computer restart is required, which can be problematic for the server. But, fortunately, it is extremely rare.



Well, that's all. It turned out a lot of text, a lot of code, but this, in my opinion, is the minimum that you need to know. The rest is easy to learn from the Internet and MSDN.

Source: https://habr.com/ru/post/91476/



All Articles