【问题标题】:c++ service doesn't start after windows shutdown and then bootc++服务在windows关闭然后启动后不启动
【发布时间】:2018-04-21 18:10:45
【问题描述】:

我想在 C++ 中创建一个 Windows 服务,以便在用户每次登录时以管理员身份启动我的程序而不弹出 UAC 窗口 因为这是我第一次这样做,所以我从这里使用了这个项目:https://code.msdn.microsoft.com/windowsapps/CppWindowsService-cacf4948/view/SourceCode

我将 CppWindowsService.cpp 中的第 74 行编辑为:

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
        );

并在 SampleService.cpp 第 101 行的工作线程中添加了一些代码,变成这样:

 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 或服务控制管理器手动启动后运行良好,但在从之前的关机启动后就不行了 当我关闭然后启动计算机时,我可以在任务管理器中看到该服务的 exe,所以我知道该服务正在运行并且有一个错误来自函数,我使用了 windows 事件并记录了错误,我终于找到了该错误来自 CreateProcessAsUser,它返回错误 5(拒绝访问) 我不知道问题出在哪里,因为服务在重新启动或手动启动后运行良好

【问题讨论】:

    标签: c++ windows winapi service


    【解决方案1】:

    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 快速启动使包含内核、驱动程序、服务等的会话 0 休眠。因此,它不会暂停所有服务并恢复它们,它只是恢复会话 0。有关更多详细信息,请查看此link
    • 现在明白了,Windows 在关机时转储会话 0 内存,然后在快速启动时恢复它,这样驱动程序和服务就可以像没有停止一样工作。我遇到的问题是因为存储在内存中的环境块、会话 ID 和令牌在快速启动后无效,因为它属于不再存在的用户会话。解决方案是检测快速启动以更新它们,但在 CreateProcessAsUser 失败时我刚刚更新令牌和其他东西之前我无法做到
    • 但是您的解决方案似乎是完成我需要的最佳方式,因为它会在快速启动后用户登录时检测新用户会话的创建,因此我可以更新我需要使用登录的变量再次用户
    • @dev65 注意:控制处理函数旨在接收通知并立即返回。回调函数应保存其参数并创建其他线程以执行额外的工作。 ----来自link备注
    【解决方案2】:

    这些是要初始化的类的成员:

    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");
     }
    

    终于在工作线程中:

    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;
       }
     }
    

    【讨论】:

    • 我在一个类中使用它们在初始化函数和线程函数之间共享它们,我想我可以用一个lambada代替
    猜你喜欢
    • 2011-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多