Выполнение командного файла на 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)

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


person Wessel T.    schedule 01.04.2011    source источник
comment
Вы не показываете, что такое command. Если он содержит пути с пробелами, вам нужно будет заключить их в кавычки.   -  person Jon    schedule 02.04.2011
comment
@Jon Я сделал это, проблема не в этом. Спасибо за ваш вклад!   -  person Wessel T.    schedule 02.04.2011
comment
Что-то не так в вашем командном файле? Возможно, вы захотите установить WorkingDirectory (или как там это свойство называется) для вашего процесса.   -  person Jonas    schedule 02.04.2011
comment
Что ж, когда я выполняю код в команде вручную (Пуск - ›Выполнить), он работает правильно. Я добавил WorkingDirectory сейчас и установил его на system32, но я все еще получаю ErrorCode: 1   -  person Wessel T.    schedule 02.04.2011
comment
Добавление этих двух операторов ExitCode = Process.ExitCode; и Process.Close (); было очень полезно.   -  person octopusgrabbus    schedule 03.06.2021


Ответы (12)


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

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 * Это оказывается, что если потоки читаются синхронно, может возникнуть тупик, либо из-за синхронного чтения до 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();
}
person steinar    schedule 01.04.2011
comment
Спасибо! теперь я действительно вижу, в чем ошибка. C: \ Windows \ System32 \ txtmanipulator.bat не распознается как внутренняя или внешняя команда, программа или пакетный файл (перевод с голландского), что странно. Потому что, когда я запускаю txtmanipulator из командной строки, он отлично работает. - person Wessel T.; 02.04.2011
comment
Мне удалось воссоздать вашу проблему, посмотрите дополнение к ответу. - person steinar; 02.04.2011
comment
Этот подход неприменим, когда я запускаю pg_dump ... ›dumpfile, который выгружает базу данных размером 27 ГБ в файл dumpfile. - person Paul; 29.10.2012
comment
Как я могу получить данные из стандартного вывода / ошибки, чтобы избежать накопления (учитывая, что пакет может работать годами, и я хочу видеть данные по мере их поступления?) - person Dani; 15.02.2015
comment
Использование методов асинхронного чтения (см. Правку 2) позволит вам выводить текст, как только строка будет прочитана. - person steinar; 25.08.2015
comment
если bat-файл включает в себя «паузу», как это может быть передано… кажется, что он заморожен на неопределенный срок. потому что он не отвечает ни на один ввод - person user1161137; 25.06.2019
comment
@ user1161137 Если командный файл записывает какой-то результат, который вы ожидаете перед остановкой (например, нажмите любую клавишу, чтобы продолжить), я бы прочитал файл до тех пор, пока это не произойдет, а затем отправлю ожидаемые ключи в процесс. Запись в стандартный ввод процесса описана здесь - person steinar; 27.06.2019

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

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

person T.S.Sathish    schedule 12.04.2012
comment
как передать параметры и прочитать результат выполнения команды? - person Janatbek Orozaly; 09.12.2017
comment
@JanatbekSharsheyev Посмотрите, если это вы просите ... - person It Wasn't Me; 14.02.2020
comment
@JanatbekSharsheyev можно передавать в качестве аргументов .. См. Ниже пример ProcessStartInfo info = new ProcessStartInfo (c: \\ batchfilename.bat); info.Arguments = -параметр; Process.Start (информация) - person sk1007; 16.03.2020

После большой помощи 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();
}
person Wessel T.    schedule 02.04.2011
comment
В моем случае пакетный файл вызывал другой пакетный файл с использованием ~%dp0. Добавление ProcessInfo.WorkingDirectory исправило это. - person Sonata; 28.05.2014
comment
Зачем передавать command, если вы вызываете файл BAT напрямую? - person sfarbota; 07.07.2014
comment
@sfarbota Аргументы в пользу BAT файла? - person sigod; 25.03.2015
comment
@sigod Я не уверен, задаете ли вы мне вопрос или предлагаете возможный ответ на мой. Да, командные файлы могут принимать аргументы. Но если вы предполагаете, что параметры command могут использоваться для отправки аргументов в файл BAT, это не то, что показывает здесь код. Фактически он вообще не используется. И если бы это было так, его, вероятно, следовало бы назвать arguments. - person sfarbota; 01.04.2015
comment
@sfarbota Это было предположение. Кстати, command используется в new ProcessStartInfo вызове. - person sigod; 05.04.2015
comment
@sigod Верно. Вот что я получаю за поспешный ответ на комментарий, сделанный 8 месяцев назад. Зная это, имеет смысл использовать command для динамической отправки аргументов в пакетный файл. Но я по-прежнему считаю, что его следует переименовать в arguments, чтобы избежать путаницы! ;) - person sfarbota; 06.04.2015
comment
@sfarbota Я только что попытался предложить исправление, но сообщество его отклонило. : | - person sigod; 15.04.2015
comment
@sigod Хм, странно. Я только что отправил им еще одну правку, так что посмотрим, понравится ли она им во второй раз. - person sfarbota; 16.04.2015
comment
@Wessel T. process.WaitForExit (); следует проверить на null, поскольку он может вернуть null = ›описание метода .start гласит: ... или null, если ресурс процесса не запущен (например, если существующий процесс используется повторно)! - person Christian Casutt; 17.06.2015
comment
Это было полезно для меня, чтобы протестировать некоторые из этих функций (мне было интересно, как это реализовать). Так что имейте значок "Хороший ответ". - person ouflak; 07.05.2017
comment
В этом коде можно использовать Path.Combine, чтобы избежать всех этих обратных косых черт. - person Sebastian; 31.05.2017

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

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

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

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

person Steve Wellens    schedule 01.04.2011
comment
Спасибо за пример, который прояснил пару изначально запутанных моментов. Требуется несколько дополнительных шагов, чтобы превратить предыдущие примеры в метод многократного использования, а параметр строковой команды в предыдущих примерах должен был называться args или parameters, как то, что передается в нем. - person Developer63; 31.01.2019
comment
Боюсь, это работает. ProcessInfo инициализируется, но все еще находится в конвейере, ожидая запуска. Добавляем строку var process = Process.Start (ProcessInfo); сделал свою работу. - person Venugopal M; 06.01.2021

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

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

/ от @ Джанатбека Шаршеева

Вариант 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();
        }
    }
}

person It Wasn't Me    schedule 13.02.2020

Код ниже работал у меня нормально

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...");
}
person Anjan Kant    schedule 29.07.2016
comment
Мне нужно было назначить ВСЕ путь в FileName, чтобы он работал (даже если у WorkingDirectory тот же корневой путь…). Если я пропущу корневой путь, я получаю исключение, что такого файла нет - person Hawlett; 20.11.2019
comment
Проверьте путь, который составляет, и проверьте его, существует или нет вручную. Это поможет разобраться в проблеме. - person Anjan Kant; 22.11.2019

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

person Kristifor    schedule 17.04.2014

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

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 +" \ "";}

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

person Developer63    schedule 31.01.2019

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

Используя CliWrap:

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

var exitCode = result.ExitCode;
var stdOut = result.StandardOutput;
person Tyrrrz    schedule 15.04.2020

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

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

person macunix    schedule 07.08.2019

person    schedule
comment
Мне нужно было назначить ВСЕ путь в FileName, чтобы он работал (даже если у WorkingDirectory тот же корневой путь…). Если я пропущу корневой путь, я получаю исключение, что такого файла нет - person Hawlett; 20.11.2019