Windows服务中CreateProcess启动客户端时截图功能失效问题求助
问题描述
开发了一套由C++客户端和Python服务器组成的远程控制应用,具备获取客户端桌面截图并上传至服务器的功能。直接运行程序时所有功能正常,但通过Windows服务中的CreateProcess接口启动客户端后,仅截图功能无法工作,其余功能均正常。
服务器端获取截图代码
if (cState == State.GETTING_SCREENSHOT): message = "SCREENSHOT".encode('utf-8') MySend(client_sock , message) curr_number = 0 with open("settings.txt" , 'r') as settings: curr_number_str = settings.readline() curr_number = int(curr_number_str) with open("settings.txt" , 'w') as settings: settings.write(str(curr_number + 1)) screenshot_file = str(curr_number) + ".png" with open(screenshot_file , 'wb') as file: while True: data = MyRecv(client_sock , 40000) data_arr = bytearray(data) if (data_arr[:10] == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'): continue if (b'ENDOFF' in data): break file.write(data) cState = State.INPUT_COMMAND
客户端发送截图代码
case SENDING_SCREENSHOT_IN_PROGRESS: { wchar_t PathToLocalAppDataFolder[MAX_PATH]; if (FAILED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, PathToLocalAppDataFolder))) { LoadMessage("Error in ShGetFolderPathW"); break; } std::wstring PathToScreenshot = std::wstring(PathToLocalAppDataFolder) + L"\\1.png"; if (!SaveScreen(PathToScreenshot.c_str())) { LoadMessage("SaveScreen api-func failed"); break; } HANDLE hFile = CreateFile(PathToScreenshot.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { LoadMessage("Can not open screenshot file"); break; } DWORD dwRead = 1; BYTE buffSend[BIG_BUFFLEN]; while (dwRead) { ZeroMemory(buffSend, BIG_BUFFLEN); dwRead = 0; ReadFile(hFile, buffSend, BIG_BUFFLEN, &dwRead, NULL); MySend(ClientSoc, (char*)buffSend, dwRead, 0); } CloseHandle(hFile); char message[SMALL_BUFFLEN] = "ENDOFFILE"; MySend(ClientSoc, message, BIG_BUFFLEN, 0); DeleteFile(PathToScreenshot.c_str()); cState = WAITING_FOR_INCOMING_COMMAND; break; }
Windows服务启动客户端代码
#define WIN32_LEAN_AND_MEAN #include <windows.h> #include <iostream> #include <shlobj_core.h> #include <string> #include "api.h" #include <fstream> #pragma comment(lib , "API.lib") #pragma warning(disable:4996) wchar_t SERVICE_NAME[] = L"RemoteController"; bool isCreate = FALSE; SERVICE_STATUS ServiceStatus = { 0 }; SERVICE_STATUS_HANDLE hServiceStatus; //StatusHandle HANDLE ServiceStopEvent = INVALID_HANDLE_VALUE; VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv); VOID WINAPI ServiceCtrlHandler(DWORD CtrlCode); DWORD WINAPI ServiceWorkerThreadEntry(LPVOID lpParam); VOID Install(VOID); BOOL dirExists(const wchar_t* dir_path); int main(int argc, char* argv[]) { FreeConsole(); Install(); SERVICE_TABLE_ENTRY ServiceTable[] = { {SERVICE_NAME , (LPSERVICE_MAIN_FUNCTION)ServiceMain} , {NULL , NULL} }; if (StartServiceCtrlDispatcher(ServiceTable) == FALSE) { LoadMessage("Service.exe : StartServiceCtrlDIspathcer error"); return GetLastError(); } return 0; } VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv) { DWORD Status = E_FAIL; hServiceStatus = RegisterServiceCtrlHandler( SERVICE_NAME, ServiceCtrlHandler ); if (hServiceStatus == NULL) { LoadMessage("Service.exe : RegisterServiceCtrlHandler failed"); return; } ZeroMemory(&ServiceStatus, sizeof(ServiceStatus)); ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS; ServiceStatus.dwControlsAccepted = 0; ServiceStatus.dwCurrentState = SERVICE_START_PENDING; ServiceStatus.dwWin32ExitCode = 0; ServiceStatus.dwServiceSpecificExitCode = 0; ServiceStatus.dwCheckPoint = 0; if (SetServiceStatus(hServiceStatus, &ServiceStatus) == FALSE) { LoadMessage("Service.exe : SetServiceStatus failed"); } ServiceStopEvent = CreateEventW(NULL, TRUE, FALSE, L"RemoteControllerEvent"); if (ServiceStopEvent == NULL) { ServiceStatus.dwControlsAccepted = 0; ServiceStatus.dwCurrentState = SERVICE_STOPPED; ServiceStatus.dwWin32ExitCode = GetLastError(); ServiceStatus.dwCheckPoint = 1; if (SetServiceStatus(hServiceStatus, &ServiceStatus) == FALSE) { LoadMessage("Service.exe : SetServiceStatus failed"); return; } } //start ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; ServiceStatus.dwCurrentState = SERVICE_RUNNING; ServiceStatus.dwWin32ExitCode = 0; ServiceStatus.dwCheckPoint = 0; if (SetServiceStatus(hServiceStatus, &ServiceStatus) == FALSE) { LoadMessage("Service.exe : SetServiceStatus failed"); } HANDLE hThread = CreateThread(NULL, 0, ServiceWorkerThreadEntry, NULL, 0, NULL); WaitForSingleObject(hThread, INFINITE); CloseHandle(ServiceStopEvent); ServiceStatus.dwControlsAccepted = 0; ServiceStatus.dwCurrentState = SERVICE_STOPPED; ServiceStatus.dwWin32ExitCode = 0; ServiceStatus.dwCheckPoint = 3; if (!SetServiceStatus(hServiceStatus, &ServiceStatus) == FALSE) { LoadMessage("Service.exe : SetServiceStatus failed"); } return; } VOID WINAPI ServiceCtrlHandler(DWORD CtrlCode) { switch (CtrlCode) { case SERVICE_CONTROL_STOP: if (ServiceStatus.dwCurrentState != SERVICE_RUNNING) break; ServiceStatus.dwControlsAccepted = 0; ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; ServiceStatus.dwWin32ExitCode = 0; ServiceStatus.dwCheckPoint = 4; if (SetServiceStatus(hServiceStatus, &ServiceStatus) == FALSE) { LoadMessage("Service.exe : SetServiceStatus failed"); return; } SetEvent(ServiceStopEvent); default: break; } } BOOL dirExists(const wchar_t* dir_path) { DWORD dwAttributes = GetFileAttributes(dir_path); if (dwAttributes == INVALID_FILE_ATTRIBUTES) return FALSE; if (dwAttributes & FILE_ATTRIBUTE_DIRECTORY) return TRUE; return FALSE; } VOID Install(VOID) { SC_HANDLE schSCManager; SC_HANDLE schService; wchar_t curr_path[MAX_PATH]; if (!GetModuleFileName(NULL, curr_path, MAX_PATH)) { LoadMessage("Service.exe : GetModuleFilename failed"); return; } schSCManager = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS ); if (schSCManager == NULL) { LoadMessage("Service.exe : OpenSCManager failed[with SC_MANAGER_ALL_ACCESS]"); return; } schService = CreateService( schSCManager, SERVICE_NAME, SERVICE_NAME, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, curr_path, NULL, NULL, NULL, NULL, NULL); if (schService == NULL) { LoadMessage("Service.exe : CreateService failed"); CloseServiceHandle(schSCManager); return; } if (StartService(schService , 0 , NULL) == 0){ LoadMessage("Service.exe : Start service failed"); CloseServiceHandle(schSCManager); CloseServiceHandle(schService); return; } LoadMessage("\tService instaled"); CloseServiceHandle(schSCManager); CloseServiceHandle(schService); } DWORD WINAPI ServiceWorkerThreadEntry(LPVOID lpParam) { if (isCreate) { return ERROR_SUCCESS; } isCreate = TRUE; wchar_t ProgramToRun[MAX_PATH]; ZeroMemory(ProgramToRun , sizeof(ProgramToRun)); SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL, 0, ProgramToRun); std::wstring tmp = std::wstring(ProgramToRun) + L"\\RemoteController\\MainClient.exe"; STARTUPINFO si; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); PROCESS_INFORMATION pi; CreateProcess(tmp.c_str(), NULL, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi); return ERROR_SUCCESS; }
原因分析
Windows服务默认运行在会话0,而用户的交互式桌面运行在会话1及以上。通过服务直接用CreateProcess启动的客户端会继承服务的会话0环境,无法访问用户的交互式桌面——截图功能需要调用系统API访问当前用户的显示设备和桌面窗口,因此会失败;而网络通信等不需要桌面交互的功能不受会话环境影响,所以能正常工作。
另外,服务中设置的SERVICE_INTERACTIVE_PROCESS仅允许服务在会话0显示UI,依然无法跨会话访问用户的交互式桌面。
解决方案
1. 核心思路:切换到用户会话启动客户端
获取当前登录用户的会话ID,通过CreateProcessAsUser()在用户会话中启动客户端,让客户端运行在能访问用户桌面的环境里。
2. 修改服务启动客户端的代码
替换原ServiceWorkerThreadEntry函数中的CreateProcess逻辑,示例代码如下:
DWORD WINAPI ServiceWorkerThreadEntry(LPVOID lpParam) { if (isCreate) { return ERROR_SUCCESS; } isCreate = TRUE; // 获取当前活跃的用户会话ID DWORD dwSessionId = WTSGetActiveConsoleSessionId(); if (dwSessionId == 0xFFFFFFFF) { LoadMessage("Failed to get active session ID"); return ERROR_FAILURE; } HANDLE hToken = NULL; // 获取会话对应的用户令牌 if (!WTSQueryUserToken(dwSessionId, &hToken)) { LoadMessage("WTSQueryUserToken failed"); return ERROR_FAILURE; } wchar_t ProgramToRun[MAX_PATH]; ZeroMemory(ProgramToRun, sizeof(ProgramToRun)); SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL, 0, ProgramToRun); std::wstring tmp = std::wstring(ProgramToRun) + L"\\RemoteController\\MainClient.exe"; STARTUPINFO si; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); si.lpDesktop = L"winsta0\\default"; // 指定用户的默认桌面 PROCESS_INFORMATION pi; // 在用户会话中启动客户端 if (!CreateProcessAsUser( hToken, tmp.c_str(), NULL, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi )) { LoadMessage("CreateProcessAsUser failed"); CloseHandle(hToken); return ERROR_FAILURE; } // 清理资源 CloseHandle(hToken); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); return ERROR_SUCCESS; }
3. 补充:添加服务权限
CreateProcessAsUser需要服务具备SE_ASSIGNPRIMARYTOKEN_NAME和SE_INCREASE_QUOTA_NAME权限,需在服务启动时添加权限控制:
BOOL EnablePrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) { TOKEN_PRIVILEGES tp; HANDLE hToken; LUID luid; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) return FALSE; if (!LookupPrivilegeValue(NULL, lpszPrivilege, &luid)) { CloseHandle(hToken); return FALSE; } tp.PrivilegeCount = 1; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = bEnablePrivilege ? SE_PRIVILEGE_ENABLED : 0; if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) { CloseHandle(hToken); return FALSE; } CloseHandle(hToken); return TRUE; }
在ServiceMain函数中启动工作线程前调用:
EnablePrivilege(SE_ASSIGNPRIMARYTOKEN_NAME, TRUE); EnablePrivilege(SE_INCREASE_QUOTA_NAME, TRUE);
4. 额外注意事项
- 确保客户端程序所在目录具备写入权限,避免截图保存失败;若放在
Program Files下,需给目录添加普通用户的写入权限,或改为保存到用户的AppData目录。 - 若系统存在多用户登录,需根据需求选择目标会话ID,上述代码默认获取当前活跃控制台的会话。
内容的提问来源于stack exchange,提问作者Егоров АРТЕМ
相关产品推荐
相关产品推荐

