如何通过输入流手动向cmd.exe进程提供命令
本文关键字:进程 exe 命令 cmd 何通过 输入流 | 更新日期: 2023-09-27 17:49:14
这个问题听起来有点晦涩。下面是稍微长一点的版本:
我需要让主循环等待用户输入,还需要运行一个进程,等待来自用户输入要发送到的流的输入。
完整的故事:我正在构建一个Cmd模拟器,起初一切看起来都很好:用户输入一个命令,它被回显到输出区域,处理和StdOut和StdErrOut被捕获,并添加到输出文本框。
唯一的问题是,由于cmd进程是为每个命令单独创建和启动的,因此没有保留任何状态。不包括变量、代码页和工作目录等。
所以我决定发明一个小技巧:输入左括号或右括号开始和停止收集命令,而不是执行它们。在右括号之后,processBatch方法中使用命令列表('batch')将它们全部提供给cmd进程,以查看其重定向输入。工作得很好。
唯一的问题是,很明显,现在我得到了状态,但失去了即时响应,所以任何错误都不会弹出,直到运行批处理。
所以我决定把好的部分结合起来,嗯,当我意识到要保持两个循环工作时,我知道我会遇到麻烦。等待,我必须使用线程。这是我多年没做过的事了。
在布局中,我选择main()循环等待用户输入,startCMDtask()在任务中运行startCMD()。在这里,输入流被扫描,直到它有数据,然后cmd进程将处理它们。
但是行不通。
List<string> batch = new List<string>();
public volatile string output = "+";
public volatile string outputErr = "-";
Process CMD;
Task cmdTask;
volatile Queue<string> cmdQueue = new Queue<string>();
volatile public bool CMDrunning = false;
这很好
private void processBatch()
{
Process p = new Process();
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = "cmd.exe";
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
info.RedirectStandardInput = true;
info.UseShellExecute = false;
p.StartInfo = info;
p.Start();
using (StreamWriter sw = p.StandardInput)
{
if (sw.BaseStream.CanWrite)
foreach(string line in batch) sw.WriteLine(line);
}
output = "^"; outputErr = "~";
try { output = p.StandardOutput.ReadToEnd(); } catch { }
try { outputErr = p.StandardError.ReadToEnd(); } catch { }
try { p.WaitForExit(); } catch { }
tb_output.AppendText(output + "'r'n" + outputErr + "'r'n");
}
这些不完全,但几乎…
private void setupCMD()
{
CMD = new Process();
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = "cmd.exe";
// info.Arguments = "/K"; // doesn't make a difference
info.CreateNoWindow = true;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
info.RedirectStandardInput = true;
info.UseShellExecute = false;
CMD.StartInfo = info;
}
private void startCMDtask()
{
var task = Task.Factory.StartNew(() => startCMD());
cmdTask = task;
}
private void startCMD()
{
try { CMD.Start(); CMDrunning = true; }
catch { output = "Error starting cmd process.'r'n"; CMDrunning = false; }
using (StreamWriter sw = CMD.StandardInput)
{
if (sw.BaseStream.CanWrite)
do {
try
{
string cmd = cmdQueue.Dequeue();
if (cmd != null & cmd !="")
{
sw.WriteLine(cmd);
processOutputStreams();
}
}
catch {}
} while (CMDrunning);
}
private void processOutputStreams()
{
string newOutput = ""; string newOutputErr = "";
while (CMD.StandardOutput.Peek() > 0)
newOutput += (char)(CMD.StandardOutput.Read());
newOutput += "!?"; // at this point stdout is correctly captured (1)
try {
while (CMD.StandardError.Peek() > 0) // from here execution jumps away (2)
{ newOutputErr += (char)(CMD.StandardError.Read()); }
} catch {
newOutputErr = "?"; // never comes here
}
lock (output) // no noticable difference
lock (outputErr) //
{ // if I jump here (3) from (1) the result is displayed
// but not if i comment out the 2nd while loop (2)
if (newOutput != null & newOutput != "") output += newOutput + "'r'n";
if (newOutputErr != null & newOutputErr != "") outputErr += newOutputErr + "'r'n";
}
}
这是主线程中输入处理器的调用:
lock (cmdQueue) cmdQueue.Enqueue(cmd);
我不知道哪一部分是问题:进程,cmd shell,输入流,输出流,线程,锁或所有它轮流…??
我终于把它修好了。我在代码示例中描述的不稳定行为的原因是3个流没有以异步方式访问。
为了纠正,我放弃了processOutput函数,并将其替换为流程本身触发的两个调用。MS文档给出了一个很好的例子
我还做了StreamWriter同步,这也为进程和整个任务提供了信息。
下面是新代码:
private void startCMDtask()
{
var task = Task.Factory.StartNew(() => startCMD());
cmdTask = task;
}
private async void startCMD()
{
try { CMD.Start(); CMDrunning = true; }
catch { cmdErrOutput.Append("'r'nError starting cmd process.");
CMDrunning = false; }
CMD.BeginOutputReadLine();
CMD.BeginErrorReadLine();
using (StreamWriter sw = CMD.StandardInput)
{
if (sw.BaseStream.CanWrite)
do {
try
{
string cmd = cmdQueue.Dequeue();
if (cmd != null & cmd !="") await sw.WriteLineAsync(cmd);
}
catch { }
} while (CMDrunning);
try { CMD.WaitForExit(); }
catch { cmdErrOutput.Append("WaitForExit Error.'r'n"); }
}
}
这个进程现在是这样设置的:
private void setupCMD()
{
CMD = new Process();
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = "cmd.exe";
info.CreateNoWindow = true;
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
info.RedirectStandardInput = true;
info.UseShellExecute = false;
CMD.OutputDataReceived += new DataReceivedEventHandler(cmdOutputDataHandler);
CMD.ErrorDataReceived += new DataReceivedEventHandler(cmdErrorDataHandler);
cmdOutput = new StringBuilder();
cmdErrOutput = new StringBuilder();
CMD.StartInfo = info;
}
下面是输出处理程序:
private static void cmdOutputDataHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
if (!String.IsNullOrEmpty(outLine.Data))
{ // Add the text to the collected output.
cmdOutput.Append(Environment.NewLine + outLine.Data);
}
}
private static void cmdErrorDataHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
if (!String.IsNullOrEmpty(outLine.Data))
{ // Add the text to the collected error output.
cmdErrOutput.Append(Environment.NewLine + outLine.Data);
}
}
在用户输入处理结束时,输入队列是这样被划分的,输出是这样被获取的:
cmdUnDoStack.Push(cmd);
Application.DoEvents();
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => updateOutputArea(uiScheduler));
使用这个小例程:
private void updateOutputArea(TaskScheduler uiScheduler)
{
Task.Factory.StartNew(() =>
{
tb_output.AppendText(cmdOutput + "'r'n" + cmdErrOutput + "'r'n");
cmdOutput.Clear();
cmdErrOutput.Clear();
}, System.Threading.CancellationToken.None, TaskCreationOptions.None, uiScheduler);
}
现在对于一些好的旧命令,如CLS或COLOR需要特殊处理。: -)