在 C# 中执行批处理文件
Posted
技术标签:
【中文标题】在 C# 中执行批处理文件【英文标题】:Executing Batch File in C# 【发布时间】:2021-11-24 01:56:35 【问题描述】:我正在尝试在 C# 中执行一个批处理文件,但我没有任何运气。
我在互联网上找到了多个这样做的例子,但它对我不起作用。
public void ExecuteCommand(string command)
int ExitCode;
ProcessStartInfo ProcessInfo;
Process Process;
ProcessInfo = new ProcessStartInfo("cmd.exe", "/c " + command);
ProcessInfo.CreateNoWindow = true;
ProcessInfo.UseShellExecute = false;
Process = Process.Start(ProcessInfo);
Process.WaitForExit();
ExitCode = Process.ExitCode;
Process.Close();
MessageBox.Show("ExitCode: " + ExitCode.ToString(), "ExecuteCommand");
命令字符串包含批处理文件的名称(存储在system32
)和它应该操作的一些文件。 (例如:txtmanipulator file1.txt file2.txt file3.txt
)。当我手动执行批处理文件时,它可以正常工作。
执行代码时,它给了我一个**ExitCode: 1** (Catch all for general errors)
我做错了什么?
【问题讨论】:
你没有显示command
是什么。如果它包含带空格的路径,则需要在它们周围加上引号。
@Jon 我已经做到了,这不是问题所在。感谢您的意见!
您的批处理文件中的某些内容是否失败?您可能希望为您的进程设置 WorkingDirectory(或任何该属性的名称)。
好吧,当我手动执行命令中的代码(开始 --> 运行)时,它运行正常。我现在添加了 WorkingDirectory 并将其设置为 system32,但我仍然得到 ErrorCode:1
添加这两条语句 ExitCode = Process.ExitCode;和 Process.Close();很有帮助。
【参考方案1】:
这应该可以。您可以尝试转储输出和错误流的内容,以了解发生了什么:
static void ExecuteCommand(string command)
int exitCode;
ProcessStartInfo processInfo;
Process process;
processInfo = new ProcessStartInfo("cmd.exe", "/c " + command);
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
// *** Redirect the output ***
processInfo.RedirectStandardError = true;
processInfo.RedirectStandardOutput = true;
process = Process.Start(processInfo);
process.WaitForExit();
// *** Read the streams ***
// Warning: This approach can lead to deadlocks, see Edit #2
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();
exitCode = process.ExitCode;
Console.WriteLine("output>>" + (String.IsNullOrEmpty(output) ? "(none)" : output));
Console.WriteLine("error>>" + (String.IsNullOrEmpty(error) ? "(none)" : error));
Console.WriteLine("ExitCode: " + exitCode.ToString(), "ExecuteCommand");
process.Close();
static void Main()
ExecuteCommand("echo testing");
* 编辑 *
鉴于您在下方评论中的额外信息,我能够重现该问题。似乎有一些安全设置会导致这种行为(尚未对此进行详细调查)。
如果批处理文件不在C:\Windows\System32
中,这确实有效。尝试将其移动到其他位置,例如您的可执行文件的位置。请注意,将自定义批处理文件或可执行文件保存在 Windows 目录中无论如何都是不好的做法。
* 编辑 2 *
turns out,如果同步读取流,可能会发生死锁,要么在 WaitForExit
之前同步读取,要么一个接一个地同时读取 stderr
和 stdout
。
如果改为使用异步读取方法,则不会发生这种情况,如下例所示:
static void ExecuteCommand(string command)
var processInfo = new ProcessStartInfo("cmd.exe", "/c " + command);
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
processInfo.RedirectStandardError = true;
processInfo.RedirectStandardOutput = true;
var process = Process.Start(processInfo);
process.OutputDataReceived += (object sender, DataReceivedEventArgs e) =>
Console.WriteLine("output>>" + e.Data);
process.BeginOutputReadLine();
process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) =>
Console.WriteLine("error>>" + e.Data);
process.BeginErrorReadLine();
process.WaitForExit();
Console.WriteLine("ExitCode: 0", process.ExitCode);
process.Close();
【讨论】:
谢谢!现在我实际上可以看到错误是什么。 “C:\Windows\System32\txtmanipulator.bat 不被识别为内部或外部命令、程序或批处理文件”(翻译自荷兰语)这很奇怪。因为当我从命令行运行 txtmanipulator 时,它会完美执行。 我能够重现您的问题,请查看答案的补充内容。 当我运行将 27 GB 数据库转储到转储文件的“pg_dump ... > dumpfile”时,此方法不适用 如何从标准输出/错误中获取数据以避免累积(假设批处理可以运行多年并且我想查看数据?) 使用异步读取方法(参见编辑 2)将允许您在读取一行后立即输出文本。【参考方案2】:System.Diagnostics.Process.Start("c:\\batchfilename.bat");
这个简单的行将执行批处理文件。
【讨论】:
如何传递参数并读取命令执行的结果? @JanatbekSharsheyev 看看这是不是你ask for... @JanatbekSharsheyev 您可以作为参数传递。请参见下面的示例 ProcessStartInfo info = new ProcessStartInfo("c:\\batchfilename.bat"); info.Arguments = "-参数"; Process.Start(信息)【参考方案3】:在 steinar 的大力帮助下,这对我有用:
public void ExecuteCommand(string command)
int ExitCode;
ProcessStartInfo ProcessInfo;
Process process;
ProcessInfo = new ProcessStartInfo(Application.StartupPath + "\\txtmanipulator\\txtmanipulator.bat", command);
ProcessInfo.CreateNoWindow = true;
ProcessInfo.UseShellExecute = false;
ProcessInfo.WorkingDirectory = Application.StartupPath + "\\txtmanipulator";
// *** Redirect the output ***
ProcessInfo.RedirectStandardError = true;
ProcessInfo.RedirectStandardOutput = true;
process = Process.Start(ProcessInfo);
process.WaitForExit();
// *** Read the streams ***
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();
ExitCode = process.ExitCode;
MessageBox.Show("output>>" + (String.IsNullOrEmpty(output) ? "(none)" : output));
MessageBox.Show("error>>" + (String.IsNullOrEmpty(error) ? "(none)" : error));
MessageBox.Show("ExitCode: " + ExitCode.ToString(), "ExecuteCommand");
process.Close();
【讨论】:
在我的例子中,一个批处理文件正在使用~%dp0
调用另一个批处理文件。添加ProcessInfo.WorkingDirectory
修复它。
如果直接调用BAT文件,为什么要传递command
?
@sfarbota BAT 文件的参数?
@sigod 我不确定你是在问我问题还是建议我的可能答案。是的,批处理文件可以带参数。但是,如果您建议 command
参数可能用于将参数发送到 BAT 文件,则此处的代码并非如此。实际上根本没有使用它。如果是的话,它可能应该被命名为arguments
。
@sfarbota 这是一个假设。顺便说一句,command
用于new ProcessStartInfo
调用。【参考方案4】:
它工作正常。我是这样测试的:
String command = @"C:\Doit.bat";
ProcessInfo = new ProcessStartInfo("cmd.exe", "/c " + command);
// ProcessInfo.CreateNoWindow = true;
我注释掉关闭窗口以便我可以看到它运行。
【讨论】:
感谢您提供的示例澄清了一些最初令人困惑的问题。将前面的示例转换为可重用的方法需要一些额外的步骤,并且前面示例中的“字符串命令”参数应该被命名为 args 或参数,因为这就是传递给它的内容。 恐怕它有效。 ProcessInfo 已初始化,但仍在等待启动的管道中。添加一行 var process = Process.Start(ProcessInfo);完成了这项工作。【参考方案5】:这里是示例 c# 代码,它将 2 个参数发送到 bat/cmd 文件以回答这个[问题][1]。
评论:如何传递参数并读取命令执行的结果?
/by [@Janatbek Sharsheyev][3]
这是对此 [answer][2] /by [@Brian Rasmussen][4] 的编辑选项 1:不隐藏控制台窗口、传递参数且不获取输出
using System;
using System.Diagnostics;
namespace ConsoleApplication
class Program
static void Main(string[] args)
System.Diagnostics.Process.Start(@"c:\batchfilename.bat", "\"1st\" \"2nd\"");
选项 2:隐藏控制台窗口、传递参数并获取输出
using System;
using System.Diagnostics;
namespace ConsoleApplication
class Program
static void Main(string[] args)
var process = new Process();
var startinfo = new ProcessStartInfo(@"c:\batchfilename.bat", "\"1st_arg\" \"2nd_arg\" \"3rd_arg\"");
startinfo.RedirectStandardOutput = true;
startinfo.UseShellExecute = false;
process.StartInfo = startinfo;
process.OutputDataReceived += (sender, argsx) => Console.WriteLine(argsx.Data); // do whatever processing you need to do in this handler
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
// C# 解码 bat 文件并运行传递参数: // 编辑 01/2022
using System;
using System.IO;
using System.Text;
using System.Diagnostics;
namespace ConsoleApplication
class Program
static void Main(string[] args)
String encodedString = @"QGVjaG8gb2ZmIAoKc2V0ICJ4PUZvbGRlciIKeGNvcHkgL3kgL3YgL2UgLlw
iJXglIlwqIFxcMTAuMC4wLjIwMFxkXAoKZm9yICUleSBpbiAoMjAyLDIwMy
wyMDQsMjA1KWRvICgKICAgICBuZXQgdXNlIFxcMTAuMC4wLiUlfnlcZSAiJ
X4xIiAvdXNlcjoiJX4yIgogICAgIGVjaG9cQ29weWluZyBmaWxlcyB0byBc
XDEwLjAuMC4lJX55XGVcCiAgICAgeGNvcHkgL3kgL3YgL2UgLlwiJXglIlw
qIFxcMTAuMC4wLiUlfnlcZVwKICAgICk=";
File.WriteAllBytes(@"z:\batchfilename.bat", Convert.FromBase64String(encodedString));
System.Diagnostics.Process.Start(@"z:\batchfilename.bat", "\"PassWord1\" \"User1\"");
/* bat file decoded:
@echo off
set "x=Folder"
xcopy /y /v /e .\"%x%"\* \\10.0.0.200\d\
for %%y in (202,203,204,205)do (
net use \\10.0.0.%%~y\e "%~1" /user:"%~2"
echo\Copying files to \\10.0.0.%%~y\e\
xcopy /y /v /e .\"%x%"\* \\10.0.0.%%~y\e\
)
Execute bat:
@"z:\batchfilename.bat", "\"PassWord1\" \"User1\""
Bat argument:
Argument %1 == PassWord1 Argument %2 == User1
*/
1.制作你的球棒并尽可能多地测试它
2.将代码转换为base64
3.在代码中使用 base64 字符串定义变量
4. 在运行时解码到预定义的适当位置以供执行
5.在解码路径上调用bat执行
6.如有必要,请传递您的论点
_ _ _
[1]: https://***.com/questions/60207122/passing-arguments-to-a-cmd-file-in-c-sharp/60211584#60211584
[2]: https://***.com/a/3742287/8177207
[3]: https://***.com/users/6097430/janatbek-sharsheyev
[4]: https://***.com/users/38206/brian-rasmussen
【讨论】:
【参考方案6】:下面的代码对我来说很好用
using System.Diagnostics;
public void ExecuteBatFile()
Process proc = null;
string _batDir = string.Format(@"C:\");
proc = new Process();
proc.StartInfo.WorkingDirectory = _batDir;
proc.StartInfo.FileName = "myfile.bat";
proc.StartInfo.CreateNoWindow = false;
proc.Start();
proc.WaitForExit();
ExitCode = proc.ExitCode;
proc.Close();
MessageBox.Show("Bat file executed...");
【讨论】:
我需要在 FileName 中分配整个路径才能使其工作(即使 WorkingDirectory 具有相同的根路径……)。如果我跳过根路径,我会得到没有这样的文件的异常 手动检查路径、正在组成的内容并检查它是否存在。这将有助于解决问题。【参考方案7】:using System.Diagnostics;
private void ExecuteBatFile()
Process proc = null;
try
string targetDir = string.Format(@"D:\mydir"); //this is where mybatch.bat lies
proc = new Process();
proc.StartInfo.WorkingDirectory = targetDir;
proc.StartInfo.FileName = "lorenzo.bat";
proc.StartInfo.Arguments = string.Format("10"); //this is argument
proc.StartInfo.CreateNoWindow = false;
proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; //this is for hiding the cmd window...so execution will happen in back ground.
proc.Start();
proc.WaitForExit();
catch (Exception ex)
Console.WriteLine("Exception Occurred :0,1", ex.Message, ex.StackTrace.ToString());
【讨论】:
我需要在 FileName 中分配整个路径才能使其工作(即使 WorkingDirectory 具有相同的根路径……)。如果我跳过根路径,我会得到没有这样的文件的异常【参考方案8】:您是否尝试过以管理员身份启动它?如果您使用 Visual Studio,请以管理员身份启动它,因为处理 .bat
文件需要这些权限。
【讨论】:
【参考方案9】:使用之前提出的解决方案,我一直在努力让多个 npm 命令循环执行并在控制台窗口上获取所有输出。
在我结合了之前的 cmets 的所有内容后,它终于开始工作了,但重新安排了代码执行流程。
我注意到事件订阅完成得太晚(在进程已经开始之后),因此没有捕获一些输出。
下面的代码现在执行以下操作:
-
在进程开始之前订阅事件,
因此确保不会遗漏任何输出。
进程一开始就开始读取输出。
代码已经针对死锁进行了测试,尽管它是同步的(同时执行一个进程),所以我无法保证如果并行运行会发生什么。
static void RunCommand(string command, string workingDirectory)
Process process = new Process
StartInfo = new ProcessStartInfo("cmd.exe", $"/c command")
WorkingDirectory = workingDirectory,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true
;
process.OutputDataReceived += (object sender, DataReceivedEventArgs e) => Console.WriteLine("output :: " + e.Data);
process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => Console.WriteLine("error :: " + e.Data);
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
Console.WriteLine("ExitCode: 0", process.ExitCode);
process.Close();
【讨论】:
【参考方案10】:我想要一些更直接可用的东西,而不需要在其中包含特定于组织的硬编码字符串值。我提供以下作为可直接重用的代码块。次要的缺点是在调用时需要确定并传递工作文件夹。
public static void ExecuteCommand(string command, string workingFolder)
int ExitCode;
ProcessStartInfo ProcessInfo;
Process process;
ProcessInfo = new ProcessStartInfo("cmd.exe", "/c " + command);
ProcessInfo.CreateNoWindow = true;
ProcessInfo.UseShellExecute = false;
ProcessInfo.WorkingDirectory = workingFolder;
// *** Redirect the output ***
ProcessInfo.RedirectStandardError = true;
ProcessInfo.RedirectStandardOutput = true;
process = Process.Start(ProcessInfo);
process.WaitForExit();
// *** Read the streams ***
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();
ExitCode = process.ExitCode;
MessageBox.Show("output>>" + (String.IsNullOrEmpty(output) ? "(none)" : output));
MessageBox.Show("error>>" + (String.IsNullOrEmpty(error) ? "(none)" : error));
MessageBox.Show("ExitCode: " + ExitCode.ToString(), "ExecuteCommand");
process.Close();
这样称呼:
// This will get the current WORKING directory (i.e. \bin\Debug)
string workingDirectory = Environment.CurrentDirectory;
// This will get the current PROJECT directory
string projectDirectory = Directory.GetParent(workingDirectory).Parent.FullName;
string commandToExecute = Path.Combine(projectDirectory, "TestSetup", "WreckersTestSetupQA.bat");
string workingFolder = Path.GetDirectoryName(commandToExecute);
commandToExecute = QuotesAround(commandToExecute);
ExecuteCommand(commandToExecute, workingFolder);
在此示例中,在 Visual Studio 2017 中,作为测试运行的一部分,我想在执行某些测试之前运行环境重置批处理文件。 (SpecFlow+xUnit)。我厌倦了单独手动运行 bat 文件的额外步骤,只想将 bat 文件作为 C# 测试设置代码的一部分运行。环境重置批处理文件将测试用例文件移回输入文件夹,清理输出文件夹等,以达到正确的测试开始状态以进行测试。 QuotesAround 方法只是在命令行周围加上引号,以防文件夹名称中有空格(“程序文件”,有人吗?)。它的全部内容是:私有字符串 QuotesAround(string input) return "\"" + input + "\"";
如果您的情况与我的情况相似,希望有人觉得这很有用并节省几分钟。
【讨论】:
【参考方案11】:使用CliWrap:
var result = await Cli.Wrap("foobar.bat").ExecuteBufferedAsync();
var exitCode = result.ExitCode;
var stdOut = result.StandardOutput;
【讨论】:
【参考方案12】:System.Diagnostics.Process.Start(BatchFileName, Parameters);
我知道这适用于批处理文件和参数,但不知道如何在 C# 中获取结果。 通常,输出是在批处理文件中定义的。
【讨论】:
以上是关于在 C# 中执行批处理文件的主要内容,如果未能解决你的问题,请参考以下文章