Выполнение командного файла в 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: без скрытия окна консоли, передача аргументов и без получения выходных данных
- Это изменение из этого ответа / от Brian Rasmussen
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 в цикле и получить все выходные данные в окне консоли.
Наконец, он начал работать после того, как я объединил все из предыдущих комментариев, но перестроил поток выполнения кода.
Я заметил, что подписка на события была выполнена слишком поздно (после того, как процесс уже начался), и поэтому некоторые выходные данные не были захвачены.
Код ниже теперь делает следующее:
- Подписывается на события до запуска процесса, что гарантирует отсутствие пропущенных выходных данных.
- Начинает чтение с выходов, как только процесс запущен.
Код был протестирован на наличие взаимоблокировок, хотя он является синхронным (выполнение одного процесса за раз), поэтому я не могу гарантировать, что произойдет, если это будет выполняться параллельно.
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#. Обычно выходные данные определяются в командном файле.