Windows一个进程启动或者激活其他任意一个桌面进程

先描述本文解决了什么问题:

        类似一个辅助进程,可以控制目标进程的启动、如果目标进程已经启动那么激活到桌面最前端的功能。

本文解决的问题:

1、一个进程已当前登录用户启动目标进程,这么做是由于,当辅助进程如果以管理员权限打开,启动另一个进程的时候,默认是以管理员权限启动的,这会导致程序中获取的路径不对,影响功能的正常执行,以及其他位置问题。

2、一个进程激活Windows桌面进程,主要解决的是Windows安全机制导致的很多场景,无法完成对目标进程的激活

3、如果目标进程最小化,该如何激活

4、进程间通信,辅助进程如何通知目标进程进行激活

下面对所有问题进行详细的描述以及附上解决的源码

一、启动进程

第一个问题比较简单,直接附上源码

当前用户权限启动程序:

// 普通用户权限启动进程
#ifdef UNICODE
bool CreateProcessWithUser(const std::wstring& exePath, const std::wstring& param, bool show)
#else
bool CreateProcessWithUser(const std::string& exePath, const std::string& param, bool show)
#endif // UNICODE
{
    HANDLE hToken = 0;
    HANDLE hNewToken = 0;
    LPVOID pEnvironment{ NULL };
    bool res{ false };
    int l = param.length();
#ifdef UNICODE
    wchar_t* cmd = new wchar_t[l + 1];
    memcpy(cmd, param.c_str(), l * sizeof(wchar_t));
    cmd[l] = 0;
#else
    char* cmd = new char[l + 1];
    memcpy(cmd, param.c_str(), l * sizeof(char));
    cmd[l] = 0;
#endif // UNICODE

    do 
    {
        if (!GetTokenWithProcessName(L"explorer.exe", hToken))
        {
            LOG_ERROR("GetTokenWithProcessName Error: %u", GetLastError());
            break;
        }
        if (!DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL/*&sa*/, SECURITY_MAX_IMPERSONATION_LEVEL, TokenPrimary, &hNewToken))
        {
            LOG_ERROR("DuplicateTokenEx Error: %u", GetLastError());
            break;
        }
        DWORD dwCreationFlag = NORMAL_PRIORITY_CLASS /*| CREATE_NEW_CONSOLE*/ | CREATE_UNICODE_ENVIRONMENT;
        // 检索指定用户的环境变量。然后,可以将此块传递给 CreateProcessAsUser 函数。
        if (!CreateEnvironmentBlock(&pEnvironment, hNewToken, FALSE))
        {
            LOG_ERROR("CreateEnvironmentBlock Error: %u", GetLastError());
            break;
        }
        STARTUPINFO si;
        PROCESS_INFORMATION pi;
        ZeroMemory(&si, sizeof(STARTUPINFO));
        si.cb = sizeof(STARTUPINFO);
#ifdef UNICODE
        wchar_t desktop[] = L"winsta0\\default";
#else
        char desktop[] = "winsta0\\default";
#endif 
        si.lpDesktop = desktop;
        si.dwFlags = STARTF_USESHOWWINDOW;
        if (show)
        {
            si.wShowWindow = SW_SHOW;
        }
        else
        {
            si.wShowWindow = SW_HIDE;
        }
        if (!CreateProcessAsUser(hNewToken, exePath.c_str(), cmd, 0, 0, FALSE, dwCreationFlag, pEnvironment, 0, &si, &pi))
        {
#ifdef UNICODE
            std::string ansicmd = mm::Charset::UnicodeToANSI(cmd);
            std::string ansibat = mm::Charset::UnicodeToANSI(exePath.c_str());
            LOG_ERROR("CreateProcessAsUser error! LastError=%ld, %s, %s", GetLastError(), ansibat.c_str(), ansicmd.c_str());
#else
            LOG_ERROR("CreateProcessAsUser error! LastError=%ld, %s, %s", GetLastError(), exePath.c_str(), cmd.c_str());
#endif // UNICODE
            break;
        }
        res = true;
    } while (0);
    // 清理
    delete[] cmd;
    if (hToken)
    {
        CloseHandle(hToken);
    }
    if (hNewToken) 
    {
        CloseHandle(hNewToken);
    }
    if (pEnvironment)
    {
        DestroyEnvironmentBlock(pEnvironment);
    }
    return res;
}

另外附上管理员权限启动程序,如果某些场景需要的话,可以参考

// 管理员权限启动进程
#ifdef UNICODE
MMSYSSHARED_EXPORT bool CreateProcessWithAdmin(const std::wstring& exe, const std::wstring& param, bool show)
#else
MMSYSSHARED_EXPORT bool CreateProcessWithAdmin(const std::string& exe, const std::string& param, bool show)
#endif // UNICODE
{
    HANDLE hToken{ NULL };
    HANDLE hTokenDup{ NULL };
    LPVOID pEnvironment{ NULL };
    bool res{ false };
#ifdef UNICODE
    wchar_t* cmd = (wchar_t*)param.c_str();
#else
    char* cmd = (wchar_t*)param.c_str();
#endif

    do
    {
        if (exe.empty())
        {
            LOG_ERROR("exe is null!");
            break;
        }
        if (!GetTokenWithProcessName(L"explorer.exe", hToken))
        {
            LOG_ERROR("GetTokenWithProcessName Error: %u", GetLastError());
            break;
        }
        // 复制令牌,把调用方有效的所有访问权限给复制后的令牌.
        if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED/*TOKEN_ALL_ACCESS*/, NULL/*&sa*/, SecurityImpersonation, TokenPrimary, &hTokenDup))
        {
            LOG_ERROR("DuplicateTokenEx Error: %u", GetLastError());
            break;
        }
        STARTUPINFO si;
        ZeroMemory(&si, sizeof(STARTUPINFO));
        si.cb = sizeof(STARTUPINFO);
#ifdef UNICODE
        wchar_t desk[]{ TEXT("WinSta0\\Default") };
#else
        char desk[]{ TEXT("WinSta0\\Default") };
#endif
        si.lpDesktop = desk;
        if (show)
        {
            si.wShowWindow = SW_SHOW;
        }
        else
        {
            si.wShowWindow = SW_HIDE;
        }
        si.dwFlags = STARTF_USESHOWWINDOW;
        PROCESS_INFORMATION pi;
        // 检索指定用户的环境变量。然后,可以将此块传递给 CreateProcessAsUser 函数。
        if (!CreateEnvironmentBlock(&pEnvironment, hTokenDup, FALSE))
        {
            LOG_ERROR("CreateEnvironmentBlock Error: %u", GetLastError());
            break;
        }
        // 缺少环境变量时某些依赖环境变量的程序打不开,或者运行不正常。
        if (!CreateProcessAsUser(hTokenDup, exe.c_str(), cmd, NULL, NULL, FALSE
            , NORMAL_PRIORITY_CLASS 
            /*| CREATE_NEW_CONSOLE  */ 
            | CREATE_UNICODE_ENVIRONMENT
            , pEnvironment, NULL, &si, &pi))
        {
            LOG_ERROR("CreateProcessAsUser Error: %u", GetLastError());
            break;
        }
        res = true;
    } while (0);
    // 清理
    if (cmd)
    {
        delete[]cmd;
    }
    if (pEnvironment)
    {
        DestroyEnvironmentBlock(pEnvironment);
    }
    if(hToken)
        CloseHandle(hToken);
    if (hTokenDup)
        CloseHandle(hTokenDup);
    return res;
}


还有一个方法,根据可执行文件名字查找进程句柄


#ifdef UNICODE
    bool GetTokenWithProcessName(const wchar_t* szName, HANDLE& hToken)
#else
    bool GetTokenWithProcessName(const char* szName, HANDLE& hToken)
#endif // _DEBUG
    {
        // HANDLE hToken{ NULL };
        HANDLE hProcessSnap{ NULL };
        PROCESSENTRY32 pe32{ NULL }; 
        HANDLE hProcess{ NULL };
        bool res{ false };
        do 
        {
            // 多用户模式时任务管理器里可能出现多个explorer
            // 需要先获取当前会话ID,再通过枚举进程,通过比较sessionID进而得到token。
            //DWORD dwSessionId = WTSGetActiveConsoleSessionId();
            //PWTS_PROCESS_INFO ppi = NULL;
            //DWORD dwProcessCount = 0;
            //if (WTSEnumerateProcesses(WTS_CURRENT_SERVER_HANDLE, 0, 1, &ppi, &dwProcessCount))
            //{
            //    for (int i = 0; i < dwProcessCount; i++)
            //    {      
            //        if (_wcsicmp(ppi[i].pProcessName, L"explorer.exe") == 0)
            //        {
            //            if (ppi[i].SessionId == dwSessionId)
            //            {
            //                break;
            //            }
            //        }
            //    }
            //    WTSFreeMemory(ppi);
            //}
            hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);       
            if (!hProcessSnap)
            {
                LOG_ERROR("CreateToolhelp32Snapshot error! %d", GetLastError());
                break;
            }
            pe32.dwSize = sizeof(PROCESSENTRY32);
            for (Process32First(hProcessSnap, &pe32); Process32Next(hProcessSnap, &pe32);)
            {
#ifdef UNICODE
                if (_wcsicmp((pe32.szExeFile), szName))
#else
                if (_stricmp((pe32.szExeFile), szName))
#endif // _DEBUG
                    continue;
                hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe32.th32ProcessID);
                if (!hProcess)
                {
                    LOG_ERROR("OpenProcess error! %d", GetLastError());
                    break;
                }
                BOOL ret = OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken);
                if(!ret)
                {
                    LOG_ERROR("OpenProcess error! %d", GetLastError());
                    break;
                }
                res = true;
                break;
            }
        } while (0);
       
        if (hProcessSnap)
        {
            CloseHandle(hProcessSnap);
        }
        if (hProcess)
        {
            CloseHandle(hProcess);
        }
        return res;
    }
    

以上解决方案参考windows服务中以 管理员权限\普通权限 启动进程_createprocess 管理员权限_火苗999℃的博客-CSDN博客

二、一个进程激活任意Windows桌面进程

可以通过获取目标进程的句柄,然后给该句柄发送windows的消息,但使用过程中,发现当辅助进程在没有获取焦点的情况下,发送的激活消息是无效的,后查阅资料,是由于windows的安全机制导致的,需要使用如下的方法,才可以在任意情况下激活目标进程

#include <iostream>
#include <windows.h>

// SwitchToThisWindow 方法
void ActiveAnyWindow_SwitchToThisWindow(HWND hWnd)
{
    // 第二个参数传TRUE:如果窗口被极小化了,则恢复
    SwitchToThisWindow(hWnd, TRUE);
}

// SetForegroundWindow 方法
void ActiveAnyWindow_SetForegroundWindow(HWND hWnd)
{
    const HWND hForeWnd = ::GetForegroundWindow();
    ::AttachThreadInput(::GetWindowThreadProcessId(hForeWnd, nullptr), ::GetCurrentThreadId(), TRUE);
    if (!::SetForegroundWindow(hWnd))
    {
        std::cout << "SetForegroundWindow GLE=%d", ::GetLastError();
    }
    else
    {
        std::cout << "SetForegroundWindow OK";
    }
    AttachThreadInput(::GetWindowThreadProcessId(hForeWnd, nullptr), ::GetCurrentThreadId(), FALSE);
}

// BringWindowToTop 方法
void ActiveAnyWindow_BringWindowToTop(HWND hWnd)
{
    const HWND hForeWnd = ::GetForegroundWindow();
    ::AttachThreadInput(::GetWindowThreadProcessId(hForeWnd, nullptr), ::GetCurrentThreadId(), TRUE);
    if (!::BringWindowToTop(hWnd))
    {
        std::cout << "BringWindowToTop GLE=%d", ::GetLastError();
    }
    else
    {
        std::cout << "BringWindowToTop OK";
    }
    AttachThreadInput(::GetWindowThreadProcessId(hForeWnd, nullptr), ::GetCurrentThreadId(), FALSE);
}

int main()
{
    ::Sleep(3000);
    // 先要打开系统记事本
    ActiveAnyWindow_BringWindowToTop(::FindWindow(L"Notepad", nullptr));
}

注意
被 AttachThreadInput 的进程,如果是具有管理员权限的进程,则调用 AttachThreadInput 的进程也必须要有管理员权限,否则 会失败,GetLastError 返回5(拒绝访问)。

窗口句柄如何获取呢?

三个办法,一个是根据窗口的title,调用windows API

HWND hwnd = ::FindWindow(NULL, "Title");

通过进程id获取窗口句柄

HWND s_hFoundWindow = NULL;

HWND FindWindowByProcessId(DWORD dwProcessId)
{
    qDebug() << "process id -> " << dwProcessId;
    s_hFoundWindow = NULL;
    EnumWindows(EnumWindowsProcId, (LPARAM)dwProcessId);
    return s_hFoundWindow;
}

BOOL CALLBACK EnumWindowsProcId(HWND hwnd, LPARAM lParam)
{
    DWORD dwID;
    GetWindowThreadProcessId(hwnd, &dwID);

    if (dwID == (DWORD)lParam)
    {
        // 找到了窗口句柄,将其保存在全局变量中
        s_hFoundWindow = hwnd;
        return FALSE;
    }
    return TRUE;
}

通过可执行文件名获取窗口句柄

HWND FindWindowByProcessName(LPCWSTR processName)
{
    s_hFoundWindow = NULL;
    EnumWindows(EnumWindowsProcName, (LPARAM)processName);
    return s_hFoundWindow;
}

BOOL CALLBACK EnumWindowsProcName(HWND hwnd, LPARAM lParam)
{
    DWORD dwID;
    GetWindowThreadProcessId(hwnd, &dwID);

    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwID);
    if (hProcess == NULL)
    {
        qDebug() << "Could not open process\n";
        return 1;
    }

    TCHAR filename[MAX_PATH];
    GetModuleFileNameEx(hProcess, NULL, filename, MAX_PATH);

    CloseHandle(hProcess);

    QString currentFileName = QString::fromStdWString(filename);

    if (currentFileName.contains(QString::fromStdString((char*)lParam), Qt::CaseInsensitive))
    {
        // 找到了窗口句柄,将其保存在全局变量中
        s_hFoundWindow = hwnd;
        return FALSE;
    }

    return TRUE;
}

注意,需要包含库和对应的头文件

下面是qt的pro配置,vs的话可以自行配置即可

LIBS += -lAdvapi32 -lWtsapi32 -lUserenv -lUser32

三、如果目标进程最小化

如果目标进程是最小化状态,那么在激活的时候,只是状态栏闪烁一下,并不会在窗口显示目标界面,需要给句柄进程发送点击的消息SC_RESTORE

结合上面的激活功能,代码如下

void ActiveAnyWindow_BringWindowToTop(HWND hWnd)
{
    const HWND hForeWnd = ::GetForegroundWindow();
    ::AttachThreadInput(::GetWindowThreadProcessId(hForeWnd, nullptr), ::GetCurrentThreadId(), TRUE);
    if (!::BringWindowToTop(hWnd)) {
        qDebug() << "BringWindowToTop GLE=%d", ::GetLastError();
    } else {
        qDebug() << "BringWindowToTop OK";
    }
    AttachThreadInput(::GetWindowThreadProcessId(hForeWnd, nullptr), ::GetCurrentThreadId(), FALSE);
    UINT msg = WM_SYSCOMMAND;
    WPARAM wParam = SC_RESTORE;
    LPARAM lParam = 0;

    PostMessage(hWnd, msg, wParam, lParam);
}

四、进程间通信

为什么会需要用进程间通信呢,因为某些程序,通过进程id或者进程名称获取的窗口句柄,并不是其显示的窗口,这样会导致并不能成功激活,即使可以通过窗口标题获取窗口句柄,但是有些程序的窗口标题是动态变化的

所以可以通过进程间通讯的方式,当需要激活的时候,告诉目标进程,目标进程按照上面说的方法,调用ActiveAnyWindow_BringWindowToTop即可

进程间通讯可考虑的有三种
1.通过windows消息WM_COPYDATA,该方案的有点事比较安全,缺点是不够灵活,需要获取窗口句柄,又回到之前的问题了

2.通过共享内存,该方案的优点是比较灵活,不需要获取窗口句柄,目标进程在需要的地方,监视共享内存锁即可,缺点是安全性不高,会有崩溃等问题的产生

3.通过socket通信,比较推荐,该方案比较安全而且比较灵活

前两种方案感兴趣的可自行查阅相关资料,本文主要对socket通信做个详细描述

主要是借助qt的LocalSocket

LocalSocketClient.h

#ifndef LOCALCLIENT_H
#define LOCALCLIENT_H

#include <QObject>
#include <QLocalSocket>

class LocalSocketClient : public QObject
{
    Q_OBJECT
public:
    explicit LocalSocketClient(QObject *parent = 0);

    ~LocalSocketClient();

    void connectServer();
    bool isConnectServer();
    void sendData(const QString &data);

private:
    QLocalSocket *socket;

private slots:
    void readData();

    void displayError(QLocalSocket::LocalSocketError socketError);

};

#endif // LOCALCLIENT_H

LocalSocketClient.cpp

#include <QDataStream>
//#include <syslog.h>
#include "LocalSocketClient.h"

#define SERVER_NAME "InterProcessCom"
#define IPC_RECV_BUF_LEN (1024)

LocalSocketClient::LocalSocketClient(QObject *parent) : QObject(parent)
{
    socket = new QLocalSocket(this);

    connect(socket, SIGNAL(readyRead()), this, SLOT(readData()));

    connect(socket, SIGNAL(error(QLocalSocket::LocalSocketError)),
            this, SLOT(displayError(QLocalSocket::LocalSocketError)));

    socket->abort();
    socket->connectToServer(SERVER_NAME);
}

LocalSocketClient::~LocalSocketClient()
{
    if(socket != NULL)
    {
        socket->abort();
        delete socket;
        socket = NULL;
    }
}

void LocalSocketClient::connectServer()
{
    socket->connectToServer(SERVER_NAME);
}

bool LocalSocketClient::isConnectServer()
{
    return socket->state() == QLocalSocket::ConnectedState;
}

void LocalSocketClient::sendData(const QString &data)
{
    socket->write(data.toStdString().c_str());
}

void LocalSocketClient::readData()
{
    //     socket->read();
}


void LocalSocketClient::displayError(QLocalSocket::LocalSocketError socketError)
{
    switch (socketError) {
    case QLocalSocket::ServerNotFoundError:
        break;
    case QLocalSocket::ConnectionRefusedError:
        break;
    case QLocalSocket::PeerClosedError:
        break;
    default:
        break;
    }
}

LocalSocketServer.h

#ifndef LOCALSERVER_H
#define LOCALSERVER_H

#include <QObject>
#include <QLocalServer>
#include <QLocalSocket>

class LocalSocketServer : public QObject
{
    Q_OBJECT
public:
    explicit LocalSocketServer(QObject *parent = 0);

    ~LocalSocketServer();

signals:
    void receiveData(const QString &data);

private slots:
    void processNewConnection();

    void disconnect();

    void readData();

private:
    QLocalServer *server;

    QVector<QLocalSocket *> localSocketList;
};

#endif // LOCALSERVER_H

LocalSocketServer.cpp

#include <QDebug>
#include <QFileInfo>
#include <QDataStream>
//#include <syslog.h>
#include "LocalSocketServer.h"

#define SERVER_NAME "InterProcessCom"
#define IPC_RECV_BUF_LEN (1024)

LocalSocketServer::LocalSocketServer(QObject *parent) : QObject(parent)
{
    qDebug() << "LocalServer construct";

    server = new QLocalServer(this);

    QLocalServer::removeServer(SERVER_NAME);

    if(!server->listen(SERVER_NAME))
    {
        qDebug() << "server listen failed!" << server->errorString();
    }
    connect(server, SIGNAL(newConnection()), this, SLOT(processNewConnection()));
}

LocalSocketServer::~LocalSocketServer()
{
    if(server != NULL)
    {
        server->close();
        delete server;
        server = NULL;
    }
}

void LocalSocketServer::processNewConnection()
{
    qDebug() << "LocalSocketServer processNewConnection";

    QLocalSocket *clientConnection = server->nextPendingConnection();
    localSocketList.append(clientConnection);
    connect(clientConnection, SIGNAL(readyRead()),
            this, SLOT(readData()));

    connect(clientConnection, SIGNAL(disconnected()),
            this, SLOT(disconnect()));
}

void LocalSocketServer::disconnect()
{
    for(int i = 0; i < localSocketList.size(); i++)
    {
        if(localSocketList.at(i)->state() != QLocalSocket::ConnectedState)
        {
            localSocketList.at(i)->deleteLater();
            localSocketList.removeAt(i);
        }
    }

    qDebug() <<  "after disconnect localSocketList size:"  << localSocketList.size();
}

void LocalSocketServer::readData()
{
    qDebug() << " data come ";
    QLocalSocket *local = static_cast<QLocalSocket*>(sender());
    if (!local)
        return ;

    receiveData(local->readAll());
}

需要注意的是,在使用的时候,发送前,需要先判断是否连接,未连接的时候先连接,同时需要注意,即使连接了,如果目标进程未启动也是会有异常,需要再次进行判断,如下

DWORD FindProcessId(const char *processName)
{
    qDebug() << QString::fromStdString(processName);
    // strip path
    PROCESSENTRY32 processInfo;
    processInfo.dwSize = sizeof(processInfo);

    HANDLE processesSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
    if (processesSnapshot == INVALID_HANDLE_VALUE)
        return 0;

    Process32First(processesSnapshot, &processInfo);
    if (!strcmp(processName, QString::fromStdWString(processInfo.szExeFile).toStdString().c_str()))
    {
        CloseHandle(processesSnapshot);
        return processInfo.th32ProcessID;
    }

    while (Process32Next(processesSnapshot, &processInfo))
    {
        if (!strcmp(processName, QString::fromStdWString(processInfo.szExeFile).toStdString().c_str()))
        {
            CloseHandle(processesSnapshot);
            return processInfo.th32ProcessID;
        }
    }

    CloseHandle(processesSnapshot);
    return 0;
}
        DWORD processId = FindProcessId("Target.exe");
        if (processId == 0) {
            OpenTargetProcess();
        } else {
            if (!m_socketClient->isConnectServer()) {
                m_socketClient->connectServer();
            }
            if (m_socketClient->isConnectServer()) {
                m_socketClient->sendData("mydata");
            }
        }

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值