Search code examples
c++windows-servicesservicecontroller

Send command to service from C++


how can I send command to a Windows service from C++? Equivalent .NET code is:

ServiceController sc = new ServiceController("MyService");
sc.ExecuteCommand(255);

Solution

  • From native C++, you will need to:

    1. Open a handle to the service control manager,
    2. Use the service control manager to obtain a service handle for the service you want to control,
    3. Send a control code or codes to the service, and
    4. Close the handles opened in steps 1 and 2.

    For example, this code restarts the time synchronization service. First, I create a wrapper class for the service handles, to close them automatically when leaving the block.

    class CSC_HANDLE
    {
    public:
     CSC_HANDLE(SC_HANDLE h) : m_h(h) { }
     ~CSC_HANDLE() { ::CloseServiceHandle(m_h); }
     operator SC_HANDLE () { return m_h; }
    private:
     SC_HANDLE m_h;
    };
    

    Then, I open the service control manager (using OpenSCManager()) and the service I want to control. Note that the dwDesiredAccess parameter to OpenService() must include permissions for each control I want to send, or the relevant control functions will fail.

    BOOL RestartTimeService()
    {
        CSC_HANDLE hSCM(::OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, GENERIC_READ));
        if (NULL == hSCM) return FALSE;
    
        CSC_HANDLE hW32Time(::OpenService(hSCM, L"W32Time", SERVICE_START | SERVICE_STOP | SERVICE_QUERY_STATUS));
        if (NULL == hW32Time) return FALSE;
    

    To stop the service, I use ControlService() to send the SERVICE_CONTROL_STOP code, and then check the return value to make sure the command succeeded. If any error other than ERROR_SERVICE_NOT_ACTIVE is reported, I assume that starting the service is not going to succeed.

        SERVICE_STATUS ss = { 0 };
        ::SetLastError(0);
        BOOL success = ::ControlService(hW32Time, SERVICE_CONTROL_STOP, &ss);
        if (!success)
        {
            DWORD le = ::GetLastError();
            switch (le)
            {
            case ERROR_ACCESS_DENIED:
            case ERROR_DEPENDENT_SERVICES_RUNNING:
            case ERROR_INVALID_HANDLE:
            case ERROR_INVALID_PARAMETER:
            case ERROR_INVALID_SERVICE_CONTROL:
            case ERROR_SERVICE_CANNOT_ACCEPT_CTRL:
            case ERROR_SERVICE_REQUEST_TIMEOUT:
            case ERROR_SHUTDOWN_IN_PROGRESS:
                return FALSE;
    
            case ERROR_SERVICE_NOT_ACTIVE:
            default:
                break;
            }
        }
    

    After instructing the service to stop, I wait for the service manager to report that the service is in fact stopped. This code has two potential bugs, which you may wish to correct for production code:

    1. Sleep(1000) will suspend the message loop on this thread, so you should use another method to delay execution if this function will run on a UI thread. You can construct a suitable sleep-with-message-loop using MsgWaitForMultipleObjectsEx().
    2. The DWORD returned from GetTickCount() will wrap around to zero eventually; if it wraps around while this function is waiting, the wait may give up sooner than I intended.

      DWORD waitstart(::GetTickCount());
      while (true)
      {
          ZeroMemory(&ss, sizeof(ss));
          ::QueryServiceStatus(hW32Time, &ss);
          if (SERVICE_STOPPED == ss.dwCurrentState) break;
          ::Sleep(1000);
          DWORD tick(::GetTickCount());
          if ((tick < waitstart) || (tick > (waitstart + 30000))) return FALSE;
      }
      

    Finally, knowing that the service is in a stopped state, I call StartService() run it again.

        success = ::StartService(hW32Time, 0, NULL);
        if (!success) return FALSE;
    
        return TRUE;
    }