QtService实现Qt后台服务程序其二_启动外部exe无窗口异常的解决

——接上篇——

启动外部exe无窗口异常的解决,Qt5.9.2亲测通过

1、背景

Windows下服务直接启动窗口程序时,在任务管理器中可以看到窗口程序正在运行,但是桌面上并没有显示出窗口。

这是因为在Windows XP、Windows Server 2003 或早期Windows 系统时代,当第一个用户登录系统后服务和应用程序是在同一个Session 中运行的,也就是Session 0。

但是这种运行方式提高了系统安全风险,因为服务是通过提升了用户权限运行的,而应用程序往往是那些不具备管理员身份的普通用户运行的,其中的危险显而易见。

所以从Vista 开始Session 0 中只包含系统服务,其他应用程序则通过分离的Session 运行,将服务与应用程序隔离提高系统的安全性。这样使得Session 0 与其他Session 之间无法进行交互,不能通过服务向桌面用户弹出信息窗口、UI 窗口等信息。

这个时候如果想让我们的界面程序被服务启动就必须穿透Session 0 隔离。在实际开发过程中,可以通过Process Explorer检查服务或程序处于哪个Session,会不会遇到Session 0 隔离问题。

        下面就是穿透Session 0 隔离及服务启动窗口程序的步骤:

        1、使用OpenProcessToken函数来打开与服务进程相关联的访问令牌;

        2、使用DuplicateTokenEx函数创建一个新的访问令牌来复制一个已经存在的标记;

        3、使用SetTokenInformation函数把服务token的SessionId替换成当前活动的Session;

        4、使用CreateEnvironmentBlock函数创建进程环境块;

        5、使用CreateProcessAsUser函数在活动的Session下创建进程

2、Demo说明

加入WtsApi32.lib,Userenv.lib库等,解决QService启动外部进程没有GUI的问题

.pro加入代码:

#WtsApi32.lib,Userenv.lib库等,解决QService启动外部进程没有GUI的问题
LIBS += \
    #-L$$DESTDIR \
    -lWtsApi32 \
    -lAdvApi32 \
    -lUserEnv

加入ProcessLoader类

processloader.h代码:

#ifndef PROCESSLOADER_H
#define PROCESSLOADER_H

#include 

//解决QService启动外部进程没有GUI的问题

class ProcessLoader
{
public:
    ProcessLoader();
    ~ProcessLoader();

public:

#ifdef Q_OS_WIN
    static bool LaunchAppIntoDifferentSession(std::wstring command);//方式2
#endif

};

#endif // PROCESSLOADER_H

processloader.cpp代码:

#include "processloader.h"

#ifdef Q_OS_WIN

#include 
#include 
#include 
#include 
#include 

#endif


ProcessLoader::ProcessLoader()
{}

ProcessLoader::~ProcessLoader()
{}


#ifdef Q_OS_WIN


#include 
bool ProcessLoader::LaunchAppIntoDifferentSession(std::wstring command)
{
    PROCESS_INFORMATION pi;
    STARTUPINFO si;
    BOOL bResult = FALSE;
    DWORD dwSessionId,winlogonPid;
    HANDLE hUserToken=INVALID_HANDLE_VALUE,hUserTokenDup=INVALID_HANDLE_VALUE,
            hPToken=INVALID_HANDLE_VALUE,hProcess=INVALID_HANDLE_VALUE;
    DWORD dwCreationFlags;

    int errorcode;//whl2023-10-18
    LPVOID pEnv =NULL;
    TCHAR commandLine[MAX_PATH];

    // Log the client on to the local computer.

    dwSessionId = WTSGetActiveConsoleSessionId();

    //
    // Find the winlogon process


    PROCESSENTRY32 procEntry;

    HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnap == INVALID_HANDLE_VALUE)
    {
        return false ;
    }

    procEntry.dwSize = sizeof(PROCESSENTRY32);

    if (!Process32First(hSnap, &procEntry))
    {
        return false ;
    }

    do
    {
        if (_wcsicmp(procEntry.szExeFile, L"winlogon.exe") == 0)
        {
            // We found a winlogon process...
            // make sure it's running in the console session
            DWORD winlogonSessId = 0;
            if (ProcessIdToSessionId(procEntry.th32ProcessID, &winlogonSessId)
                    && winlogonSessId == dwSessionId)
            {
                winlogonPid = procEntry.th32ProcessID;
                break;
            }
        }

    } while (Process32Next(hSnap, &procEntry));



    WTSQueryUserToken(dwSessionId,&hUserToken);
    dwCreationFlags = NORMAL_PRIORITY_CLASS|CREATE_NEW_CONSOLE;
    ZeroMemory(&si, sizeof(STARTUPINFO));
    si.cb= sizeof(STARTUPINFO);
    si.lpDesktop = L"winsta0\\default";
    ZeroMemory(&pi, sizeof(pi));
    TOKEN_PRIVILEGES tp;
    LUID luid;
    hProcess = OpenProcess(MAXIMUM_ALLOWED,FALSE,winlogonPid);

    if(!::OpenProcessToken(hProcess,TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY
                           |TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY|TOKEN_ADJUST_SESSIONID
                           |TOKEN_READ|TOKEN_WRITE,&hPToken))
    {
        errorcode = GetLastError();
        printf("Process token open Error: %u\n",GetLastError());
        //goto ToFree;//whl2023-10-18
    }

    if (!LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid))
    {
        printf("Lookup Privilege value Error: %u\n",GetLastError());
        //goto ToFree;//whl2023-10-18
    }
    tp.PrivilegeCount =1;
    tp.Privileges[0].Luid =luid;
    tp.Privileges[0].Attributes =SE_PRIVILEGE_ENABLED;

    DuplicateTokenEx(hPToken,MAXIMUM_ALLOWED,NULL,
                     SecurityIdentification,TokenPrimary,&hUserTokenDup);
    errorcode = GetLastError();

    // Adjust Token privilege
    SetTokenInformation(hUserTokenDup,
                        TokenSessionId,(void*)dwSessionId,sizeof(DWORD));

    if (!AdjustTokenPrivileges(hUserTokenDup,FALSE,&tp,sizeof(TOKEN_PRIVILEGES),
                               (PTOKEN_PRIVILEGES)NULL,NULL))
    {
        errorcode = GetLastError();
        printf("Adjust Privilege value Error: %u\n",GetLastError());
        //goto ToFree;//whl2023-10-18
    }

    if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
    {
        printf("Token does not have the provilege\n");
        //goto ToFree;//whl2023-10-18
    }



    if(CreateEnvironmentBlock(&pEnv,hUserTokenDup,TRUE))
    {
        dwCreationFlags|=CREATE_UNICODE_ENVIRONMENT;
    }
    else
        pEnv=NULL;

    // Launch the process in the client's logon session.

    _tcscpy_s(commandLine, MAX_PATH, command.c_str());

    bResult = CreateProcessAsUser(
                hUserTokenDup,                     // client's access token
                //_T("cmd.exe"),        // file to execute
                //NULL,                 // command line
                NULL,                   // file to execute
                (LPWSTR)(commandLine),  // command line
                NULL,            // pointer to process SECURITY_ATTRIBUTES
                NULL,               // pointer to thread SECURITY_ATTRIBUTES
                FALSE,              // handles are not inheritable
                dwCreationFlags,     // creation flags
                pEnv,               // pointer to new environment block
                NULL,               // name of current directory
                &si,               // pointer to STARTUPINFO structure
                &pi                // receives information about new process
                );
    // End impersonation of client.

    // GetLastError Shud be 0

    errorcode = GetLastError();
    bResult = true;
    // Perform All the Close Handles tasks
ToFree:
    {
        if(hProcess != INVALID_HANDLE_VALUE)
        {
            CloseHandle(hProcess);
        }
        if(hUserToken != INVALID_HANDLE_VALUE)
        {
            CloseHandle(hUserToken);
        }
        if(hUserTokenDup != INVALID_HANDLE_VALUE)
        {
            CloseHandle(hUserTokenDup);
        }
        if(hPToken != INVALID_HANDLE_VALUE)
        {
            CloseHandle(hPToken);
        }
    }
    return bResult;
}

#endif

调用方式举例:

//要启动的进程名
#define PROCESS_NAME "EDU_CLIENT.exe"
QString str_app_name = PROCESS_NAME;
#ifdef Q_OS_WIN
        std::wstring command = str_app_name.toStdWString();
        if (ProcessLoader::LaunchAppIntoDifferentSession(command) == false) {
            qDebug() << "Failed to launch " << command.c_str();
        }
#endif

附:

亲测无效方式1:

bool ProcessLoader::ServerRunWndProcess(std::wstring command)
{
    TCHAR commandLine[MAX_PATH];
    HANDLE hToken = NULL;
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken))
    {
        return false;
    }

    HANDLE hTokenDup = NULL;
    bool bRet = DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL, SecurityIdentification, TokenPrimary, &hTokenDup);
    if (!bRet || hTokenDup == NULL)
    {
        CloseHandle(hToken);
        return false;
    }

    DWORD dwSessionId = WTSGetActiveConsoleSessionId();
    //把服务hToken的SessionId替换成当前活动的Session(即替换到可与用户交互的winsta0下)
    if (!SetTokenInformation(hTokenDup, TokenSessionId, &dwSessionId, sizeof(DWORD)))
    {
        DWORD nErr = GetLastError();
        CloseHandle(hTokenDup);
        CloseHandle(hToken);
        return false;
    }

    STARTUPINFO si;
    ZeroMemory(&si, sizeof(STARTUPINFO));

    si.cb = sizeof(STARTUPINFO);
    si.lpDesktop = _T("WinSta0\\Default");
    si.wShowWindow = SW_SHOW;
    si.dwFlags = STARTF_USESHOWWINDOW /*|STARTF_USESTDHANDLES*/;

    //创建进程环境块
    LPVOID pEnv = NULL;
    bRet = CreateEnvironmentBlock(&pEnv, hTokenDup, FALSE);
    if (!bRet)
    {
        CloseHandle(hTokenDup);
        CloseHandle(hToken);
        return false;
    }

    if (pEnv == NULL)
    {
        CloseHandle(hTokenDup);
        CloseHandle(hToken);
        return false;
    }

    //在活动的Session下创建进程
    PROCESS_INFORMATION processInfo;
    ZeroMemory(&processInfo, sizeof(PROCESS_INFORMATION));
    DWORD dwCreationFlag = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT;
    _tcscpy_s(commandLine, MAX_PATH, command.c_str());
    if (!CreateProcessAsUser(hTokenDup, NULL, (LPWSTR)(commandLine) , NULL, NULL, FALSE, dwCreationFlag, pEnv, NULL, &si, &processInfo))
    {
        DWORD nRet = GetLastError();
        CloseHandle(hTokenDup);
        CloseHandle(hToken);
        return false;
    }

    DestroyEnvironmentBlock(pEnv);
    CloseHandle(hTokenDup);
    CloseHandle(hToken);

    return true;
}

亲测无效方式2:

bool ProcessLoader::loadWindowsApplication(std::wstring command)
{
    BOOL bResult = FALSE;

    DWORD dwSessionId = WTSGetActiveConsoleSessionId();
    if (dwSessionId == 0xFFFFFFFF)
    {
        return false;
    }

    HANDLE hUserToken = NULL;
    if (WTSQueryUserToken(dwSessionId, &hUserToken) == FALSE)
    {
        DWORD error = GetLastError();
        return false;
    }

    HANDLE hTheToken = NULL;
    if (DuplicateTokenEx(hUserToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, 0, SecurityImpersonation, TokenPrimary, &hTheToken) == TRUE)
    {

        if (ImpersonateLoggedOnUser(hTheToken) == TRUE)
        {
            DWORD dwCreationFlags = HIGH_PRIORITY_CLASS | CREATE_NEW_CONSOLE;

            STARTUPINFO si = { sizeof(si) };
            PROCESS_INFORMATION pi;
            SECURITY_ATTRIBUTES Security1 = { sizeof(Security1) };
            SECURITY_ATTRIBUTES Security2 = { sizeof(Security2) };

            LPVOID pEnv = NULL;
            if (CreateEnvironmentBlock(&pEnv, hTheToken, TRUE) == TRUE)
            {
                dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
            }

            TCHAR commandLine[MAX_PATH];
            _tcscpy_s(commandLine, MAX_PATH, command.c_str());

            // Launch the process in the client's logon session.
            bResult = CreateProcessAsUser(
                hTheToken,
                NULL,   // (LPWSTR)(path),
                (LPWSTR)(commandLine),
                &Security1,
                &Security2,
                FALSE,
                dwCreationFlags,
                pEnv,
                NULL,
                &si,
                &pi
                );

            RevertToSelf();

            if (pEnv)
            {
                DestroyEnvironmentBlock(pEnv);
            }
        }
        CloseHandle(hTheToken);
    }
    CloseHandle(hUserToken);

    return bResult==TRUE;
}

你可能感兴趣的:(qt,开发语言)