Esecuzione del file batch in C #


140

Sto cercando di eseguire un file batch in C #, ma non ho fortuna a farlo.

Ho trovato più esempi su Internet mentre lo faccio, ma non funziona per me.

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 stringa di comando contiene il nome del file batch (archiviato system32) e alcuni file che dovrebbe manipolare. (Esempio:) txtmanipulator file1.txt file2.txt file3.txt. Quando eseguo manualmente il file batch, funziona correttamente.

Quando eseguo il codice, mi dà un **ExitCode: 1** (Catch all for general errors)

Che cosa sto facendo di sbagliato?


4
Non mostri cosa commandsia. Se contiene percorsi con spazi, dovrai inserire delle virgolette attorno ad essi.
Jon

@Jon l'ho fatto, non è questo il problema. Grazie per il tuo contributo!
Wessel T.

Si è verificato un errore nel file batch? Potresti voler impostare la WorkingDirectory (o come si chiama quella proprietà) per il tuo processo.
Jonas

Bene, quando eseguo manualmente il codice nel comando (Start -> Esegui), viene eseguito correttamente. Ho aggiunto WorkingDirectory ora e impostato su system32, ma ottengo ancora ErrorCode: 1
Wessel T.

Risposte:


192

Questo dovrebbe funzionare. Potresti provare a scaricare il contenuto dell'output e i flussi di errore per scoprire cosa sta succedendo:

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

* MODIFICARE *

Date le informazioni extra nel tuo commento qui sotto, sono stato in grado di ricreare il problema. Sembra esserci qualche impostazione di sicurezza che provoca questo comportamento (non l'ho studiato in dettaglio).

Questo fa il lavoro se il file batch non si trova in C:\Windows\System32. Prova a spostarlo in un'altra posizione, ad esempio la posizione del tuo eseguibile. Si noti che mantenere file batch o eseguibili personalizzati nella directory di Windows è comunque una cattiva pratica.

* EDIT 2 * Si scopre che se i flussi vengono letti in modo sincrono, può verificarsi un deadlock, leggendo in modo sincrono prima WaitForExito leggendo entrambi stderre in modo stdoutsincrono uno dopo l'altro.

Ciò non dovrebbe accadere se si utilizzano invece i metodi di lettura asincroni, come nell'esempio seguente:

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
Grazie! ora posso effettivamente vedere qual è l'errore. "C: \ Windows \ System32 \ txtmanipulator.bat non è riconosciuto come comando, programma o file batch interno o esterno" (Tradotto da olandese). È strano. Perché quando eseguo txtmanipulator dalla riga di comando si esegue perfettamente.
Wessel T.

2
Sono stato in grado di ricreare il tuo problema, controlla l'aggiunta alla risposta.
steinar

Questo approccio non è applicabile quando eseguo "pg_dump ...> dumpfile" che scarica un database da 27 GB in dumpfile
Paul

Come posso ottenere i dati dall'output / errore standard per evitare l'accumulo (dato che il batch può funzionare per anni e voglio vedere i dati così come sono?)
Dani

L'uso dei metodi di lettura asincrona (vedi modifica 2) ti permetterà di emettere testo non appena una riga è stata letta.
steinar,

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

questa semplice riga eseguirà il file batch.


3
come posso passare parametri e leggere un risultato dell'esecuzione del comando?
Janatbek Sharsheyev,

@JanatbekSharsheyev Vedi se è questo che chiedi ...
Non ero io il

1
@JanatbekSharsheyev puoi passare come argomenti. Vedi l'esempio di seguito ProcessStartInfo info = new ProcessStartInfo ("c: \\ batchfilename.bat"); info.Arguments = "-parameter"; Process.Start (informazioni)
sk1007

17

Dopo un grande aiuto da parte di steinar questo è ciò che ha funzionato per me:

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
Nel mio caso, un file batch stava chiamando un altro file batch utilizzando ~%dp0. Aggiungendo il ProcessInfo.WorkingDirectoryriparato.
Sonata,

1
Perché passare un commandse si chiama direttamente il file BAT?
sfarbota,

Argomenti @sfarbota per il file BAT?
sigod

@sigod Non sono sicuro che tu mi stia facendo una domanda o suggerendo una possibile risposta alla mia. Sì, i file batch possono accettare argomenti. Ma se stai suggerendo che i commandparametri potrebbero essere usati per inviare argomenti al file BAT, questo non è ciò che mostra il codice qui. In realtà non è usato affatto. E se fosse, dovrebbe probabilmente essere chiamato argumentsinvece.
sfarbota,

@sfarbota Era un'ipotesi. A proposito, commandviene utilizzato in new ProcessStartInfochiamata.
sigod

13

Funziona bene L'ho provato in questo modo:

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

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

Ho commentato spegnendo la finestra in modo da poter vederlo correre.


Grazie per l'esempio che ha chiarito un paio di punti inizialmente confusi. Sono necessari alcuni passaggi aggiuntivi per trasformare gli esempi precedenti in un metodo riutilizzabile e il parametro "comando stringa" negli esempi precedenti avrebbe dovuto essere chiamato args o parametri, poiché è quello che viene passato in esso.
Developer63

7

Ecco un codice c # di esempio che invia 2 parametri a un file bat / cmd per rispondere a questa domanda .

Commento: come posso passare i parametri e leggere un risultato dell'esecuzione del comando?

/ di @Janatbek Sharsheyev

Opzione 1: senza nascondere la finestra della console, passare argomenti e senza ottenere gli output

using System;
using System.Diagnostics;


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

Opzione 2: nascondere la finestra della console, passare argomenti e prendere output


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

Di seguito il codice ha funzionato bene per me

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

Ho dovuto assegnare TUTTO il percorso in FileName per farlo funzionare (anche se WorkingDirectory ha lo stesso percorso di root ...). Se salto il percorso di root ottengo l'eccezione che non esiste un file simile
Hawlett,

Controlla il percorso, ciò che sta componendo e controllalo, esiste o non manualmente. Aiuterà a capire il 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());
    }
}

Ho dovuto assegnare TUTTO il percorso in FileName per farlo funzionare (anche se WorkingDirectory ha lo stesso percorso di root ...). Se salto il percorso di root ottengo l'eccezione che non esiste un file simile
Hawlett,

1

Hai provato ad avviarlo come amministratore? Avvia Visual Studio come amministratore se lo usi, perché lavorare con i .batfile richiede quei privilegi.


0

Volevo qualcosa che fosse più direttamente utilizzabile senza valori di stringa codificati specifici dell'organizzazione. Offro quanto segue come parte di codice riutilizzabile direttamente. L'aspetto negativo minore è necessario per determinare e passare la cartella di lavoro quando si effettua la chiamata.

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

Chiamato così:

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

In questo esempio, all'interno di Visual Studio 2017, come parte di un'esecuzione di test, voglio eseguire un file batch di ripristino dell'ambiente prima di eseguire alcuni test. (SpecFlow + xUnit). Mi sono stancato di ulteriori passaggi per l'esecuzione manuale del file bat separatamente e volevo solo eseguire il file bat come parte del codice di installazione del test C #. Il file batch di ripristino dell'ambiente sposta i file dei casi di test nella cartella di input, pulisce le cartelle di output, ecc. Per raggiungere lo stato iniziale di test corretto per i test. Il metodo QuotesAround inserisce semplicemente le virgolette intorno alla riga di comando nel caso in cui vi siano spazi nei nomi delle cartelle ("Programmi", chiunque?). Tutto ciò che c'è dentro è questo: stringa privata QuotesAround (input stringa) {return "\" "+ input +" \ "";}

Spero che alcuni lo trovino utile e risparmino qualche minuto se il tuo scenario è simile al mio.


0

Con le soluzioni precedentemente proposte, ho faticato a ottenere più comandi npm eseguiti in un ciclo e ottenere tutti gli output sulla finestra della console.

Alla fine ha iniziato a funzionare dopo che ho combinato tutto dai commenti precedenti, ma ho riorganizzato il flusso di esecuzione del codice.

Quello che ho notato è che la sottoscrizione degli eventi è stata fatta troppo tardi (dopo che il processo è già iniziato) e quindi alcuni output non sono stati acquisiti.

Il codice seguente ora procede come segue:

  1. Si iscrive agli eventi, prima dell'avvio del processo, garantendo quindi che non venga perso alcun output.
  2. Inizia la lettura dagli output non appena viene avviato il processo.

Il codice è stato testato rispetto ai deadlock, sebbene sia sincrono (un'esecuzione di un processo alla volta), quindi non posso garantire cosa accadrebbe se questo fosse eseguito in parallelo.

    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

Utilizzando CliWrap :

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

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

-1

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

So che funzionerà per file batch e parametri, ma nessuna idea su come ottenere i risultati in C #. Di solito, gli output sono definiti nel file batch.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.