Служба c ++ не запускается после завершения работы Windows, а затем загружается

dev65 спросил: 28 марта 2018 в 04:30 в: c++

Я хотел сделать службу Windows на c ++, чтобы запускать мои программы в качестве администратора каждый раз, когда пользователь входит в систему без всплытия окна UAC. Это первый раз, когда я это делал. Я использовал проект здесь: https:// code .msdn.microsoft.com / windowsapps / CppWindowsService-cacf4948 / view / SourceCode

Я отредактировал строку 74 в CppWindowsService.cpp:

InstallService(
        SERVICE_NAME,               // Name of service
        SERVICE_DISPLAY_NAME,       // Name to display
        SERVICE_AUTO_START,         // Service start type
        SERVICE_DEPENDENCIES,       // Dependencies
        0,            // Service running account
        SERVICE_PASSWORD            // Password of the account
        );

и добавил некоторый код для рабочего потока в строке 101 SampleService.cpp, чтобы выглядеть следующим образом:

 void CSampleService::ServiceWorkerThread(void)
{
// Periodically check if the service is stopping.
PSID gpSidMIL_High;
ConvertStringSidToSidW(L"S-1-16-12288", &gpSidMIL_High);
DWORD userSessionID = WTSGetActiveConsoleSessionId();
HANDLE hToken, hToken2;if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken)) WriteEventLogEntry(L"OpenProcessToken failed error", EVENTLOG_ERROR_TYPE);
if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hToken2)) WriteEventLogEntry(L"DuplicateTokenEx error", EVENTLOG_ERROR_TYPE);if (!SetTokenInformation(hToken2, TokenSessionId, &userSessionID, sizeof(userSessionID))) WriteEventLogEntry(L"SetTokenInformation 1 error", EVENTLOG_ERROR_TYPE);DWORD dwUIAccess = 1;
if (!SetTokenInformation(hToken2, TokenUIAccess, &dwUIAccess, sizeof(dwUIAccess))) WriteEventLogEntry(L"SetTokenInformation 2 error", EVENTLOG_ERROR_TYPE);//Set "high" mandatory integrity level
TOKEN_MANDATORY_LABEL tml = { 0 };
tml.Label.Attributes = SE_GROUP_INTEGRITY;
tml.Label.Sid = gpSidMIL_High;if (!SetTokenInformation(hToken2, TokenIntegrityLevel, &tml, sizeof(TOKEN_MANDATORY_LABEL) + ::GetSidLengthRequired(1))) WriteEventLogEntry(L"SetTokenInformation 3 error", EVENTLOG_ERROR_TYPE);
LPVOID pEnv = 0;
if (!CreateEnvironmentBlock(&pEnv, hToken2, FALSE)) WriteEventLogEntry(L"CreateEnvironmentBlock error", EVENTLOG_ERROR_TYPE);if (!ImpersonateLoggedOnUser(hToken2)) WriteEventLogEntry(L"ImpersonateLoggedOnUser error", EVENTLOG_ERROR_TYPE);while (!m_fStopping)
{
     STARTUPINFO stinfo = { 0 };
   PROCESS_INFORMATION pinfo = { 0 };
   stinfo.cb = sizeof(stinfo);
   stinfo.lpDesktop = L"winsta0\\default";
   if (!CreateProcessAsUserW(hToken2, L"path to exe that shows a message box", 0, 0, 0, FALSE, CREATE_UNICODE_ENVIRONMENT|CREATE_BREAKAWAY_FROM_JOB, pEnv, L"cwd of the exe file", &stinfo, &pinfo))
   {
// after debugging I found that the error is coming from here 
       std::wstring error = L"CreateProcessAsUserW failed with error : ";
       error += std::to_wstring(GetLastError());
       WriteEventLogEntry(wcsdup(error.c_str()), EVENTLOG_ERROR_TYPE);
       Sleep(10000);
   }
   while (!m_fStopping && pinfo.hProcess)
   {
       if(WaitForSingleObject(pinfo.hProcess, 1000) != WAIT_TIMEOUT) break;
   }
   // ::Sleep(2000);  // Simulate some lengthy operations.
}

Проблема в том, что эта услуга очень хорошо работает после перезапуска Windows или запуска ее вручную с помощью sc. exe или диспетчер управления службами, но не после загрузки из предыдущего shutdownWhen я завершаю работу, затем загружаю компьютер, я могу видеть exe службы, запущенной в диспетчере задач, поэтому я знал, что служба запущена, и возникает ошибка от функции, I использовал события Windows и зарегистрировал ошибки, и я наконец обнаружил, что ошибка исходит от CreateProcessAsUser, которые возвращают ошибку 5 (Access denied) знаю, где проблема здесь, как служба работает хорошо после перезагрузки или при запуске вручную


2 ответа

Есть решение
VictorV ответил: 16 июня 2018 в 01:26

ServiceMain не вызывается во время быстрого запуска. при использовании быстрого запуска сеанс 0 (включая ядро, драйверы, все службы) не завершался. Он выключил сессию 0, когда вы выключаете и продолжаете работать как зависание VirtualMachine с выходом пользователя из системы. Таким образом, ваша служба сохраняет статус, а не запускается снова.

Простой способ справиться с этим: обработать SERVICE_ACCEPT_SESSIONCHANGE управляющий код.

Вот пример.

SERVICE_STATUS gSvcStatus;
DWORD WINAPI HandleCtrlEx(
    DWORD dwControl,
    DWORD dwEventType,
    LPVOID lpEventData,
    LPVOID lpContext
)
{
    // Handle the requested control code. 
    switch (dwControl)
    {
        case SERVICE_CONTROL_SHUTDOWN:
        case SERVICE_CONTROL_STOP:
            ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);            // Signal the service to stop.            SetEvent(ghSvcStopEvent);
            ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);            break;        case SERVICE_CONTROL_SESSIONCHANGE:
            if (dwEventType == WTS_SESSION_LOGON) {
                DWORD *data = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DWORD));
                *data = ((PWTSSESSION_NOTIFICATION)lpEventData)->dwSessionId;
                HANDLE hThread = CreateThread(NULL, 0, RunProgram, data, 0, NULL);// remember to free memory in RunProgram
                if (!hThread) {
                    PrintLogA(LOG"can't create thread,error:%u", GetLastError());
                }
                else {
                    CloseHandle(hThread);
                }
            }
            break;        default:
            break;
    }
    return NO_ERROR;
}VOID ReportSvcStatus(DWORD dwCurrentState,
    DWORD dwWin32ExitCode,
    DWORD dwWaitHint)
{
    static DWORD dwCheckPoint = 1;    // Fill in the SERVICE_STATUS structure.    gSvcStatus.dwCurrentState = dwCurrentState;
    gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;
    gSvcStatus.dwWaitHint = dwWaitHint;    if (dwCurrentState == SERVICE_START_PENDING)
        gSvcStatus.dwControlsAccepted = 0;
    else {
        gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
        gSvcStatus.dwControlsAccepted |= SERVICE_ACCEPT_SESSIONCHANGE;// <--- remember add this.
    }    if ((dwCurrentState == SERVICE_RUNNING) ||
        (dwCurrentState == SERVICE_STOPPED))
        gSvcStatus.dwCheckPoint = 0;
    else gSvcStatus.dwCheckPoint = dwCheckPoint++;    // Report the status of the service to the SCM.
    SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
}VOID SvcInit(DWORD dwArgc, LPTSTR *lpszArgv)
{
    ghSvcStopEvent = CreateEvent(
        NULL,    // default security attributes
        TRUE,    // manual reset event
        FALSE,   // not signaled
        NULL);   // no name    if (ghSvcStopEvent == NULL)
    {
        ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
        return;
    }    // Report running status when initialization is complete.
    ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);    RunProgram();    // Check whether to stop the service.
    WaitForSingleObject(ghSvcStopEvent, INFINITE);
    ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
    return;
}VOID WINAPI SvcMain(DWORD dwArgc, LPTSTR *lpszArgv){
    // Register the handler function for the service
    gSvcStatusHandle = RegisterServiceCtrlHandlerEx(
        SVCNAME,
        HandleCtrlEx,
        NULL);
    if (!gSvcStatusHandle)
    {
        SvcReportEventA(LOG"RegisterServiceCtrlHandler failed");
        return;
    }    // These SERVICE_STATUS members remain as set here    gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    gSvcStatus.dwServiceSpecificExitCode = 0;
    // Report initial status to the SCM    ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);    // Perform service-specific initialization and work.    SvcInit(dwArgc, lpszArgv);
}
dev65 ответил: 09 июня 2018 в 01:39
Исходя из этого, я понимаю, что быстрое отключение выполняет что-то вроде приостановки службы, а затем быстрый запуск возобновляет ее. Я прав ?
VictorV ответил: 09 июня 2018 в 12:52
@ dev65 Быстрый запуск hibernates сеанс 0, который содержит ядро, драйверы, службы и т. д. Так, он не приостанавливает все службы и не возобновляет их, он просто возобновляет сеанс 0. Для более подробной информации, проверьте эту ссылку
dev65 ответил: 09 июня 2018 в 05:09
Теперь понятно, что Windows сбрасывает память сеанса 0 при завершении работы, а затем восстанавливает ее при быстром запуске, чтобы драйверы и службы работали так, как если бы они не останавливались. Проблема, с которой я столкнулся, заключается в том, что блок среды, идентификатор сеанса и токен, хранящиеся в памяти, были недействительными после быстрого запуска, поскольку он принадлежит сеансу пользователя, который больше не существует. Решение состоит в том, чтобы обнаружить быстрый запуск, чтобы обновить их, но, как я не мог раньше, я просто обновил токен и другие вещи, когда CreateProcessAsUser не удается
dev65 ответил: 09 июня 2018 в 05:10
Но ваше решение кажется лучшим способом сделать то, что мне было нужно, так как оно обнаружит создание нового пользовательского сеанса, когда пользователь входит в систему после быстрого запуска, чтобы я мог обновить переменные, необходимые для работы с вошедшим в систему пользователем, снова
VictorV ответил: 10 июня 2018 в 06:58
@ dev65 Примечание: функция обработчика управления предназначена для получения уведомлений и немедленного возврата. Функция обратного вызова должна сохранять свои параметры и создавать другие потоки для выполнения дополнительной работы. ---- Из замечаний этой ссылки
dev65 ответил: 29 марта 2018 в 08:18

это члены класса, которые будут инициализированы:

PSID gpSidMIL_High = 0;
DWORD userSessionID = 0;
HANDLE hToken = 0, hToken2 = 0;
DWORD dwUIAccess = 1;
TOKEN_MANDATORY_LABEL tml = { 0 };
LPVOID pEnv = 0;
STARTUPINFO stinfo = { 0 };
PROCESS_INFORMATION pinfo = { 0 };

и в защищенных методах я сделал этот метод:

void intialize();

и это функция:

void CSampleService::intialize() {
// the members may were used before , without assigning them to zero again all the functions below will fail , I don't know why ?
gpSidMIL_High = 0;
userSessionID = 0;
hToken = 0, hToken2 = 0;
dwUIAccess = 1;
tml = { 0 };
pEnv = 0;
stinfo = { 0 };
pinfo = { 0 };WriteEventLogEntry(L"initializing", EVENTLOG_INFORMATION_TYPE);ConvertStringSidToSidW(L"S-1-16-12288", &gpSidMIL_High);
userSessionID = WTSGetActiveConsoleSessionId();if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken)) WriteErrorLogEntry(L"OpenProcessToken");
if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hToken2)) WriteErrorLogEntry(L"DuplicateTokenEx error");if (!SetTokenInformation(hToken2, TokenSessionId, &userSessionID, sizeof(userSessionID))) WriteErrorLogEntry(L"SetTokenInformation 1 error");if (!SetTokenInformation(hToken2, TokenUIAccess, &dwUIAccess, sizeof(dwUIAccess))) WriteErrorLogEntry(L"SetTokenInformation 2");//Set "high" mandatory integrity level
//TOKEN_MANDATORY_LABEL tml = { 0 };
tml.Label.Attributes = SE_GROUP_INTEGRITY;
tml.Label.Sid = gpSidMIL_High;if (!SetTokenInformation(hToken2, TokenIntegrityLevel, &tml, sizeof(TOKEN_MANDATORY_LABEL) + ::GetSidLengthRequired(1))) WriteErrorLogEntry(L"SetTokenInformation 3");
LPVOID pEnv = 0;
if (!CreateEnvironmentBlock(&pEnv, hToken2, FALSE)) WriteErrorLogEntry(L"CreateEnvironmentBlock");
 }

finally в рабочем потоке:

void CSampleService::ServiceWorkerThread(void)
{
WriteEventLogEntry(L"the thread of the service started", EVENTLOG_INFORMATION_TYPE);
intialize();
while (!m_fStopping)
{
stinfo.cb = sizeof(stinfo);
   stinfo.lpDesktop = L"winsta0\\default";
   // the service starts from here after fast boot
   if (!CreateProcessAsUserW(hToken2, L"C:\\Users\\user\\Desktop\\ConEmuTrap\\test.exe", 0, 0, 0, FALSE, CREATE_UNICODE_ENVIRONMENT | CREATE_BREAKAWAY_FROM_JOB, pEnv, L"C:\\Users\\user\\Desktop\\ConEmuTrap", &stinfo, &pinfo))
   {
       WriteErrorLogEntry(L"CreateProcessAsUserW 1");
       intialize();
   }
   while (!m_fStopping && pinfo.hProcess)
   {
       if(WaitForSingleObject(pinfo.hProcess, 1000) != WAIT_TIMEOUT) break;
   }
 }
dev65 ответил: 29 марта 2018 в 08:20
Я использую их в классе, чтобы разделить их между функцией инициализации и функцией потока, я думаю, что вместо этого я могу использовать лямбаду