Batchdatei in C # ausführen

139

Ich versuche, eine Batch-Datei in C # auszuführen, habe aber kein Glück damit.

Ich habe im Internet mehrere Beispiele dafür gefunden, aber es funktioniert bei mir nicht.

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

Die Befehlszeichenfolge enthält den Namen der Batchdatei (gespeichert in system32) und einige Dateien, die bearbeitet werden sollen. (Beispiel:txtmanipulator file1.txt file2.txt file3.txt . Wenn ich die Batchdatei manuell ausführe, funktioniert sie ordnungsgemäß.

Bei der Ausführung des Codes gibt es mir eine **ExitCode: 1** (Catch all for general errors)

Was mache ich falsch?

Wessel T.
quelle
4
Sie zeigen nicht, was commandist. Wenn es Pfade mit Leerzeichen enthält, müssen Sie sie in Anführungszeichen setzen.
Jon
@ Jon Ich habe das getan, das ist nicht das Problem. Danke für deinen Beitrag!
Wessel T.
Fällt etwas in Ihrer Batch-Datei aus? Möglicherweise möchten Sie das WorkingDirectory (oder wie auch immer diese Eigenschaft heißt) für Ihren Prozess festlegen.
Jonas
Nun, wenn ich den Code im Befehl manuell ausführe (Start -> Ausführen), läuft er korrekt. Ich habe das WorkingDirectory jetzt hinzugefügt und auf system32 gesetzt, aber ich erhalte immer noch den ErrorCode: 1
Wessel T.

Antworten:

191

Das sollte funktionieren. Sie könnten versuchen, den Inhalt der Ausgabe- und Fehlerströme zu löschen, um herauszufinden, was passiert:

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

* BEARBEITEN *

Aufgrund der zusätzlichen Informationen in Ihrem Kommentar unten konnte ich das Problem neu erstellen. Es scheint eine Sicherheitseinstellung zu geben, die zu diesem Verhalten führt (habe dies nicht im Detail untersucht).

Dies funktioniert , wenn sich die Batchdatei nicht in befindet C:\Windows\System32. Versuchen Sie, es an einen anderen Speicherort zu verschieben, z. B. an den Speicherort Ihrer ausführbaren Datei. Beachten Sie, dass das Speichern benutzerdefinierter Batchdateien oder ausführbarer Dateien im Windows-Verzeichnis ohnehin eine schlechte Praxis ist.

* EDIT 2 * Es stellt sich heraus, dass beim synchronen Lesen der Streams ein Deadlock auftreten kann, entweder durch synchrones Lesen vor WaitForExitoder durch Lesen von beiden stderrundstdout synchron nacheinander.

Dies sollte nicht passieren, wenn stattdessen die asynchronen Lesemethoden verwendet werden, wie im folgenden Beispiel:

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
quelle
1
Vielen Dank! Jetzt kann ich tatsächlich sehen, was der Fehler ist. "C: \ Windows \ System32 \ txtmanipulator.bat wird nicht als interner oder externer Befehl, Programm oder Batchdatei erkannt" (aus dem Niederländischen übersetzt) ​​Was seltsam ist. Denn wenn ich txtmanipulator über die Kommandozeile starte, wird es perfekt ausgeführt.
Wessel T.
2
Ich konnte Ihr Problem neu erstellen und den Zusatz zur Antwort überprüfen.
Steinar
Dieser Ansatz ist nicht anwendbar, wenn ich "pg_dump ...> dumpfile" ausführe, das eine 27-GB-Datenbank auf dumpfile ausgibt
Paul
Wie kann ich die Daten aus der Standardausgabe / dem Standardfehler abrufen, um eine Akkumulation zu vermeiden (vorausgesetzt, der Stapel kann jahrelang ausgeführt werden und ich möchte die Daten so sehen, wie sie kommen?)
Dani,
Mit den asynchronen Lesemethoden (siehe Bearbeitung 2) können Sie Text ausgeben, sobald eine Zeile gelesen wurde.
Steinar
132
System.Diagnostics.Process.Start("c:\\batchfilename.bat");

Diese einfache Zeile führt die Batchdatei aus.

TSSathish
quelle
3
Wie kann ich Parameter übergeben und ein Ergebnis der Befehlsausführung lesen?
Janatbek Sharsheyev
@ JanatbekSharsheyev Sehen Sie, ob Sie danach fragen ...
Ich war es nicht
1
@JanatbekSharsheyev können Sie als Argumente übergeben. Siehe Beispiel unten ProcessStartInfo info = new ProcessStartInfo ("c: \\ batchfilename.bat"); info.Arguments = "-Parameter"; Process.Start (info)
sk1007
17

Nach einiger großer Hilfe von Steinar hat dies für mich funktioniert:

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();
}
Wessel T.
quelle
1
In meinem Fall hat eine Batchdatei eine andere Batchdatei mit aufgerufen ~%dp0. Hinzufügen der ProcessInfo.WorkingDirectorybehoben es.
Sonate
1
Warum eine übergeben, commandwenn Sie die BAT-Datei direkt aufrufen?
Sfarbota
@sfarbota Argumente für BAT-Datei?
Sigod
@sigod Ich bin mir nicht sicher, ob du mir eine Frage stellst oder eine mögliche Antwort auf meine vorschlägst. Ja, Batchdateien können Argumente annehmen. Wenn Sie jedoch vorschlagen, dass die commandParameter zum Senden von Argumenten an die BAT-Datei verwendet werden könnten, wird der Code hier nicht angezeigt. Es wird in der Tat überhaupt nicht verwendet. Und wenn ja, sollte es wahrscheinlich stattdessen benannt argumentswerden.
Sfarbota
@ Farbota Es war eine Annahme. Wird übrigens commandim new ProcessStartInfoAnruf verwendet.
Sigod
13

Es funktioniert gut. Ich habe es so getestet:

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

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

Ich habe das Fenster ausgeschaltet, damit ich sehen kann, wie es läuft.

Steve Wellens
quelle
Vielen Dank für das Beispiel, das einige zunächst verwirrende Punkte verdeutlicht hat. Es sind einige zusätzliche Schritte erforderlich, um die vorherigen Beispiele in eine wiederverwendbare Methode umzuwandeln, und der Parameter "string command" in früheren Beispielen sollte args oder parameter heißen, da dies darin übergeben wird.
Developer63
7

Hier ist ein Beispiel-C # -Code, der 2 Parameter an eine bat / cmd-Datei sendet, um diese Frage zu beantworten .

Kommentar: Wie kann ich Parameter übergeben und ein Ergebnis der Befehlsausführung lesen?

/ von @Janatbek Sharsheyev

Option 1: Ohne das Konsolenfenster auszublenden, Argumente zu übergeben und ohne die Ausgaben zu erhalten

using System;
using System.Diagnostics;


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

Option 2: Ausblenden des Konsolenfensters, Übergeben von Argumenten und Ausführen von Ausgaben


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

Ich war es nicht
quelle
3

Der folgende Code hat für mich gut funktioniert

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...");
}
Anjan Kant
quelle
Ich musste den GANZEN Pfad in FileName zuweisen, damit es funktioniert (auch wenn WorkingDirectory denselben Stammpfad hat…). Wenn ich den
Stammpfad
Überprüfen Sie, ob der Pfad vorhanden ist oder nicht, ob er vorhanden ist oder nicht. Es wird helfen, das Problem herauszufinden.
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
quelle
Ich musste den GANZEN Pfad in FileName zuweisen, damit es funktioniert (auch wenn WorkingDirectory denselben Stammpfad hat…). Wenn ich den
Stammpfad
1

Haben Sie versucht, es als Administrator zu starten? Starten Sie Visual Studio als Administrator, wenn Sie es verwenden, da für die Arbeit mit .batDateien diese Berechtigungen erforderlich sind.

Kristifor
quelle
0

Ich wollte etwas, das direkter ohne organisationsspezifische fest codierte Zeichenfolgenwerte verwendet werden kann. Ich biete Folgendes als direkt wiederverwendbaren Codeabschnitt an. Der kleine Nachteil ist, dass der Arbeitsordner beim Tätigen des Anrufs ermittelt und übergeben werden muss.

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

So genannt:

    // 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 diesem Beispiel möchte ich in Visual Studio 2017 im Rahmen eines Testlaufs eine Batchdatei zum Zurücksetzen der Umgebung ausführen, bevor einige Tests ausgeführt werden. (SpecFlow + xUnit). Ich hatte es satt, zusätzliche Schritte zum manuellen Ausführen der Bat-Datei separat auszuführen, und wollte die Bat-Datei nur als Teil des C # -Test-Setup-Codes ausführen. Die Batchdatei zum Zurücksetzen der Umgebung verschiebt Testfalldateien zurück in den Eingabeordner, bereinigt Ausgabeordner usw., um den richtigen Teststartstatus für das Testen zu erreichen. Die QuotesAround-Methode setzt einfach Anführungszeichen um die Befehlszeile, falls Leerzeichen in Ordnernamen vorhanden sind ("Programme", jemand?). Alles, was drin ist, ist Folgendes: privater String QuotesAround (String-Eingabe) {return "\" "+ input +" \ "";}

Hoffe, einige finden dies nützlich und sparen ein paar Minuten, wenn Ihr Szenario meinem ähnlich ist.

Entwickler63
quelle
0

Bei zuvor vorgeschlagenen Lösungen hatte ich Probleme, mehrere npm-Befehle in einer Schleife auszuführen und alle Ausgaben im Konsolenfenster zu erhalten.

Es fing endlich an zu funktionieren, nachdem ich alles aus den vorherigen Kommentaren kombiniert, aber den Code-Ausführungsfluss neu geordnet hatte.

Was mir aufgefallen ist, ist, dass das Abonnieren von Ereignissen zu spät erfolgte (nachdem der Prozess bereits gestartet wurde) und daher einige Ausgaben nicht erfasst wurden.

Der folgende Code bewirkt nun Folgendes:

  1. Abonniert die Ereignisse, bevor der Prozess gestartet wurde, um sicherzustellen, dass keine Ausgabe übersehen wird.
  2. Beginnt mit dem Lesen von Ausgängen, sobald der Prozess gestartet wird.

Der Code wurde gegen die Deadlocks getestet, obwohl er synchron ist (jeweils eine Prozessausführung), sodass ich nicht garantieren kann, was passieren würde, wenn dies parallel ausgeführt würde.

    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();
    }
Bewunderer Tuzović
quelle
0

Verwenden von CliWrap :

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

var exitCode = result.ExitCode;
var stdOut = result.StandardOutput;
Tyrrrz
quelle
-1

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

Ich weiß, dass dies für Batch-Dateien und Parameter funktioniert, aber keine Ideen, wie die Ergebnisse in C # erhalten werden können. Normalerweise werden die Ausgaben in der Batch-Datei definiert.

Macunix
quelle