C#调用执行命令行窗口cmd,及需要交互执行的处理 您所在的位置:网站首页 proceed和process联系 C#调用执行命令行窗口cmd,及需要交互执行的处理

C#调用执行命令行窗口cmd,及需要交互执行的处理

2023-11-24 00:11| 来源: 网络整理| 查看: 265

C#执行外部程序用到的是Process进程类,打开一个进程,可以指定进程的启动信息StartInfo(启动的程序名、输入输出是否重定向、是否显示UI界面、一些必要参数等)。

相关代码在网上可以找到很多,本篇在参考这些代码的基础上,进行一些修改,尤其是后面探讨交互执行时的情况。

交互执行输入信息,完成程序的执行,是一个相对很必要的情况。

需要添加命名空间System.Diagnostics。

在CMCode项目中添加文件夹ExecApplications,存放执行外部程序的相关文件代码。

定义一个Process执行外部程序的输出类

public class ExecResult { public string Output { get; set; } /// /// 程序正常执行后的错误输出,需要根据实际内容判断是否成功。如果Output为空但Error不为空,则基本可以说明发生了问题或错误,但是可以正常执行结束 /// public string Error { get; set; } /// /// 执行发生的异常,表示程序没有正常执行并结束 /// public Exception ExceptError { get; set; } }调用cmd执行命令行调用cmd直接执行

如下为执行cmd的帮助类,提供了异步Async方法、同步方法和一次执行多个命令的方法。主要代码部分都有注释。

获取Windows系统环境特殊文件夹路径的方法: Environment.GetFolderPath(Environment.SpecialFolder.SystemX86),获取C:\Windows\System32\/// /// 执行cmd命令 /// public static class ExecCMD { #region 异步方法 /// /// 执行cmd命令 返回cmd窗口显示的信息 /// 多命令请使用批处理命令连接符: /// /// ///执行的命令 ///工作目录 /// cmd命令执行窗口的输出 public static async Task RunAsync(string command,string workDirectory=null) { command = command.Trim().TrimEnd('&') + "&exit"; //说明:不管命令是否成功均执行exit命令,否则当调用ReadToEnd()方法时,会处于假死状态 string cmdFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "cmd.exe");// @"C:\Windows\System32\cmd.exe"; using (Process p = new Process()) { var result = new ExecResult(); try { p.StartInfo.FileName = cmdFileName; p.StartInfo.UseShellExecute = false; //是否使用操作系统shell启动 p.StartInfo.RedirectStandardInput = true; //接受来自调用程序的输入信息 p.StartInfo.RedirectStandardOutput = true; //由调用程序获取输出信息 p.StartInfo.RedirectStandardError = true; //重定向标准错误输出 p.StartInfo.CreateNoWindow = true; //不显示程序窗口 if (!string.IsNullOrWhiteSpace(workDirectory)) { p.StartInfo.WorkingDirectory = workDirectory; } p.Start();//启动程序 //向cmd窗口写入命令 p.StandardInput.WriteLine(command); p.StandardInput.AutoFlush = true; // 若要使用StandardError,必须设置ProcessStartInfo.UseShellExecute为false,并且必须设置 ProcessStartInfo.RedirectStandardError 为 true。 否则,从 StandardError 流中读取将引发异常。 //获取cmd的输出信息 result.Output = await p.StandardOutput.ReadToEndAsync(); result.Error = await p.StandardError.ReadToEndAsync(); p.WaitForExit();//等待程序执行完退出进程。应在最后调用 p.Close(); } catch (Exception ex) { result.ExceptError = ex; } return result; } } /// /// 执行多个cmd命令 返回cmd窗口显示的信息 /// 此处执行的多条命令并不是交互执行的信息,是多条独立的命令。也可以使用&连接多条命令为一句执行 /// ///执行的命令 /// cmd命令执行窗口的输出 /// 工作目录 public static async Task RunAsync(string[] commands,string workDirectory=null) { if (commands == null) { throw new ArgumentNullException(); } if (commands.Length == 0) { return default(ExecResult); } return await Task.Run(() => { commands[commands.Length - 1] = commands[commands.Length - 1].Trim().TrimEnd('&') + "&exit"; //说明:不管命令是否成功均执行exit命令 string cmdFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "cmd.exe");// @"C:\Windows\System32\cmd.exe"; using (Process p = new Process()) { var result = new ExecResult(); try { p.StartInfo.FileName = cmdFileName; p.StartInfo.UseShellExecute = false; //是否使用操作系统shell启动 p.StartInfo.RedirectStandardInput = true; //接受来自调用程序的输入信息 p.StartInfo.RedirectStandardOutput = true; //由调用程序获取输出信息 p.StartInfo.RedirectStandardError = true; //重定向标准错误输出 p.StartInfo.CreateNoWindow = true; //不显示程序窗口 if (!string.IsNullOrWhiteSpace(workDirectory)) { p.StartInfo.WorkingDirectory = workDirectory; } // 接受输出的方式逐次执行每条 //var output = string.Empty; var inputI = 1; p.OutputDataReceived += (sender, e) => { // cmd中的输出会包含换行;其他应用可以考虑接收数据是添加换行 Environment.NewLine result.Output +=$"{ e.Data}{Environment.NewLine}" ;// 获取输出 if (inputI >= commands.Length) { return; } if (e.Data.Contains(commands[inputI - 1])) { p.StandardInput.WriteLine(commands[inputI]); } inputI++; }; p.ErrorDataReceived+= (sender, e) => { result.Error += $"{ e.Data}{Environment.NewLine}";// 获取输出 if (inputI>= commands.Length) { return; } if (e.Data.Contains(commands[inputI - 1])) { p.StandardInput.WriteLine(commands[inputI]); } inputI++; }; p.Start();//启动程序 // 开始异步读取输出流 p.BeginOutputReadLine(); p.BeginErrorReadLine(); //向cmd窗口写入命令 p.StandardInput.WriteLine(commands[0]); p.StandardInput.AutoFlush = true; p.WaitForExit();//等待程序执行完退出进程。应在最后调用 p.Close(); } catch (Exception ex) { result.ExceptError = ex; } return result; } }); } #endregion /// /// 执行cmd命令 返回cmd窗口显示的信息 /// 多命令请使用批处理命令连接符: /// /// ///执行的命令 ///工作目录 public static ExecResult Run(string command, string workDirectory = null) { command = command.Trim().TrimEnd('&') + "&exit"; //说明:不管命令是否成功均执行exit命令,否则当调用ReadToEnd()方法时,会处于假死状态 string cmdFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "cmd.exe");// @"C:\Windows\System32\cmd.exe"; using (Process p = new Process()) { var result = new ExecResult(); try { p.StartInfo.FileName = cmdFileName; p.StartInfo.UseShellExecute = false; //是否使用操作系统shell启动,设置为false可以重定向输入输出错误流;同时会影响WorkingDirectory的值 p.StartInfo.RedirectStandardInput = true; //接受来自调用程序的输入信息 p.StartInfo.RedirectStandardOutput = true; //由调用程序获取输出信息 p.StartInfo.RedirectStandardError = true; //重定向标准错误输出 p.StartInfo.CreateNoWindow = true; //不显示程序窗口 if (!string.IsNullOrWhiteSpace(workDirectory)) { p.StartInfo.WorkingDirectory = workDirectory; } p.Start();//启动程序 //向cmd窗口写入命令 p.StandardInput.WriteLine(command); p.StandardInput.AutoFlush = true; //获取cmd的输出信息 result.Output = p.StandardOutput.ReadToEnd(); result.Error = p.StandardError.ReadToEnd(); p.WaitForExit();//等待程序执行完退出进程。应在最后调用 p.Close(); } catch (Exception ex) { result.ExceptError = ex; } return result; } } }调用cmd执行交互命令

很多情况下,命令的执行都需要输入交互信息。在C#调用cmd执行时,处理交互信息却并不是直接输入那么简单(可自行测试...)

cmd中执行获取输出一般意味着命令(或新程序)已经执行结束,而交互命令则需要未执行完等待输入。

因此,大多数情况下交互信息都会新开一个窗口进行输入,这就不受当前Process的控制了,通常是等待使用者输入。这时需要额外操作新开窗口输入信息。

当然,也会有在当前主线程等待输入的情况,这时直接输入就可以了。

总之,交互命令或信息的输入,要根据实际情况来出来,很难抽象为一个统一的方法。

下面以PostgreSQL的createuser命令(位于其bin目录)为例,借助Win32 API窗口句柄相关的EnumWindows方法遍历查找新开的cmd窗口,通过SendMessage输入密码和回车,最后关闭窗口(之前已经介绍过窗口句柄的操作,相关方法作为WndHelper帮助类直接使用,如有需要请查看之前文章,不再重复)。完成用户的新建过程。

由于是新打开了窗口,尝试连续输入、或异步读取输出都无法与新窗口交互,只能借助窗口句柄操作,也可以改为UI自动化输入信息。

PostgreSQL命令行创建用户和数据库,也可以查看之前的介绍。// 用户密码 var userName = "myuser1"; var userPwd = "mypwd1"; // bin路径 var psqlBinPath = Path.Combine("C:\PostgreSQL", "pgsql", "bin"); // 执行createuser.exe var result_cuser = await RunCMDPSqlCreateUserAsync(Path.Combine(psqlBinPath, "createuser.exe")+$" -P {userName}", userPwd);

如下为命令行执行时新窗口交互输入密码口令的实现。有打开窗口需要时间,因此查找窗口前等待一段时间,同时,输入口令和确认口令时也有个等待时间。

执行完成后需要关闭新开的窗口,否则会一直处于等待中。

需要注意的是,新开的窗口的标题应该包含执行的命令,但是其显示的内容命令中多了一个空格,因此,查找上也是额外处理的。

传入的命令C:\PostgreSQL\pgsql\bin\createuser.exe -P myuser1,在新窗口标题中变为了C:\PostgreSQL\pgsql\bin\createuser.exe -P myuser1。

/// /// 执行cmd命令行创建用户,交互输入密码 /// ///执行的命令 /// 交互输入的口令密码 /// cmd命令执行的输出 public static async System.Threading.Tasks.Task RunCMDPSqlCreateUserAsync(string createCommand, string userPwd) { createCommand = createCommand.Trim().TrimEnd('&'); string cmdFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "cmd.exe");// @"C:\Windows\System32\cmd.exe"; using (Process p = new Process()) { var result = new ExecResult(); try { p.StartInfo.FileName = cmdFileName; p.StartInfo.UseShellExecute = false; //是否使用操作系统shell启动 p.StartInfo.RedirectStandardInput = true; //接受来自调用程序的输入信息 p.StartInfo.RedirectStandardOutput = true; //由调用程序获取输出信息 p.StartInfo.RedirectStandardError = true; //重定向标准错误输出 p.StartInfo.CreateNoWindow = false; //需要显示程序窗口(操作新打开的确认口令窗口) p.Start();//启动程序 p.StandardInput.AutoFlush = false; //向cmd窗口写入命令 p.StandardInput.WriteLine(createCommand); p.StandardInput.Flush(); // 等待一会,等待新窗口打开 Thread.Sleep(500); // 命令行窗口包标题-P前包含两个空格,和传入的不符,因此无法查找到 @"管理员: C:\WINDOWS\system32\cmd.exe - C:\PostgreSQL\pgsql\bin\createuser.exe -P myuser".Contains(command); var windows = WndHelper.FindAllWindows(x => x.Title.Contains(@"C:\PostgreSQL\pgsql\bin\createuser.exe")); // x.Title.Contains(command) var window = windows[0]; WndHelper.SendText(window.Hwnd, userPwd); WndHelper.SendEnter(window.Hwnd); // 等待 Thread.Sleep(100); WndHelper.SendText(window.Hwnd, userPwd); WndHelper.SendEnter(window.Hwnd); // 等待 需要等待多一点再关闭,否则可能创建不成功 Thread.Sleep(500); // 处理完后关闭窗口,否则后面一直阻塞 WndHelper.CloseWindow(window.Hwnd); // 或者发送 exit 和回车 // 不能直接使用标准输出,会一直等待;需要关闭新打开的窗口(或执行完exti退出) result.Output = await p.StandardOutput.ReadToEndAsync(); result.Error = await p.StandardError.ReadToEndAsync(); p.WaitForExit();//等待程序执行完退出进程。应在最后调用 p.Close(); } catch (Exception ex) { result.ExceptError = ex; } return result; } }

执行结果如下:

为了实现这个交互查找了很多资料,后来在大佬的提醒下,显示窗口查看一下问题。从而确认是新开了窗口。

未显示窗口时查看异步输出信息,会一直等待没有确认口令的提示:

显示窗口时,可以看到新的窗口有提示确认口令的信息,这个信息和原Process接受到的是分开的,如果通过Process与此新窗口交互。

cmd命令作为参数执行时需要指定/K

在执行cmd命令时,也可以将命令作为cmd的启动参数执行。

但是,如果执行下面的命令,将会看到没有任何效果(只打开cmd窗口)。

Process.Start(@"C:\WINDOWS\system32\cmd.exe", "ping 127.0.0.1");

命令作为cmd的启动参数执行时,需要在最开始指定/K参数。

Process.Start(@"C:\WINDOWS\system32\cmd.exe", "/K ping 127.0.0.1");

将会正确执行。

或者,使用ProcessStartInfo对象。

Process process = new Process(); ProcessStartInfo startInfo = new ProcessStartInfo(); //startInfo.WindowStyle = ProcessWindowStyle.Hidden; startInfo.FileName = "cmd.exe"; startInfo.Arguments = "/K ping 127.0.0.1"; process.StartInfo = startInfo; process.Start();cmd /k 的含义是执行后面的命令,并且执行完毕后保留窗口。参考C#程序调用cmd.exe执行命令


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有