0

正如WTSQueryUserToken 引发错误 1008 中所讨论的,即使在 LocalSystem 下运行时,我也无法让我的 Windows 服务在特定用户登录后立即在其桌面上启动交互式进程。

建议的解决方案是处理SERVICE_CONTROL_SESSIONCHANGE控制代码并使用传递的dwSessionId. 这是所有代码(抱歉,它很长,但我被告知无论如何都要在这里发布):

// These headers just contain system header #include's function prototypes
// and global variable declarations. If a variable below seems like it is
// undefined, rest assured that it *is* defined in one of these headers.
#include "events.h"
#include "main.h"

int __cdecl _tmain(int argc, LPTSTR argv[]) {
    sysStart = system_clock::now();
    LogInit();

    // If command-line parameter is "install", install the service.
    // Otherwise, the service is probably being started by the SCM
    if (lstrcmpi(argv[1], L"install") == 0) {
        return SvcInstall();
    }

    SERVICE_TABLE_ENTRY dispatchTable[] = {
        { &svcName[0], (LPSERVICE_MAIN_FUNCTION)SvcMain },
        { nullptr, nullptr }
    };

    // This call returns when the service has stopped. The
    // process should simply terminate when the call returns
    if (!StartServiceCtrlDispatcher(dispatchTable)) {
        ReportSvcEvent("StartServiceCtrlDispatcher");
    }

    return ERROR_SUCCESS;
}

char* WINAPI GetTimestamp(string& buf) {
    int ms = (high_resolution_clock::now().
        time_since_epoch().count() / 1000000) % 1000;

    auto tt = system_clock::to_time_t(
        system_clock::now());
    tm time;
    localtime_s(&time, &tt);

    strftime(&buf[0], 21, "[%d-%m-%Y %T", &time);
    snprintf(&buf[0], 26, "%s.%03d] ", &buf[0], ms);
    buf[25] = ' ';

    return &buf[0];
}

bool WINAPI LaunchDebugger(void) {
    // Get System directory, typically C:\Windows\System32
    wstring systemDir(MAX_PATH + 1, '\0');
    UINT nChars = GetSystemDirectory(&systemDir[0], systemDir.length());

    if (nChars == 0) {
        return false; // failed to get system directory
    }

    systemDir.resize(nChars);

    // Get process ID and create the command line
    // wostringstream ss;
    // ss << systemDir << L"\\vsjitdebugger.exe -p " << GetCurrentProcessId();
    wstring cmdLine = L"";

    // Start debugger process
    STARTUPINFOW si;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);

    PROCESS_INFORMATION pi;
    ZeroMemory(&pi, sizeof(pi));

    if (!CreateProcess(nullptr, &cmdLine[0], nullptr,
        nullptr, false, 0, nullptr, nullptr, &si, &pi)) {
        return false;
    }

    // Close debugger process handles to eliminate resource leaks
    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);

    // Wait for the debugger to attach
    while (!IsDebuggerPresent()) {
        Sleep(100);
    }

    // Stop execution so the debugger can take over
    DebugBreak();
    return true;
}

VOID WINAPI LogActiveTime(void) {
    // The computer is shutting down - write an entry to logFile to reflect
    // this, prefixed with a null byte to mark the current file position
    // (used for parsing in the timestamp on the next system boot)
    logFile << '\0';
    LogMessage("User action", "System shutting down after being "
        "active for " + DurationString(system_clock::now() - sysStart));
    logFile.close();

    // If the log file contains > 40 lines (10 boot/shutdown cycles),
    // remove the first 4 lines (the earliest boot/shutdown cycle).
    // This stops the file from getting too long to read easily
    ifstream inFile(logFilePath);
    string line;
    auto count = 0;

    while (getline(inFile, line)) {
        count++;
    }
}

DWORD WINAPI LogError(const string& funcName) {
    auto err = 0;
    LogMessage(funcName, system_category(
        ).message(err = GetLastError()), true);
    return err;
}

DWORD WINAPI LogInactiveTime(void) {
    // Create a new log file to be used as the input on the next run
    LogInit("temp");

    // Open the existing log file for reading and find the last shutdown
    // log entry by copying its contents to the new file until a null byte
    // or EOF is found (see LogActiveTime() for more info)
    ifstream inFile(logFilePath);
    if (!inFile) {
        return LogError("LogInactiveTime");
    }

    char ch = inFile.get();
    while (ch != '\0' && !inFile.eof()) {
        logFile << ch;
        ch = inFile.get();
    }

    if (inFile.eof()) {
        // No shutdown log entry was found, i.e. this is probably the first
        // time the service has run on the current instance of our log file.
        // Close the temp file and re-open the original log file before
        // returning, otherwise future messages won't make it to the file!
        LogInit();
        return ERROR_SUCCESS;
    }

    // At this point we can be sure that a valid shutdown log entry
    // exists, so we now need to parse it into a chrono::time_point.
    // Also save the entry's starting position in pos for later use
    auto pos = inFile.tellg();
    auto tt = system_clock::to_time_t(sysStart);
    tm start, end = { 0 };

    localtime_s(&start, &tt);
    inFile >> get_time(&end, "[%d-%m-%Y %T");

    if (inFile.fail() || inFile.bad()) {
        return LogError("LogInactiveTime");
    }

    // Ensure that both time_points refer to
    // the correct time, regardless of DST
    end.tm_isdst = start.tm_isdst;
    sysEnd = system_clock::from_time_t(mktime(&end));

    // Go back to the *actual* start of the shutdown
    // log entry so we can copy it into the new file
    inFile.seekg(pos);

    // Finish copying over the rest of our existing log
    // file, then close it and replace it with the new one
    ch = inFile.get();
    while (!inFile.eof()) {
        logFile << ch;
        ch = inFile.get();
    }

    inFile.close();
    remove(logFilePath.c_str());

    logFile.close();
    rename("temp", logFilePath.c_str());

    // Finally, do what we *actually* came here to do!
    LogMessage("User action", "System booting after being "
        "inactive for " + DurationString(sysStart - sysEnd));
    return ERROR_SUCCESS;
}

VOID WINAPI LogInit(const string& filePath) {
    setlocale(LC_ALL, "en_US.UTF8");

    if (logFile.is_open()) {
        logFile.close();
    }

    logFile.open(filePath == "" ?
        logFilePath : filePath, ios::app);

    if (!logFile) {
        exit(GetLastError());
    }
}

VOID WINAPI LogMessage(const string& funcName,
    const string& msg, bool isError) {
    if (!logFile.is_open()) {
        LogInit();
    }

    string buf(52, '\0');
    snprintf(&buf[0], 52, "%s%-6s %-18s ", GetTimestamp(buf),
        isError ? "ERROR:" : "INFO:", &(funcName + ':')[0]);
    buf[51] = ' ';

    logFile << buf << msg << endl;
}

VOID WINAPI ReportSvcEvent(const string& funcName) {
    HANDLE eventSrc = RegisterEventSource(nullptr, &svcName[0]);
    if (eventSrc != nullptr) {
        LPCSTR errParams[2] = { "WinUtilities" };
        char buf[MAX_PATH];

        StringCchPrintfA(buf, MAX_PATH, "Function '%s' failed: %s",
            funcName.c_str(), system_category().message(GetLastError(
            )).c_str());
        errParams[1] = buf;

        ReportEventA(eventSrc,      // event log handle
            EVENTLOG_ERROR_TYPE,    // event type
            0,                      // event category
            SVC_ERROR,              // event identifier
            nullptr,                // no security identifier
            2,                      // size of lpszStrings array
            0,                      // no binary data
            errParams,              // array of strings
            nullptr);               // no binary data

        DeregisterEventSource(eventSrc);
    }
}

VOID WINAPI ReportSvcStatus(DWORD newState,
    DWORD exitCode, DWORD waitHint) {
    static DWORD dwCheckPoint = 1;
    static unordered_map<int, string> svcStates;

    if (svcStates.empty()) {
        // Initialise mapping from service state codes to readable strings
        svcStates.insert({ SERVICE_STOPPED, "Stopped" });
        svcStates.insert({ SERVICE_START_PENDING, "Start Pending" });
        svcStates.insert({ SERVICE_STOP_PENDING, "Stop Pending" });
        svcStates.insert({ SERVICE_RUNNING, "Running" });
        svcStates.insert({ SERVICE_CONTINUE_PENDING, "Continue Pending" });
        svcStates.insert({ SERVICE_PAUSE_PENDING, "Pause Pending" });
        svcStates.insert({ SERVICE_PAUSED, "Paused" });
    }

    // Update the SERVICE_STATUS structure with the new passed-in values
    svcStatus.dwCurrentState = newState;
    svcStatus.dwWin32ExitCode = exitCode;
    svcStatus.dwWaitHint = waitHint;

    if (newState == SERVICE_START_PENDING) {
        svcStatus.dwControlsAccepted = 0;
    } else {
        svcStatus.dwControlsAccepted =
            SERVICE_ACCEPT_SESSIONCHANGE |
            SERVICE_ACCEPT_STOP |
            SERVICE_ACCEPT_PRESHUTDOWN;
    }

    if (newState == SERVICE_RUNNING ||
        newState == SERVICE_STOPPED) {
        svcStatus.dwCheckPoint = 0;
    } else {
        svcStatus.dwCheckPoint = dwCheckPoint++;
    }

    // Report the status of the service to the SCM and our log file
    if (!SetServiceStatus(statusHandle, &svcStatus)) {
        LogError("SetServiceStatus");
    } else {
        LogMessage("SetServiceStatus", "Service status " \
            "updated to '" + svcStates[newState] + "'.");
    }
}

DWORD WINAPI SvcCtrlHandler(DWORD ctrlCode, DWORD
    eventType, LPVOID eventData, LPVOID context) {
    switch (ctrlCode) {
        case SERVICE_CONTROL_SESSIONCHANGE: {
            auto sessionId = ((WTSSESSION_NOTIFICATION*
                )eventData)->dwSessionId;

            switch (eventType) {
                case WTS_SESSION_LOGON: {
                    string userName;
                    DWORD size;

                    WTSQuerySessionInformationA(WTS_CURRENT_SERVER_HANDLE, sessionId,
                        WTS_INFO_CLASS::WTSUserName, (LPSTR*)&userName[0], &size);
                    ReportSvcEvent("log on");

                    // A user has successfully logged on to the PC. Now we can start
                    // an interactive worker process under that user's account which
                    // will perform the actual work that we want to do
                    STARTUPINFO si = { 0 };
                    si.cb = sizeof(si);
                    si.wShowWindow = true;

                    HANDLE hToken;
                    if (!WTSQueryUserToken(sessionId, &hToken)) {
                        LogError("WTSQueryUserToken");
                        return ERROR_CALL_NOT_IMPLEMENTED;
                    }

                    wstring cmdLine = L"C:\\Path\\to\\my\\app.exe";
                    if (!CreateProcessAsUser(hToken, &cmdLine[0], nullptr, nullptr, nullptr,
                        false, CREATE_NO_WINDOW, nullptr, nullptr, &si, &workerProc)) {
                        LogError("CreateProcessAsUser");
                        return ERROR_CALL_NOT_IMPLEMENTED;
                    }

                    CloseHandle(hToken);
                    break;
                } default: {
                    break;
                }
            }

            break;
        } case SERVICE_CONTROL_STOP: {
            // Signal the service to stop
            ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
            SetEvent(svcStopEvent);
            break;
        } case SERVICE_CONTROL_PRESHUTDOWN: {
            LogActiveTime();
            break;
        } default: {
            return ERROR_CALL_NOT_IMPLEMENTED;
        }
    }

    return NO_ERROR;
}

VOID WINAPI SvcInit(DWORD argc, LPTSTR argv[]) {
    // Get the time at which the last shutdown occurred, and
    // log the duration for which the system was inactive
    if (LogInactiveTime() > 0) {
        return;
    }

    // Create an event. The control handler function (SvcCtrlHandler)
    // signals this event when it receives the stop control code
    svcStopEvent = CreateEvent(
        nullptr,    // default security attributes
        TRUE,       // manual reset event
        FALSE,      // not signaled
        nullptr);   // no name

    if (svcStopEvent == nullptr) {
        LogError("CreateEvent");
        ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
        return;
    }

    // Report running status when initialisation is complete
    ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);

    // Wait until our stop event has been signalled
    WaitForSingleObject(svcStopEvent, INFINITE);

    // Code execution won't reach here until the service has been
    // fully stopped. Report this to the SCM when it happens, then
    // terminate the worker process and clean up its handles
    ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);

    if (workerProc.hProcess) {
        TerminateProcess(workerProc.hProcess, 0);

        CloseHandle(workerProc.hProcess);
        CloseHandle(workerProc.hThread);
    }
}

DWORD WINAPI SvcInstall(void) {
    TCHAR path[MAX_PATH];
    if (!GetModuleFileName(nullptr, path, MAX_PATH)) {
        return LogError("GetModuleFileName");
    }

    // Get a handle to the SCM database
    auto scm = OpenSCManager(
        nullptr,                 // local computer
        nullptr,                 // ServicesActive database
        SC_MANAGER_ALL_ACCESS);  // full access rights

    if (scm == nullptr) {
        return LogError("OpenSCManager");
    }

    // Create the service
    auto svc = CreateService(
        scm,                        // SCM database
        &svcName[0],                // name of service
        L"Windows Utilities",       // service name to display
        SERVICE_ALL_ACCESS,         // desired access
        SERVICE_WIN32_OWN_PROCESS,  // service type
        SERVICE_AUTO_START,         // start type
        SERVICE_ERROR_NORMAL,       // error control type
        path,                       // path to service's binary
        nullptr,                    // no load ordering group
        nullptr,                    // no tag identifier
        nullptr,                    // no dependencies
        nullptr,                    // LocalSystem account
        nullptr);                   // no password

    if (svc == nullptr) {
        CloseServiceHandle(scm);
        return LogError("CreateService");
    }

    SERVICE_DESCRIPTION sd;
    sd.lpDescription = const_cast<LPTSTR>(L"Logs system "
        "shutdown events to a text file on the desktop. "
        "Also creates a system-wide hot key to perform "
        "internet searches on any selected text.");

    if (!ChangeServiceConfig2(
        svc,                        // handle to service
        SERVICE_CONFIG_DESCRIPTION, // change: description
        &sd))                       // new description
    {
        CloseServiceHandle(svc);
        CloseServiceHandle(scm);

        return LogError("ChangeServiceConfig2");
    }

    CloseServiceHandle(svc);
    CloseServiceHandle(scm);

    LogMessage("SvcInstall", "Service installed successfully.");
    return ERROR_SUCCESS;
}

VOID WINAPI SvcMain(DWORD argc, LPTSTR argv[]) {
    // Register the handler function for the service
    statusHandle = RegisterServiceCtrlHandlerEx(
        &svcName[0], SvcCtrlHandler, 0);

    if (!statusHandle) {
        ReportSvcEvent("RegisterServiceCtrlHandlerEx");
        return;
    }

    // These SERVICE_STATUS members remain as set here
    svcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    svcStatus.dwServiceSpecificExitCode = 0;

    // Report initial status to the SCM
    ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);

    // Perform service-specific initialization and work
    SvcInit(argc, argv);
}

不起作用的部分在SvcCtrlHandler()函数中,我试图在其中捕获上述控制代码。

我什至已经用 C# 重写了整个事情(这是我应该首先使用的语言,因为我的代码现在变得更加干净和清晰了),你猜怎么着?我的方法仍然存在完全相同的问题OnSessionChange()

当我冷启动计算机并允许我的 PC 自动登录到我的单个用户帐户时,没有任何反应(即没有app.exe启动)。但是,如果我然后注销并重新登录,我会得到我正在寻找的结果。

所以似乎我的服务是最后几个加载的服务之一,这阻止了它正确捕获SERVICE_CONTROL_SESSIONCHANGE控制代码。我怎样才能解决这个问题?大都会!:D

4

0 回答 0