在C#中执行批处理文件


139

我正在尝试在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)

我究竟做错了什么?


4
你不显示什么command。如果它包含带空格的路径,则需要在其两边加上引号。
乔恩

@Jon我已经做到了,那不是问题。感谢您的输入!
Wessel T.

批处理文件中的某些内容出现故障?您可能要为您的流程设置WorkingDirectory(或任何称为该属性的)。
乔纳斯(Jonas)

好吧,当我手动执行命令(开始->运行)中的代码时,它可以正常运行。我现在添加了WorkingDirectory并将其设置为system32,但是我仍然收到ErrorCode:1
Wessel

Answers:


191

这应该工作。您可以尝试转储输出和错误流的内容,以便找出正在发生的情况:

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目录中仍然是不好的做法。

* EDIT 2 *原来的是,如果流被同步读取,可发生死锁,无论是由读取前同步WaitForExit或通过读取两个stderrstdout同步一前一后。

如果改为使用异步读取方法,则不应发生这种情况,如以下示例所示:

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();
}

1
谢谢!现在我实际上可以看到错误所在。“ C:\ Windows \ System32 \ txtmanipulator.bat无法识别为内部或外部命令,程序或批处理文件”(从荷兰语翻译而来),这很奇怪。因为当我从命令行运行txtmanipulator时,它执行得很好。
Wessel T.

2
我能够重现您的问题,请查看答案中的补充内容。
steinar

当我运行“ pg_dump ...> dumpfile”将27 GB的数据库转储到dumpfile时,这种方法不适用
Paul

我如何从标准输出/错误中获取数据以避免累积(假设批处理可以运行数年,并且我希望看到数据的来历?)
Dani

使用异步读取方法(请参见编辑2)将允许您在读取一行后立即输出文本。
steinar

132
System.Diagnostics.Process.Start("c:\\batchfilename.bat");

此简单行将执行批处理文件。


3
如何传递参数并读取命令执行的结果?
Janatbek Sharsheyev

@JanatbekSharsheyev看看这是您要的吗?
不是我

1
@JanatbekSharsheyev可以作为参数传递。请参见下面的示例ProcessStartInfo info = new ProcessStartInfo(“ c:\\ batchfilename.bat”); info.Arguments =“-参数”; Process.Start(信息)
sk1007

17

在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();
}

1
在我的情况下,一个批处理文件正在使用调用另一个批处理文件~%dp0。添加ProcessInfo.WorkingDirectory固定的。
奏鸣曲

1
command如果直接调用BAT文件,为什么要传递a ?
sfarbota 2014年

@sfarbota BAT文件的参数?
2015年

@sigod我不确定您是要问我一个问题还是建议一个可能的答案。是的,批处理文件可以带有参数。但是,如果您建议command使用参数来将参数发送到BAT文件,则此处的代码不是这样。实际上根本不使用它。如果是这样,则可能应该arguments改用它命名。
sfarbota 2015年

@sfarbota这是一个假设。顺便说一下,command用于new ProcessStartInfo通话中。
2015年

13

工作正常。我这样测试:

String command = @"C:\Doit.bat";

ProcessInfo = new ProcessStartInfo("cmd.exe", "/c " + command);
// ProcessInfo.CreateNoWindow = true;

我评论说关闭了窗口,以便可以看到它正在运行。


感谢您提供的示例,该示例澄清了一些最初令人困惑的观点。需要花费一些额外的步骤才能将先前的示例转换为可重用的方法,并且先前示例中的“字符串命令”参数应已命名为args或参数,因为这就是其中传递的内容。
Developer63

7

这是示例c#代码,正在向bat / cmd文件发送2个参数以回答此问题

注释:如何传递参数并读取命令执行的结果?

/ by @Janatbek Sharsheyev

选项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();
        }
    }
}


3

下面的代码对我来说很好

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中分配WHOLE路径,以使其正常工作(即使WorkingDirectory具有相同的根路径…)。如果我跳过根路径,我会得到一个例外,那就是没有这样的文件
Hawlett

检查路径,正在构成的内容并进行检查,是否存在或不手动。这将有助于找出问题所在。
Anjan Kant

2
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中分配WHOLE路径,以使其正常工作(即使WorkingDirectory具有相同的根路径…)。如果我跳过根路径,我会得到一个例外,那就是没有这样的文件
Hawlett

1

您是否尝试过以管理员身份启动它?如果使用Visual Studio,请以管理员身份启动,因为使用.bat文件需要这些特权。


0

我想要在没有组织特定的硬编码字符串值的情况下更直接可用的东西。我提供以下代码作为可直接重用的代码块。较小的缺点是在拨打电话时需要确定并传递工作文件夹。

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#测试设置代码的一部分运行。环境重置批处理文件将测试用例文件移回输入文件夹,清理输出文件夹等,以进入正确的测试开始状态以进行测试。如果文件夹名称中有空格(“ Program Files”,有人吗?),QuotesAround方法只是在命令行两边加上引号。它的全部内容是:私有字符串QuotesAround(字符串输入){返回“ \”“ +输入+” \“”;}

如果您的情况类似于我的情况,希望有人会觉得有用,并节省几分钟。


0

使用先前提出的解决方案,我一直努力使多个npm命令循环执行,并在控制台窗口上获取所有输出。

在我合并了之前的注释中的所有内容之后,它终于开始工作了,但是重新安排了代码执行流程。

我注意到的是,事件订阅完成得太晚了(该过程已经开始之后),因此没有捕获到某些输出。

现在,下面的代码执行以下操作:

  1. 在过程开始之前订阅事件,因此确保没有丢失任何输出。
  2. 处理开始后,即开始从输出读取。

该代码已针对死锁进行了测试,尽管它是同步的(当时是一个进程执行),所以我不能保证如果并行运行该代码会发生什么情况。

    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();
    }

0

使用CliWrap

var result = await Cli.Wrap("foobar.bat").ExecuteBufferedAsync();

var exitCode = result.ExitCode;
var stdOut = result.StandardOutput;

-1

System.Diagnostics.Process.Start(BatchFileName, Parameters);

我知道这将适用于批处理文件和参数,但是不知道如何在C#中获得结果。通常,输出在批处理文件中定义。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.