Виконання пакетного файлу в C #


140

Я намагаюся виконати пакетний файл у 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 Я це зробив, це не проблема. Дякуємо за ваш внесок!
Вессел Т.

Невдало щось у вашому пакетному файлі? Ви можете встановити для свого процесу WorkingDirectory (або все, що називається).
Йонас

Добре, коли я виконую код в команді вручну (Пуск -> Виконати), він працює правильно. Я додав WorkingDirectory зараз і встановив його на system32, але я все одно отримую ErrorCode: 1
Wessel T.

Відповіді:


192

Це має спрацювати. Ви можете спробувати скинути вміст потоків виводу та помилок, щоб дізнатися, що відбувається:

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

1
Дякую! тепер я насправді бачу, в чому помилка. "C: \ Windows \ System32 \ txtmanipulator.bat не розпізнається як внутрішня чи зовнішня команда, програма чи пакетний файл" (Переклад з голландської). Що не дивно. Тому що, коли я запускаю txtmanipulator з командного рядка, він виконує ідеально.
Вессель Т.

2
Я зміг відтворити вашу проблему, ознайомтеся з додатком до відповіді.
стейнар

Цей підхід не застосовується, коли я запускаю "pg_dump ...> dumpfile", який скидає базу даних на 27 ГБ у dumpfile
Пол

Як я можу захопити дані зі стандартного виводу / помилки, щоб уникнути накопичення (враховуючи, що пакет може працювати роками, і я хочу бачити дані, як вони з'являються?)
Дани,

Використання асинхронних методів читання (див. Редагування 2) дозволить вам виводити текст, як тільки прочитаний рядок.
steinar

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

цей простий рядок виконає пакетний файл.


3
як я можу передавати параметри і читати результат виконання команди?
Янатбек Шаршеєв

@JanatbekSharsheyev Подивіться, чи це ви просите ...
Це не я

1
@JanatbekSharsheyev ви можете передавати як аргументи. Див. Нижче приклад Інформація ProcessStartInfo = new ProcessStartInfo ("c: \\ batchfilename.bat"); info.Аргументи = "-параметр"; Process.Start (інформація)
sk1007

17

Після чудової допомоги від стейнара це працювало для мене:

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
Навіщо передавати a, commandякщо ви викликаєте файл BAT безпосередньо?
sfarbota

@sfarbota Аргументи для файлу BAT?
sigod

@sigod Я не впевнений, ви ставите мені запитання чи пропонуєте можливу відповідь на мою. Так, пакетні файли можуть приймати аргументи. Але якщо ви припускаєте, що commandпараметри можуть бути використані для надсилання аргументів до файлу BAT, тут не показано код. Він взагалі не використовується. І якби це було, його, мабуть, слід було б назвати argumentsзамість цього.
sfarbota

@sfarbota Це було припущення. До речі, commandвикористовується у new ProcessStartInfoвиклику.
сигід

13

Це чудово працює. Я перевірив це так:

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

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

Я прокоментував вимкнення вікна, щоб побачити, як воно працює.


Дякую за приклад, який прояснив пару спочатку заплутаних моментів. Потрібно виконати кілька додаткових кроків, щоб перетворити попередні приклади в метод багаторазового використання, і параметр "string command" у попередніх прикладах повинен був бути названий аргументами або параметрами, оскільки саме це передається в ньому.
Developer63

7

Ось зразок c # коду, який надсилає 2 параметри у файл bat / cmd для відповіді на це запитання .

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

/ автор @Janatbek Шаршеєв

Варіант 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

Перевірте шлях, що складається і перевірте, чи існує він чи не вручну. Це допоможе з’ясувати проблему.
Анян Кант

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

Сподіваємось, деякі знайдуть це корисним і заощадять кілька хвилин, якщо ваш сценарій схожий на мій.


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 #. Зазвичай виходи визначаються у пакетному файлі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.