Выполнение командного файла в C#

Я пытаюсь выполнить командный файл в 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)

Что я делаю неправильно?

7 ответов

Решение

Это должно работать. Вы можете попытаться вывести содержимое потоков вывода и ошибок, чтобы выяснить, что происходит:

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 или читая оба 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();
}
System.Diagnostics.Process.Start("c:\\batchfilename.bat");

эта простая строка выполнит командный файл.

После некоторой большой помощи от 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();
}

Работает нормально. Я проверил это так:

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

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

Я прокомментировал выключение окна, чтобы я мог ВИДЕТЬ, что это работает.

Вот пример кода C#, который отправляет 2 параметра в файл bat / cmd для ответа на этот вопрос.

Комментарий: как передать параметры и прочитать результат выполнения команды?

/ Автор 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();
        }
    }
}

Ниже код работал нормально для меня

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

Вы пытались запустить его как администратор? Запустите Visual Studio как администратор, если вы его используете, потому что работа с .bat Файлы требуют этих привилегий.

С помощью ранее предложенных решений я изо всех сил пытался выполнить несколько команд 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();
    }

Использование CliWrap:

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

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

Я хотел что-то более удобное для использования без жестко закодированных строковых значений для конкретной организации. Я предлагаю следующее как кусок кода многократного использования. Незначительным недостатком является необходимость определить и передать рабочую папку при выполнении вызова.

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 просто помещает кавычки вокруг командной строки на случай, если в именах папок есть пробелы ("Program Files", кто-нибудь?). Все, что в нем есть, это: приватная строка QuotesAround(ввод строки) {return "\"" + input + "\"";}

Надеюсь, некоторые найдут это полезным и сэкономят несколько минут, если ваш сценарий похож на мой.

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

Я знаю, что это будет работать для командного файла и параметров, но не знаю, как получить результаты в C#. Обычно выходные данные определяются в командном файле.

Другие вопросы по тегам