您现在的位置是:首页 >其他 >C++以服务形式启动一个进程,需要关注的点网站首页其他

C++以服务形式启动一个进程,需要关注的点

della_Xiang 2024-06-17 10:26:05
简介C++以服务形式启动一个进程,需要关注的点

一、如何将进程以服务方式改写的代码,网上有很多教程,不再赘述。这里也附上一份代码可以参考:

C++以服务方式启动进程

二、改为服务启动后,可以用windows的sc命令去关闭、开启、删除、安装。

具体命令为:

sc create yourservicename binPath= D:sample.exe type= kernel // sample.exe是要启动的进程路径
sc start yourservicename //开启
sc stop yourservicename //停止
sc delete yourservicename //删除服务

也可以在任务管理器-服务界面 开启/停止服务。

三、下面来说说代码中因为改为服务启动需要修改的地方:

1. 代码中如果有SendMessage之类的API,在服务中不会生效,需要改成WTSSendMessageW。

原因是服务运行在session0 中,而所有包含界面的程序必须运行在用户会话中,而WTSSendMessageW这个API内部做了封装,突破了会话限制。

关于session 0 和 其他session的资料,可以参考这里:

session 0 和其它session 区别

2. 权限问题。

改为服务启动后,在任务管理器的进程列表看到的用户是system,而在服务打开其它的进程或者创建管道(大家的常用写法是把权限控制的参数填NULL)时会失败,需要做对应的降权处理。

具体的问题描述和解决方法,可以参考我另一篇文章:

给管道设置安全描述符,实现与chrome跨进程通信

3. 展示界面问题。

上面说到用WTSSendMessageW展示简单的提示信息,但如果是自己写的界面呢,如何在服务启动的方式下展示出来?

直接在服务的代码调用界面代码肯定是不行了,因为涉及到 session 隔离的问题,服务是无法展示界面的。这时候可以把界面代码封装成一个可以单独运行的exe,在服务里调用CreateProcessAsUser 函数,使用用户的session去创建进程,运行这个exe。

这种方法不仅支持在本机显示界面,还支持在远程PC显示界面。

相关代码如下:

#include <Iphlpapi.h>
#pragma comment(lib, "Iphlpapi.lib")

#include <wtsapi32.h>
#pragma comment(lib,"Wtsapi32.lib")

void policy::show_strategy_info(std::wstring message)
{
    DWORD session_id = WTSGetActiveConsoleSessionId();
    if (0xFFFFFFFF == session_id)
    {
        spdlog::warn("WTSGetActiveConsoleSessionId failed! sessionid:{}, {}", session_id, GetLastError());
        return;
    }
    
    HANDLE user_token = nullptr;
    if (!WTSQueryUserToken(session_id, &user_token)) 
    {
        spdlog::warn("WTSQueryUserToken(sessionid:{}) Failed! Error = {}", session_id, GetLastError());

        DWORD dwSessionId = - 1; 
        DWORD dwCount = 0 ;  
        PWTS_SESSION_INFO pSessionInfo = NULL ;
        WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessionInfo, &dwCount);

        int dataSize = sizeof(WTS_SESSION_INFO);
        for (DWORD i = 0; i < dwCount; ++i)
        {
            WTS_SESSION_INFO si = pSessionInfo[i];
            if (WTSActive == si.State && session_id != si.SessionId)
            {
            // If the current session is active – store its ID
                dwSessionId = si.SessionId;
                spdlog::warn("WTSEnumerateSessions sessionId = {}", dwSessionId);
                break;
            }
        }

        if(!WTSQueryUserToken(dwSessionId, &user_token))
        {
            spdlog::warn("WTSQueryUserToken sessionId={}, ErrorCode = {}", dwSessionId, GetLastError());
            CloseHandle(user_token);
            user_token = nullptr;
            return;
        }
        
    }

    STARTUPINFOW startup_info;
    PROCESS_INFORMATION process_info;
    ZeroMemory(&startup_info, sizeof(STARTUPINFOW));
    ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION));
    startup_info.cb = sizeof(STARTUPINFOW);
    //startup_info.lpDesktop = "WinSta0\Default";
    startup_info.dwFlags = STARTF_USESHOWWINDOW;
    startup_info.wShowWindow = SW_SHOWNORMAL;

    DWORD creation_flags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT;

    std::string temp_path = utils::current_path() + "xdrPop.exe";
    std::wstring pop_exe_path = utils::ansi2unicode(temp_path);
    BOOL result = CreateProcessAsUserW(user_token, pop_exe_path.c_str(), message.data(), NULL, NULL, FALSE, creation_flags, NULL, NULL, &startup_info, &process_info);
    if (!result) 
    {
        CloseHandle(user_token);
        user_token = nullptr;
        spdlog::warn("CreateProcessAsUser Failed! Error = {}", GetLastError());
        return;
    }

    CloseHandle(user_token);
    user_token = nullptr;
}

4. 注册表不能访问HKCU的问题。

HKCU是跟用户相关的,服务的session0 跟 用户的session是隔离的,自然无法访问HKCU。但是可以访问 HKLM。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。