text: #ifdef WIN32 
  
  #include "MiNiFiWindowsService.h"
  
  #include <Windows.h>
  #include <Strsafe.h>
  #include <tuple>
  #include <tlhelp32.h>
  
  #include "core/FlowConfiguration.h"
  
  //#define DEBUG_SERVICE
  
  #ifdef DEBUG_SERVICE
    #define LOG_INFO(...)       OutputDebug(__VA_ARGS__)
    #define LOG_ERROR(...)      OutputDebug(__VA_ARGS__)
    #define LOG_LASTERROR(str)  OutputDebug(str " lastError %x", GetLastError())
  #else
    #define LOG_INFO(...)       Log()->log_info(__VA_ARGS__)
    #define LOG_ERROR(...)      Log()->log_error(__VA_ARGS__)
    #define LOG_LASTERROR(str)  Log()->log_error(str " lastError %x", GetLastError())
  #endif
  
  #undef DEBUG_SERVICE
  
  // Implemented in MiNiFiMain.cpp
  void SignalExitProcess();
  
  static char* SERVICE_TERMINATION_EVENT_NAME = "Global\\MiNiFiServiceTermination";
  
  static void OutputDebug(const char* format, ...) {
    va_list args;
    va_start(args, format);
  
    char buf[256];
    sprintf_s(buf, _countof(buf), "%s: %s", SERVICE_NAME, format);
  
    char out[1024];
    StringCbVPrintfA(out, sizeof(out), buf, args);
  
    OutputDebugStringA(out);
  
    va_end(args);
  };
  
  void CheckRunAsService() {
    static const int WAIT_TIME_EXE_TERMINATION = 5000;
    static const int WAIT_TIME_EXE_RESTART = 60000;
  
    static SERVICE_STATUS s_serviceStatus;
    static SERVICE_STATUS_HANDLE s_statusHandle;
    static HANDLE s_hProcess;
    static HANDLE s_hEvent;
  
    static auto Log = []() {
      static std::shared_ptr<logging::Logger> s_logger = logging::LoggerConfiguration::getConfiguration().getLogger("service");
      return s_logger;
    };
  
    SERVICE_TABLE_ENTRY serviceTable[] =
    {
      {
        SERVICE_NAME,
        [](DWORD argc, LPTSTR *argv)
        {
          LOG_INFO("ServiceCtrlDispatcher");
  
          s_hEvent = CreateEvent(0, TRUE, FALSE, SERVICE_TERMINATION_EVENT_NAME);
          if (!s_hEvent) {
            LOG_LASTERROR("!CreateEvent");
            return;
          }
  
          s_statusHandle = RegisterServiceCtrlHandler(
            SERVICE_NAME,
            [](DWORD ctrlCode) {
              LOG_INFO("ServiceCtrlHandler ctrlCode %d", ctrlCode);
  
              if (SERVICE_CONTROL_STOP == ctrlCode) {
                LOG_INFO("ServiceCtrlHandler ctrlCode = SERVICE_CONTROL_STOP");
  
                // Set service status SERVICE_STOP_PENDING.
                s_serviceStatus.dwControlsAccepted = 0;
                s_serviceStatus.dwCurrentState = SERVICE_STOP_PENDING;
                s_serviceStatus.dwWin32ExitCode = 0;
  
                if (!SetServiceStatus(s_statusHandle, &s_serviceStatus)) {
                  LOG_LASTERROR("!SetServiceStatus SERVICE_STOP_PENDING");
                }
  
                bool exeTerminated = false;
  
                SetEvent(s_hEvent);
  
                LOG_INFO("Wait for exe termination");
                switch (auto res = WaitForSingleObject(s_hProcess, WAIT_TIME_EXE_TERMINATION)) {
                  case WAIT_OBJECT_0:
                    LOG_INFO("Exe terminated");
                    exeTerminated = true;
                    break;
  
                  case WAIT_TIMEOUT:
                    LOG_ERROR("WaitForSingleObject timeout %d", WAIT_TIME_EXE_TERMINATION);
                    break;
  
                  default:
                    LOG_ERROR("!WaitForSingleObject return %d", res);
                }
  
                if (!exeTerminated) {
                  LOG_INFO("TerminateProcess");
                  if (TerminateProcess(s_hProcess, 0)) {
                    s_serviceStatus.dwControlsAccepted = 0;
                    s_serviceStatus.dwCurrentState = SERVICE_STOPPED;
                    s_serviceStatus.dwWin32ExitCode = 0;
  
                    if (!SetServiceStatus(s_statusHandle, &s_serviceStatus)) {
                      LOG_LASTERROR("!SetServiceStatus SERVICE_STOPPED");
                    }
                  } else {
                    LOG_LASTERROR("!TerminateProcess");
                  }
                }
              }
            }
          );
  
          if (!s_statusHandle) {
            LOG_LASTERROR("!RegisterServiceCtrlHandler");
            return;
          }
  
          // Set service status SERVICE_START_PENDING.
          ZeroMemory(&s_serviceStatus, sizeof(s_serviceStatus));
          s_serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
          s_serviceStatus.dwControlsAccepted = 0;
          s_serviceStatus.dwCurrentState = SERVICE_START_PENDING;
          s_serviceStatus.dwWin32ExitCode = 0;
          s_serviceStatus.dwServiceSpecificExitCode = 0;
  
          if (!SetServiceStatus(s_statusHandle, &s_serviceStatus)) {
            LOG_LASTERROR("!SetServiceStatus SERVICE_START_PENDING");
            return;
          }
  
          // Set service status SERVICE_RUNNING.
          s_serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
          s_serviceStatus.dwCurrentState = SERVICE_RUNNING;
          s_serviceStatus.dwWin32ExitCode = 0;
  
          if (!SetServiceStatus(s_statusHandle, &s_serviceStatus)) {
            LOG_LASTERROR("!SetServiceStatus SERVICE_RUNNING");
  
            // Set service status SERVICE_START_PENDING.
            s_serviceStatus.dwControlsAccepted = 0;
            s_serviceStatus.dwCurrentState = SERVICE_STOPPED;
            s_serviceStatus.dwWin32ExitCode = GetLastError();
  
            if (!SetServiceStatus(s_statusHandle, &s_serviceStatus)) {
              LOG_LASTERROR("!SetServiceStatus SERVICE_STOPPED");
            }
  
            return;
          }
  
          char filePath[MAX_PATH];
          if (!GetModuleFileName(0, filePath, _countof(filePath))) {
            LOG_LASTERROR("!GetModuleFileName");
            return;
          }
  
          do {
            LOG_INFO("Start exe path %s", filePath);
  
            STARTUPINFO startupInfo{};
            startupInfo.cb = sizeof(startupInfo);
  
            PROCESS_INFORMATION processInformation{};
  
            if (!CreateProcess(filePath, 0, 0, 0, 0, FALSE, 0, 0, &startupInfo, &processInformation)) {
              LOG_LASTERROR("!CreateProcess");
              return;
            }
  
            s_hProcess = processInformation.hProcess;
  
            LOG_INFO("%s started", filePath);
  
            auto res = WaitForSingleObject(processInformation.hProcess, INFINITE);
            CloseHandle(processInformation.hProcess);
            CloseHandle(processInformation.hThread);
  
            if (WAIT_FAILED == res) {
              LOG_LASTERROR("!WaitForSingleObject hProcess");
            } else if (WAIT_OBJECT_0 != res) {
              LOG_ERROR("!WaitForSingleObject hProcess return %d", res);
            }
  
            LOG_INFO("Sleep %d sec before restarting exe", WAIT_TIME_EXE_RESTART/1000);
            res = WaitForSingleObject(s_hEvent, WAIT_TIME_EXE_RESTART);
  
            if (WAIT_OBJECT_0 == res) {
              LOG_INFO("Service was stopped, exe won't be restarted");
              break;
            }
  
            if (WAIT_FAILED == res) {
              LOG_LASTERROR("!WaitForSingleObject s_hEvent");
            } if (WAIT_TIMEOUT != res) {
              LOG_ERROR("!WaitForSingleObject s_hEvent return %d", res);
            }
          } while (true);
  
          s_serviceStatus.dwControlsAccepted = 0;
          s_serviceStatus.dwCurrentState = SERVICE_STOPPED;
          s_serviceStatus.dwWin32ExitCode = 0;
  
          if (!SetServiceStatus(s_statusHandle, &s_serviceStatus)) {
            LOG_LASTERROR("!SetServiceStatus SERVICE_STOPPED");
          }
        } 
      },
      {0, 0}
    };
  
    if (!StartServiceCtrlDispatcher(serviceTable)) {
      if (ERROR_FAILED_SERVICE_CONTROLLER_CONNECT == GetLastError()) {
        // Run this exe as console.
        return;
      }
  
      LOG_LASTERROR("!StartServiceCtrlDispatcher");
  
      ExitProcess(1);
    }
  
    LOG_INFO("Service exit");
  
    ExitProcess(0);
  }
  
  bool CreateServiceTerminationThread(std::shared_ptr<logging::Logger> logger) {
    HANDLE hEvent = CreateEvent(0, TRUE, FALSE, SERVICE_TERMINATION_EVENT_NAME);
    if (!hEvent) {
      logger->log_error("!CreateEvent lastError %x", GetLastError());
      return false;
    }
  
    if (GetLastError() != ERROR_ALREADY_EXISTS) {
      CloseHandle(hEvent);
      return true;
    }
  
    // Get hService and monitor it - if service is terminated, then terminate current exe, otherwise the exe becomes unmanageable when service is restarted.
    auto hService = [&logger]() -> HANDLE {
      auto hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
      if (INVALID_HANDLE_VALUE == hSnapShot) {
        logger->log_error("!CreateToolhelp32Snapshot lastError %x", GetLastError());
        return 0;
      }
  
      auto getProcessInfo = [&logger, &hSnapShot](DWORD processId, DWORD& parentProcessId, std::string& parentProcessName) {
        parentProcessId = 0;
        parentProcessName.clear();
  
        PROCESSENTRY32 procentry{};
        procentry.dwSize = sizeof(procentry);
  
        if (!Process32First(hSnapShot, &procentry)) {
          logger->log_error("!Process32First lastError %x", GetLastError());
          return;
        }
  
        do {
          if (processId == procentry.th32ProcessID) {
            parentProcessId = procentry.th32ParentProcessID;
            parentProcessName = procentry.szExeFile;
            return;
          }
        } while (Process32Next(hSnapShot, &procentry));
      };
  
      // Find current process info, which contains parentProcessId.
      DWORD parentProcessId{};
      std::string parentProcessName;
      getProcessInfo(GetCurrentProcessId(), parentProcessId, parentProcessName);
  
      // Find parent process info (the service which started current process), which contains service name.
      DWORD parentParentProcessId{};
      getProcessInfo(parentProcessId, parentParentProcessId, parentProcessName);
  
      CloseHandle(hSnapShot);
  
      // Just in case check that service name == current process name.
      char filePath[MAX_PATH];
      if (!GetModuleFileName(0, filePath, _countof(filePath))) {
        logger->log_error("!GetModuleFileName lastError %x", GetLastError());
        return 0;
      }
  
      const auto pSlash = strrchr(filePath, '\\');
      if (!pSlash) {
        logger->log_error("Invalid filePath %s", filePath);
        return 0;
      }
      const std::string fileName = pSlash + 1;
  
      if (_stricmp(fileName.c_str(), parentProcessName.c_str())) {
        logger->log_error("Parent process %s != current process %s", parentProcessName.c_str(), fileName.c_str());
        return 0;
      }
  
      const auto hParentProcess = OpenProcess(SYNCHRONIZE, FALSE, parentProcessId);
      if (!hParentProcess) {
        logger->log_error("!OpenProcess lastError %x", GetLastError());
        return 0;
      }
  
      return hParentProcess;
    }();
    if (!hService)
      return false;
  
    using ThreadInfo = std::tuple<std::shared_ptr<logging::Logger>, HANDLE, HANDLE>;
    auto pThreadInfo = new ThreadInfo(logger, hEvent, hService);
  
    HANDLE hThread = (HANDLE)_beginthreadex(
      0, 0,
      [](void* pPar) {
        const auto pThreadInfo = static_cast<ThreadInfo*>(pPar);
        const auto logger = std::get<0>(*pThreadInfo);
        const auto hEvent = std::get<1>(*pThreadInfo);
        const auto hService = std::get<2>(*pThreadInfo);
        delete pThreadInfo;
  
        HANDLE arHandle[] = { hEvent, hService };
        switch (auto res = WaitForMultipleObjects(_countof(arHandle), arHandle, FALSE, INFINITE))
        {
          case WAIT_FAILED:
            logger->log_error("!WaitForSingleObject lastError %x", GetLastError());
          break;
  
          case WAIT_OBJECT_0:
            logger->log_info("Service event received");
          break;
  
          case WAIT_OBJECT_0 + 1:
            logger->log_info("Service is terminated");
          break;
  
          default:
            logger->log_info("WaitForMultipleObjects return %d", res);
        }
  
        SignalExitProcess();
  
        return 0U;
      },
      pThreadInfo,
      0, 0);
    if (!hThread) {
      logger->log_error("!_beginthreadex lastError %x", GetLastError());
  
      delete pThreadInfo;
  
      return false;
    }
  
    return true;
  }
  
  #endif
  