You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

纯C++ Windows服务实现用户注销:EWX_LOGOFF调用失败求助

解决Windows服务触发用户注销的纯C++方案

原来的代码替换EWX_SHUTDOWN为EWX_LOGOFF后无效,核心原因是Windows服务默认运行在Session 0(非交互式会话),而ExitWindowsEx(EWX_LOGOFF)必须在用户的交互式会话(通常是Session 1及以上)上下文执行。下面是纯C++解决方案,核心思路是切换到用户会话上下文执行注销操作:

实现步骤

1. 给服务进程启用必要权限

服务需要SE_TCB_NAME(用于获取用户会话令牌)和SE_SHUTDOWN_NAME(用于执行注销)权限,先调整当前进程的令牌权限:

BOOL EnableRequiredPrivileges() {
    HANDLE hToken;
    TOKEN_PRIVILEGES tkp;

    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
        return FALSE;

    // 启用SE_TCB_NAME权限
    LookupPrivilegeValue(NULL, SE_TCB_NAME, &tkp.Privileges[0].Luid);
    tkp.PrivilegeCount = 1;
    tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0);
    if (GetLastError() != ERROR_SUCCESS) {
        CloseHandle(hToken);
        return FALSE;
    }

    // 启用SE_SHUTDOWN_NAME权限
    LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid);
    AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, NULL, 0);
    BOOL result = (GetLastError() == ERROR_SUCCESS);

    CloseHandle(hToken);
    return result;
}

2. 获取当前活跃的用户会话ID

用WTSGetActiveConsoleSessionId()获取用户正在使用的控制台会话ID:

DWORD GetActiveUserSessionId() {
    DWORD sessionId = WTSGetActiveConsoleSessionId();
    // 0xFFFFFFFF表示无活跃会话
    return (sessionId != 0xFFFFFFFF) ? sessionId : 0;
}

3. 获取目标会话的用户令牌

通过WTSQueryUserToken()拿到指定会话的用户令牌,用于后续在用户上下文创建进程:

HANDLE GetUserSessionToken(DWORD sessionId) {
    HANDLE hUserToken = NULL;
    if (WTSQueryUserToken(sessionId, &hUserToken)) {
        return hUserToken;
    }
    return NULL;
}

4. 在用户会话中执行注销

创建一个轻量辅助程序(比如LogoffHelper.exe),仅负责调用注销API;然后在服务中用CreateProcessAsUser()启动该程序,让它运行在用户会话上下文:

辅助程序代码(LogoffHelper.cpp)

#include <Windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
    // 执行注销,添加EWX_FORCE可强制关闭未响应程序
    ExitWindowsEx(EWX_LOGOFF, SHTDN_REASON_MAJOR_OPERATINGSYSTEM | SHTDN_REASON_MINOR_SECURITY);
    return 0;
}

服务中启动辅助程序的代码

BOOL TriggerUserLogoff() {
    if (!EnableRequiredPrivileges())
        return FALSE;

    DWORD sessionId = GetActiveUserSessionId();
    if (sessionId == 0)
        return FALSE;

    HANDLE hUserToken = GetUserSessionToken(sessionId);
    if (!hUserToken)
        return FALSE;

    STARTUPINFO si = {0};
    si.cb = sizeof(STARTUPINFO);
    si.lpDesktop = L"winsta0\\default"; // 指定用户桌面会话

    PROCESS_INFORMATION pi = {0};

    // 替换为你的LogoffHelper.exe实际路径
    BOOL success = CreateProcessAsUser(
        hUserToken,
        L"C:\\YourPath\\LogoffHelper.exe",
        NULL,
        NULL,
        NULL,
        FALSE,
        CREATE_UNICODE_ENVIRONMENT,
        NULL,
        NULL,
        &si,
        &pi
    );

    // 清理资源
    CloseHandle(hUserToken);
    if (success) {
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    }

    return success;
}

注意事项

  • 辅助程序需与服务保持相同架构(32位/64位),否则CreateProcessAsUser可能失败。
  • 确保服务以SYSTEM权限运行(默认服务登录身份即为SYSTEM)。
  • si.lpDesktop必须设置为winsta0\\default,保证进程在用户的交互式桌面中运行。
  • 若需要强制注销(忽略未保存数据),可在ExitWindowsEx中添加EWX_FORCE参数。

内容的提问来源于stack exchange,提问作者Happy Hobbyist

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 11:52:28