使用PsExec和C#远程运行程序:已连接但无法正常执行
解决C#调用PsExec远程执行.exe安装程序的阻塞问题
哥们,你现在的代码逻辑有几个致命问题,导致远程程序没启动,还卡在WaitForExit()上。我帮你拆解问题并给出修正方案:
核心问题分析
- 输入流混淆:你先启动本地cmd,然后输入PsExec命令打开远程cmd,接着又输入
commandToRunRemote——但这个命令其实是发给本地cmd的,根本没传到远程控制台里,所以远程机器上完全没启动目标程序,本地cmd还在等着输入,自然会无限阻塞。 - 多此一举的中间步骤:PsExec本身可以直接指定要远程运行的程序和参数,不需要先启动远程cmd再输入命令,这步完全是画蛇添足。
- 标准流死锁风险:你在
WaitForExit()之后才去读StandardError和StandardOutput,如果输出缓冲区满了,远程进程会卡住,导致本地进程永远等不到退出信号。
修正后的代码
直接用PsExec远程调用目标安装程序,去掉多余的cmd步骤,同时处理好标准流的读取:
using System; using System.Text; using System.Diagnostics; class RemoteInstaller { static string hostname = @"111.111.1.11"; static string username = "myusername"; static string password = "mypassword"; public string RunRemoteInstaller() { try { // 注意:如果PsExec不在系统PATH里,要写完整路径,比如@"C:\Tools\PsExec.exe" string psexecPath = @"psexec.exe"; // 远程机器上的目标程序路径 string remoteExePath = @"C:\Users\MyUser\Desktop\MyInstaller\DLLConfiguration.exe"; // 目标程序的参数 string exeArguments = @"""C:\Users\MyUser\Desktop\MyInstaller\Installers"" -s -update"; ProcessStartInfo startInfo = new ProcessStartInfo { FileName = psexecPath, // 拼接PsExec的完整参数:远程主机、会话、工作目录、凭据、目标程序、程序参数 Arguments = $@"\\{hostname} -i 1 -w ""C:\Users\MyUser\Desktop\Updater"" -u {username} -p {password} ""{remoteExePath}"" {exeArguments}", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, // 不需要显示窗口就设为true,需要交互就设为false WindowStyle = ProcessWindowStyle.Hidden }; // 使用using确保进程资源被正确释放 using (Process process = new Process { StartInfo = startInfo }) { StringBuilder outputBuilder = new StringBuilder(); StringBuilder errorBuilder = new StringBuilder(); // 异步读取输出和错误,避免缓冲区满导致进程挂起 process.OutputDataReceived += (sender, e) => { if (!string.IsNullOrEmpty(e.Data)) outputBuilder.AppendLine(e.Data); }; process.ErrorDataReceived += (sender, e) => { if (!string.IsNullOrEmpty(e.Data)) errorBuilder.AppendLine(e.Data); }; process.Start(); // 开始异步读取 process.BeginOutputReadLine(); process.BeginErrorReadLine(); // 设置超时时间(比如30秒),避免无限等待 bool didExit = process.WaitForExit(30000); if (!didExit) { process.Kill(); return $"ERROR: 进程超时未退出。错误信息:{errorBuilder.ToString()}"; } string output = outputBuilder.ToString(); string error = errorBuilder.ToString(); if (process.ExitCode == 0) { return $"执行成功!输出信息:{output}"; } else { return $"执行失败。退出码:{process.ExitCode}。错误信息:{error}。输出信息:{output}"; } } } catch (Exception ex) { return $"发生异常:{ex.Message}"; } } }
关键改进说明
- 直接调用PsExec执行目标程序:跳过中间的远程cmd,让PsExec直接在远程机器上启动你的安装程序,参数直接传递,彻底解决输入流混淆的问题。
- 异步读取标准流:用
BeginOutputReadLine和BeginErrorReadLine异步读取输出,防止缓冲区满导致进程卡死,这也是之前WaitForExit无限等待的重要原因。 - 超时处理:设置30秒超时,超时后主动终止进程,避免程序无限挂起。
- 资源管理:用
using块管理Process对象,确保进程资源被正确释放。
额外注意事项
- 确保本地机器能访问远程机器的
ADMIN$共享(PsExec依赖这个),远程防火墙允许445端口通信。 - 确认你的用户名和密码拥有远程机器的管理员权限,PsExec需要管理员权限才能远程执行程序。
- 检查
-i 1指定的会话ID是否正确:可以在远程机器上运行query session命令查看当前交互会话的ID,确保和你指定的一致(如果安装程序是GUI程序,这个参数很重要)。 - 确认远程机器上的所有路径都存在(包括目标程序路径和参数里的Installers路径),路径写错会导致程序无法启动。
内容的提问来源于stack exchange,提问作者YoungEddie
相关产品推荐
相关产品推荐

