Ejecutar archivo por lotes en C #


140

Estoy tratando de ejecutar un archivo por lotes en C #, pero no tengo suerte al hacerlo.

He encontrado varios ejemplos en Internet haciéndolo, pero no funciona para mí.

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

La cadena de comando contiene el nombre del archivo por lotes (almacenado en system32) y algunos archivos que debe manipular. (Ejemplo: txtmanipulator file1.txt file2.txt file3.txt). Cuando ejecuto el archivo por lotes manualmente, funciona correctamente.

Al ejecutar el código, me da un **ExitCode: 1** (Catch all for general errors)

¿Qué estoy haciendo mal?


44
No muestras lo que commandes. Si contiene rutas con espacios, deberá poner comillas a su alrededor.
Jon

@ Jon Lo he hecho, ese no es el problema. ¡Gracias por tu contribución!
Wessel T.

¿Hay algún error en el archivo por lotes? Es posible que desee establecer WorkingDirectory (o como se llame esa propiedad) para su proceso.
Jonas

Bueno, cuando ejecuto el código en el comando manualmente (Inicio -> Ejecutar) se ejecuta correctamente. Agregué WorkingDirectory ahora y lo configuré en system32, pero sigo obteniendo el Código de error: 1
Wessel T.

Respuestas:


192

Esto debería funcionar. Podría intentar volcar el contenido de las secuencias de salida y error para saber qué está sucediendo:

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

* EDITAR *

Dada la información adicional en su comentario a continuación, pude recrear el problema. Parece que hay una configuración de seguridad que resulta en este comportamiento (no lo he investigado en detalle).

Esto hace el trabajo si el archivo por lotes no se encuentra en C:\Windows\System32. Intente moverlo a otra ubicación, por ejemplo, la ubicación de su ejecutable. Tenga en cuenta que mantener archivos por lotes personalizados o ejecutables en el directorio de Windows es una mala práctica de todos modos.

* EDIT 2 * Se resulta que si las corrientes se leen de forma sincrónica, se puede producir un callejón sin salida, ya sea mediante la lectura de forma sincronizada antes WaitForExito mediante la lectura de ambos stderrystdout de forma sincronizada uno tras otro.

Esto no debería suceder si se utilizan métodos de lectura asíncronos, como en el siguiente ejemplo:

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
¡Gracias! ahora puedo ver cuál es el error. "C: \ Windows \ System32 \ txtmanipulator.bat no se reconoce como un comando interno o externo, programa o archivo por lotes" (Traducido del holandés) Lo cual es extraño. Porque cuando ejecuto txtmanipulator desde la línea de comandos, se ejecuta perfectamente.
Wessel T.

2
Pude recrear tu problema, mira la adición a la respuesta.
steinar 01 de

Este enfoque no es aplicable cuando ejecuto "pg_dump ...> dumpfile", que descarga una base de datos de 27 GB en dumpfile
Paul

¿Cómo puedo obtener los datos de la salida / error estándar para evitar la acumulación (dado que el lote puede ejecutarse durante años y quiero ver los datos tal como vienen?)
Dani

El uso de los métodos de lectura asíncrona (ver edición 2) le permitirá generar texto tan pronto como se haya leído una línea.
steinar

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

Esta línea simple ejecutará el archivo por lotes.


3
¿Cómo puedo pasar parámetros y leer un resultado de la ejecución del comando?
Janatbek Sharsheyev

@JanatbekSharsheyev Vea si es esto lo que pide ...
No

1
@JanatbekSharsheyev puede pasar como argumentos. Consulte el siguiente ejemplo ProcessStartInfo info = new ProcessStartInfo ("c: \\ batchfilename.bat"); info.Arguments = "-parámetro"; Process.Start (info)
sk1007

17

Después de una gran ayuda de Steinar, esto es lo que funcionó para mí:

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
En mi caso, un archivo por lotes estaba llamando a otro archivo por lotes usando ~%dp0. Añadiendo el ProcessInfo.WorkingDirectoryarreglado.
Sonata

1
¿Por qué pasar un commandsi llama directamente al archivo BAT?
sfarbota

@sfarbota Argumentos para el archivo BAT?
sigod

@sigod No estoy seguro si me haces una pregunta o sugieres una posible respuesta a la mía. Sí, los archivos por lotes pueden tomar argumentos. Pero si está sugiriendo que los commandparámetros podrían usarse para enviar argumentos al archivo BAT, eso no es lo que muestra el código aquí. De hecho, no se usa en absoluto. Y si lo fuera, probablemente debería nombrarse argumentsen su lugar.
sfarbota 01 de

@sfarbota Fue una suposición. Por cierto, commandse usa en new ProcessStartInfollamadas.
sigod

13

Funciona bien. Lo probé así:

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

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

Comenté apagando la ventana para poder verla correr.


Gracias por el ejemplo que aclaró un par de puntos inicialmente confusos. Se requieren algunos pasos adicionales para convertir los ejemplos anteriores en un método reutilizable, y el parámetro "comando de cadena" en los ejemplos anteriores debería haberse denominado args o parámetros, ya que eso es lo que se pasa en él.
Desarrollador63

7

Aquí hay un código de muestra c # que envía 2 parámetros a un archivo bat / cmd para responder esta pregunta .

Comentario: ¿cómo puedo pasar parámetros y leer un resultado de la ejecución del comando?

/ por @Janatbek Sharsheyev

Opción 1: sin ocultar la ventana de la consola, pasando argumentos y sin obtener las salidas

using System;
using System.Diagnostics;


namespace ConsoleApplication
{
    class Program
    { 
        static void Main(string[] args)
        {
         System.Diagnostics.Process.Start(@"c:\batchfilename.bat", "\"1st\" \"2nd\"");
        }
    }
}

Opción 2: Ocultar la ventana de la consola, pasar argumentos y tomar salidas


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

El siguiente código funcionó bien para mí

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

Necesitaba asignar TODA la ruta en FileName para que funcione (incluso si WorkingDirectory tiene la misma ruta raíz ...). Si me salteo la ruta raíz, recibo la excepción de que no existe tal archivo
Hawlett

Verifique que la ruta, lo que está componiendo y verifíquela, exista o no manualmente. Ayudará a resolver el problema.
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());
    }
}

Necesitaba asignar TODA la ruta en FileName para que funcione (incluso si WorkingDirectory tiene la misma ruta raíz ...). Si me salteo la ruta raíz, recibo la excepción de que no existe tal archivo
Hawlett

1

¿Has intentado iniciarlo como administrador? Inicie Visual Studio como administrador si lo usa, porque trabajar con .batarchivos requiere esos privilegios.


0

Quería algo que fuera más directamente utilizable sin valores de cadena codificados específicos de la organización. Ofrezco lo siguiente como un fragmento de código directamente reutilizable. La desventaja menor es determinar y pasar la carpeta de trabajo al hacer la llamada.

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

Llamado así:

    // 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);

En este ejemplo, desde Visual Studio 2017, como parte de una ejecución de prueba, quiero ejecutar un archivo por lotes de restablecimiento del entorno antes de ejecutar algunas pruebas. (SpecFlow + xUnit). Me cansé de los pasos adicionales para ejecutar manualmente el archivo bat por separado, y solo quería ejecutar el archivo bat como parte del código de configuración de prueba de C #. El archivo por lotes de reinicio del entorno mueve los archivos de casos de prueba nuevamente a la carpeta de entrada, limpia las carpetas de salida, etc. El método QuotesAround simplemente pone comillas alrededor de la línea de comando en caso de que haya espacios en los nombres de las carpetas ("Archivos de programa", ¿alguien?). Todo lo que contiene es esto: cadena privada QuotesAround (entrada de cadena) {return "\" "+ input +" \ "";}

Espero que algunos encuentren esto útil y ahorren unos minutos si su escenario es similar al mío.


0

Con las soluciones propuestas anteriormente, he tenido problemas para ejecutar múltiples comandos npm en un bucle y obtener todas las salidas en la ventana de la consola.

Finalmente comenzó a funcionar después de haber combinado todo de los comentarios anteriores, pero reorganicé el flujo de ejecución del código.

Lo que he notado es que la suscripción de eventos se realizó demasiado tarde (después de que el proceso ya comenzó) y, por lo tanto, algunas salidas no se capturaron.

El siguiente código ahora hace lo siguiente:

  1. Se suscribe a los eventos, antes de que comience el proceso, asegurando así que no se pierda ningún resultado.
  2. Comienza a leer de las salidas tan pronto como se inicia el proceso.

El código ha sido probado contra los puntos muertos, aunque es sincrónico (ejecución de un proceso en ese momento), por lo que no puedo garantizar lo que sucedería si se ejecutara en paralelo.

    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

Usando CliWrap :

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

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

-1

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

Sé que esto funcionará para archivos y parámetros por lotes, pero no tengo idea de cómo obtener los resultados en C #. Por lo general, las salidas se definen en el archivo por lotes.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.