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

141

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

Ответы:

194

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

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();
}
Steinar
источник
1
Благодарность! теперь я действительно вижу, в чем ошибка. "C: \ Windows \ System32 \ txtmanipulator.bat не распознается как внутренняя или внешняя команда, программа или пакетный файл" (в переводе с голландского) Что странно. Потому что, когда я запускаю txtmanipulator из командной строки, он отлично работает.
Wessel T.
2
Мне удалось воссоздать вашу проблему, посмотрите дополнение к ответу.
Steinar 01
Этот подход неприменим, когда я запускаю «pg_dump ...> dumpfile», который выгружает базу данных размером 27 ГБ в dumpfile
Пол
Как я могу получить данные из стандартного вывода / ошибки, чтобы избежать накопления (учитывая, что пакет может работать годами, и я хочу видеть данные по мере их поступления?)
Дани
Использование методов асинхронного чтения (см. Правку 2) позволит вам выводить текст, как только строка будет прочитана.
Steinar
134
System.Diagnostics.Process.Start("c:\\batchfilename.bat");

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

TSSathish
источник
3
как передать параметры и прочитать результат выполнения команды?
Жанатбек Шаршеев
@JanatbekSharsheyev Посмотри, просишь ли ты об этом ...
Это был не я
2
@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 напрямую?
sfarbota 07
@sfarbota Аргументы в пользу BAT файла?
sigod
@sigod Я не уверен, задаете ли вы мне вопрос или предлагаете возможный ответ на мой. Да, командные файлы могут принимать аргументы. Но если вы предполагаете, что commandпараметры могут использоваться для отправки аргументов в файл BAT, это не то, что здесь показывает код. Фактически он вообще не используется. И если бы это было так, вероятно, его следовало бы назвать argumentsвместо этого.
sfarbota 01
@sfarbota Это было предположение. Кстати, commandиспользуется в new ProcessStartInfoзвонке.
sigod 05
13

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

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

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

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

Стив Велленс
источник
Спасибо за пример, который прояснил пару изначально запутанных моментов. Требуется несколько дополнительных шагов, чтобы превратить предыдущие примеры в метод многократного использования, а параметр «строковая команда» в предыдущих примерах должен был называться args или parameters, как то, что передается в нем.
Developer63
9

Вот пример кода 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();
        }
    }
}

Это был не я
источник
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, чтобы он работал (даже если у 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());
    }
}
Lijo
источник
Мне нужно было назначить ВСЕ путь в FileName, чтобы он работал (даже если у 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 #. Пакетный файл сброса среды перемещает файлы тестовых примеров обратно во входную папку, очищает выходные папки и т. Д., Чтобы перейти в правильное начальное состояние теста для тестирования. Метод QuotesAround просто помещает в кавычки командную строку на случай, если в именах папок есть пробелы («Program Files», кто-нибудь?). Все, что в нем, это: приватная строка QuotesAround (строковый ввод) {return "\" "+ input +" \ "";}

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

Разработчик63
источник
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;
Tyrrrz
источник
-1

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

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

Macunix
источник