R027---Uipath调用python程序的exe

本文介绍了如何使用 UiPath 的 InvokeCode 活动来调用 Python 程序编译后的 EXE 文件,无需安装 Python 环境。详细步骤包括 InvokeCode 活动的使用、Python EXE 的生成(通过 pyinstaller 打包)以及注意事项。

​一、缘起

看到不少朋友问Uipath调用python的方法,

这里说一个方法:调用python程序编译后的exe。

由于调用的是exe文件,所以,其实没有用到UiPath.Python.Activities,用的是UiPath.Core.Activities.InvokeCode。

二、Invoke Code说明

     译https://activities.uipath.com/docs/invoke-code

     中文翻译:调用代码

     英文全名:UiPath.Core.Activities.InvokeCode

      此activity从v2017.1版本开始提供。

同步调用VB.net代码,可选地传递一个输入参数列表。此活动还可以返回调用者工作流的Out参数。

属性

基本

  • ContinueOnError - 指定即使活动引发错误,自动化是否仍应继续。该字段仅支持布尔值(True,False)。默认值为False。因此,如果该字段为空并且抛出错误,则项目的执行将停止。如果该值设置为True,则无论出现任何错误,项目的执行都将继续。

  • DisplayName - 活动的显示名称。

注意:

如果此活动包含在Try Catch中,并且ContinueOnError属性的值为True,则在执行项目时不会捕获任何错误。

输入

  • 参数 - 可以传递给调用的代码的参数。

  • 代码 - 要调用的VB.net代码。

<think>我们使用Python调用CreateProcessAsUser启动UI进程,需要借助ctypes库调用Windows API。根据引用[1]和引用[3]的内容,我们知道在服务中启动UI进程需要用到CreateProcessAsUser,并且需要获取目标用户会话的令牌(Token)和环境信息。同时,引用[2]提醒我们注意权限问题。 步骤概述: 1. 获取当前活动会话ID(可能需要枚举会话找到活动用户) 2. 通过WTSQueryUserToken获取该会话的用户令牌 3. 复制令牌(DuplicateTokenEx) 4. 创建用户环境块(CreateEnvironmentBlock) 5. 设置STARTUPINFO结构,指定桌面为"winsta0\default" 6. 调用CreateProcessAsUser创建进程 注意:由于Python的ctypes调用Windows API较为底层,代码会相对复杂,并且需要处理很多错误情况。 下面是一个Python实现的示例代码。注意:此代码应在以LocalSystem账户运行的服务中执行,并且需要适当权限(如SeAssignPrimaryTokenPrivilege和SeIncreaseQuotaPrivilege,通常LocalSystem已具备)。 注意:在Windows 11中,会话隔离机制依然存在,所以此方法仍然适用。 代码步骤: 1. 导入ctypes和相关库 2. 定义所需的结构体和常量 3. 加载Wtsapi32.dll和Advapi32.dll中的函数 4. 获取活动会话ID(这里简化使用WTSGetActiveConsoleSessionId,对于多用户环境可能需要枚举) 5. 获取用户令牌并复制 6. 创建环境块 7. 设置启动信息 8. 调用CreateProcessAsUser 注意:代码中需要处理错误检查和资源释放。 由于代码较长,我们将分步骤实现。 参考引用[1]和引用[3]的内容,我们使用CreateProcessAsUser方法。 代码如下: </think>以下是使用 Python 调用 `CreateProcessAsUser` 在 Windows 11 服务中启动 UI 进程的完整解决方案,基于引用内容[^1][^2][^3]的最佳实践: ### Python 实现步骤(需管理员权限) ```python import ctypes from ctypes import wintypes # 加载所需库 advapi32 = ctypes.WinDLL('advapi32', use_last_error=True) wtsapi32 = ctypes.WinDLL('wtsapi32', use_last_error=True) userenv = ctypes.WinDLL('userenv', use_last_error=True) # 定义结构体和常量 class STARTUPINFO(ctypes.Structure): _fields_ = [ ('cb', wintypes.DWORD), ('lpReserved', wintypes.LPWSTR), ('lpDesktop', wintypes.LPWSTR), ('lpTitle', wintypes.LPWSTR), ('dwX', wintypes.DWORD), ('dwY', wintypes.DWORD), ('dwXSize', wintypes.DWORD), ('dwYSize', wintypes.DWORD), ('dwXCountChars', wintypes.DWORD), ('dwYCountChars', wintypes.DWORD), ('dwFillAttribute', wintypes.DWORD), ('dwFlags', wintypes.DWORD), ('wShowWindow', wintypes.WORD), ('cbReserved2', wintypes.WORD), ('lpReserved2', ctypes.POINTER(ctypes.c_byte)), ('hStdInput', wintypes.HANDLE), ('hStdOutput', wintypes.HANDLE), ('hStdError', wintypes.HANDLE) ] class PROCESS_INFORMATION(ctypes.Structure): _fields_ = [ ('hProcess', wintypes.HANDLE), ('hThread', wintypes.HANDLE), ('dwProcessId', wintypes.DWORD), ('dwThreadId', wintypes.DWORD) ] MAXIMUM_ALLOWED = 0x2000000 TokenPrimary = 1 CREATE_UNICODE_ENVIRONMENT = 0x00000400 SecurityIdentification = 1 # 函数原型声明 wtsapi32.WTSEnumerateSessionsW.restype = wintypes.BOOL wtsapi32.WTSGetActiveConsoleSessionId.restype = wintypes.DWORD wtsapi32.WTSQueryUserToken.argtypes = [wintypes.DWORD, ctypes.POINTER(wintypes.HANDLE)] advapi32.CreateProcessAsUserW.restype = wintypes.BOOL userenv.CreateEnvironmentBlock.restype = wintypes.BOOL def create_ui_process_from_service(exe_path): """从服务会话启动用户UI进程""" # 1. 获取活动会话ID[^1] session_id = wtsapi32.WTSGetActiveConsoleSessionId() if session_id == 0xFFFFFFFF: raise ctypes.WinError(ctypes.get_last_error()) # 2. 获取用户令牌 hToken = wintypes.HANDLE() if not wtsapi32.WTSQueryUserToken(session_id, ctypes.byref(hToken)): raise ctypes.WinError(ctypes.get_last_error()) # 3. 复制令牌[^2] hDupToken = wintypes.HANDLE() if not advapi32.DuplicateTokenEx( hToken, MAXIMUM_ALLOWED, None, SecurityIdentification, TokenPrimary, ctypes.byref(hDupToken) ): raise ctypes.WinError(ctypes.get_last_error()) # 4. 创建环境块 env_block = ctypes.c_void_p() if not userenv.CreateEnvironmentBlock(ctypes.byref(env_block), hDupToken, False): raise ctypes.WinError(ctypes.get_last_error()) # 5. 配置启动信息 startup_info = STARTUPINFO() startup_info.cb = ctypes.sizeof(STARTUPINFO) startup_info.lpDesktop = "winsta0\\default" # 关键:指定用户桌面[^3] proc_info = PROCESS_INFORMATION() # 6. 创建进程 if not advapi32.CreateProcessAsUserW( hDupToken, None, exe_path, None, None, False, CREATE_UNICODE_ENVIRONMENT, env_block, None, ctypes.byref(startup_info), ctypes.byref(proc_info) ): raise ctypes.WinError(ctypes.get_last_error()) # 7. 清理资源 ctypes.windll.kernel32.CloseHandle(proc_info.hProcess) ctypes.windll.kernel32.CloseHandle(proc_info.hThread) userenv.DestroyEnvironmentBlock(env_block) ctypes.windll.kernel32.CloseHandle(hDupToken) ctypes.windll.kernel32.CloseHandle(hToken) # 使用示例 if __name__ == "__main__": try: # 替换为你的UI程序绝对路径 create_ui_process_from_service(r"C:\Apps\Popup.exe") print("UI进程启动成功") except Exception as e: print(f"错误: {e}") ``` ### UI 进程示例 (Python/PyQt6) ```python # popup.py - UI进程代码 import sys from PyQt6.QtWidgets import QApplication, QMessageBox if __name__ == "__main__": app = QApplication(sys.argv) # 创建消息框 msg = QMessageBox() msg.setIcon(QMessageBox.Icon.Information) msg.setWindowTitle("服务通知") msg.setText("后台任务已完成 ✅") msg.setStandardButtons(QMessageBox.StandardButton.Ok) msg.exec() sys.exit(app.exec()) ``` ### 部署注意事项 1. **权限要求**: - 服务必须以 `LocalSystem` 账户运行 - 授予服务 `SeAssignPrimaryTokenPrivilege` 权限: ```powershell sc.exe sdset <服务名> "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)" ``` 2. **清单文件要求**: - UI 程序需要添加清单文件防止 UAC 拦截[^2] ```xml <!-- popup.exe.manifest --> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> <security> <requestedPrivileges> <requestedExecutionLevel level="asInvoker" uiAccess="false"/> </requestedPrivileges> </security> </trustInfo> <dpiAwareness>PerMonitorV2</dpiAwareness> </assembly> ``` 3. **会话处理增强**: ```python def get_active_session(): """处理多用户会话场景[^3]""" sessions = ctypes.POINTER(wintypes.LPWSTR)() count = wintypes.DWORD() if wtsapi32.WTSEnumerateSessionsW(0, 0, 1, ctypes.byref(sessions), ctypes.byref(count)): for i in range(count.value): session_id = sessions[i].SessionId # 检查会话状态(简化示例) if wtsapi32.WTSQuerySessionInformationW(0, session_id, 25, ...) == 'Active': return session_id return 0xFFFFFFFF ``` ### 替代方案(推荐) 对于不需要复杂交互的场景,优先使用 `WTSSendMessage` 更安全[^3]: ```python def show_service_message(title, message): session_id = wtsapi32.WTSGetActiveConsoleSessionId() response = wintypes.DWORD() wtsapi32.WTSSendMessageW( wintypes.HANDLE(0), session_id, title, (len(title) + 1) * 2, message, (len(message) + 1) * 2, 0x40, 10, ctypes.byref(response), True ) ``` ### 常见错误处理 | 错误代码 | 原因 | 解决方案 | |---------|------|---------| | `5` | 权限不足 | 以LocalSystem运行服务 | | `740` | 需要提权[^2] | 添加UI程序清单文件 | | `1314` | 特权缺失 | 授予服务`SeAssignPrimaryTokenPrivilege` | | `1359` | 无效桌面 | 设置`lpDesktop="winsta0\\default"` | > **最佳实践**:对于 Windows 11 服务交互,简单通知使用 `WTSSendMessage`,复杂 UI 使用 `CreateProcessAsUser` 并配合清单文件,多用户场景优先选择任务计划程序方案[^1][^3]。
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值