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

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
简介: C#执行外部程序用到的是Process进程类,打开一个进程,可以指定进程的启动信息StartInfo(启动的程序名、输入输出是否重定向、是否显示UI界面、一些必要参数等)...

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

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

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

需要添加命名空间System.Diagnostics

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

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

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

调用cmd执行命令行

调用cmd直接执行

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

获取Windows系统环境特殊文件夹路径的方法: Environment.GetFolderPath(Environment.SpecialFolder.SystemX86),获取 C:\Windows\System32\
/// <summary>
/// 执行cmd命令
/// </summary>
public static class ExecCMD
{
    #region 异步方法
    /// <summary>
    /// 执行cmd命令 返回cmd窗口显示的信息
    /// 多命令请使用批处理命令连接符:
    /// <![CDATA[
    /// &:同时执行两个命令
    /// |:将上一个命令的输出,作为下一个命令的输入
    /// &&:当&&前的命令成功时,才执行&&后的命令
    /// ||:当||前的命令失败时,才执行||后的命令]]>
    /// </summary>
    ///<param name="command">执行的命令</param>
    ///<param name="workDirectory">工作目录</param>
    /// <returns>cmd命令执行窗口的输出</returns>
    public static async Task<ExecResult> 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;
        }
    }

    /// <summary>
    /// 执行多个cmd命令 返回cmd窗口显示的信息
    /// 此处执行的多条命令并不是交互执行的信息,是多条独立的命令。也可以使用&连接多条命令为一句执行
    /// </summary>
    ///<param name="command">执行的命令</param>
    /// <returns>cmd命令执行窗口的输出</returns>
    /// <returns>工作目录</returns>
    public static async Task<ExecResult> 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
    /// <summary>
    /// 执行cmd命令 返回cmd窗口显示的信息
    /// 多命令请使用批处理命令连接符:
    /// <![CDATA[
    /// &:同时执行两个命令
    /// |:将上一个命令的输出,作为下一个命令的输入
    /// &&:当&&前的命令成功时,才执行&&后的命令
    /// ||:当||前的命令失败时,才执行||后的命令]]>
    /// </summary>
    ///<param name="command">执行的命令</param>
    ///<param name="workDirectory">工作目录</param>
    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

/// <summary>
/// 执行cmd命令行创建用户,交互输入密码
/// </summary>
///<param name="createCommand">执行的命令</param>
/// <param name="userPwd">交互输入的口令密码</param>
/// <returns>cmd命令执行的输出</returns>
public static async System.Threading.Tasks.Task<ExecResult> 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 的含义是执行后面的命令,并且执行完毕后保留窗口。

参考

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
消息中间件 安全 API
C#实现操作Windows窗口句柄:SendMessage/PostMessage发送系统消息、事件和数据【窗口句柄总结之二】
SendMessage/PostMessage API 可以实现发送系统消息,这些消息可以定义为常见的鼠标或键盘事件、数据的发送等各种系统操作......
4100 1
C#实现操作Windows窗口句柄:SendMessage/PostMessage发送系统消息、事件和数据【窗口句柄总结之二】
|
21天前
|
SQL 存储 数据库连接
C#编程与数据库交互的实现
【4月更文挑战第20天】C#与数据库交互是现代软件开发的关键,涉及数据库连接、数据操作和访问方式。使用ADO.NET建立连接,执行SQL实现读取、插入、更新和删除数据。可通过直接SQL或数据访问对象进行操作。注意性能优化,使用连接池,处理异常,确保安全,以提升应用性能和稳定性。
|
21天前
|
JavaScript C#
C#winForm程序与html JS交互调用
C#winForm程序与html JS交互调用
|
21天前
|
设计模式 C#
36.c#:如何设置MDL窗口
36.c#:如何设置MDL窗口
18 1
|
21天前
|
C# 开发者
35.c#:winform窗口
35.c#:winform窗口
13 1
|
21天前
|
开发框架 .NET C#
无标题自用临时文档.C# | python交互
无标题自用临时文档.C# | python交互
97 0
|
7月前
|
开发框架 自然语言处理 文字识别
一款C#开发的窗口文本提取开源软件
一款C#开发的窗口文本提取开源软件
51 2
|
C# Windows
推荐一个C#开发的窗口扩展菜单,支持系统所有窗口
一个C#开发的窗口扩展项目,采用.NET Framework 4.0开发,支持Windows Xp以及更高版本的系统,同时支持命令模式,可供代码调用。
99 0
推荐一个C#开发的窗口扩展菜单,支持系统所有窗口
|
SQL 数据库连接 数据库
C#常见控件与SQL Sever数据库交互
首先,我们采用DataSet作为临时的数据库,这样会比较好
|
Rust 测试技术 API
【Rust 实战】Rust与C#交互-生成DLL库
【Rust 实战】Rust与C#交互-生成DLL库
【Rust 实战】Rust与C#交互-生成DLL库