Wykonywanie pliku wsadowego w C #

140

Próbuję wykonać plik wsadowy w C #, ale nie mam przy tym szczęścia.

Znalazłem wiele przykładów w Internecie, ale to nie działa.

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

Ciąg polecenia zawiera nazwę pliku wsadowego (przechowywanego w system32) i niektórych plików, którymi powinien manipulować. (Przykład:) txtmanipulator file1.txt file2.txt file3.txt. Kiedy uruchamiam plik wsadowy ręcznie, działa on poprawnie.

Podczas wykonywania kodu daje mi plik **ExitCode: 1** (Catch all for general errors)

Co ja robię źle?

Wessel T.
źródło
4
Nie pokazujesz, co commandjest. Jeśli zawiera ścieżki ze spacjami, musisz umieścić je w cudzysłowie.
Jon
@Jon Zrobiłem to, to nie jest problem. Dzięki za wkład!
Wessel T.
Czy coś w Twoim pliku wsadowym nie działa? Możesz chcieć ustawić WorkingDirectory (lub jakąkolwiek inną nazwę tej właściwości) dla swojego procesu.
Jonas
Cóż, kiedy wykonuję kod w poleceniu ręcznie (Start -> Uruchom), działa on poprawnie. Dodałem teraz katalog roboczy i ustawiłem go na system32, ale nadal otrzymuję kod błędu: 1
Wessel T.

Odpowiedzi:

192

To powinno działać. Możesz spróbować zrzucić zawartość strumieni wyjściowych i strumieni błędów, aby dowiedzieć się, co się dzieje:

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

* EDYTOWAĆ *

Biorąc pod uwagę dodatkowe informacje zawarte w poniższym komentarzu, udało mi się odtworzyć problem. Wydaje się, że istnieje pewne ustawienie zabezpieczeń, które powoduje takie zachowanie (nie zbadano tego szczegółowo).

Działa to, jeśli plik wsadowy nie znajduje się w C:\Windows\System32. Spróbuj przenieść go w inne miejsce, np. Lokalizację pliku wykonywalnego. Zauważ, że przechowywanie niestandardowych plików wsadowych lub plików wykonywalnych w katalogu Windows jest i tak złą praktyką.

EDIT * 2 * To Okazuje się , że jeśli strumienie są odczytywane synchronicznie, może nastąpić zakleszczenie, albo przed przeczytaniem synchronicznie WaitForExitlub czytając jednocześnie stderri stdoutsynchronicznie jeden po drugim.

Nie powinno to mieć miejsca, jeśli zamiast tego używasz asynchronicznych metod odczytu, jak w poniższym przykładzie:

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
źródło
1
Dzięki! teraz faktycznie widzę, na czym polega błąd. „C: \ Windows \ System32 \ txtmanipulator.bat nie jest rozpoznawany jako wewnętrzne lub zewnętrzne polecenie, program lub plik wsadowy” (przetłumaczone z holenderskiego) Co jest dziwne. Ponieważ kiedy uruchamiam txtmanipulator z wiersza poleceń, działa on doskonale.
Wessel T.
2
Udało mi się odtworzyć Twój problem, sprawdź dodatek do odpowiedzi.
steinar
To podejście nie ma zastosowania, gdy uruchamiam "pg_dump ...> dumpfile", który zrzuca bazę danych 27 GB do pliku dumpfile
Paul
Jak mogę pobrać dane ze standardowego wyjścia / błędu, aby uniknąć ich kumulacji (biorąc pod uwagę, że partia może działać latami i chcę zobaczyć dane tak, jak się pojawią?)
Dani
Użycie asynchronicznych metod odczytu (zobacz edycję 2) pozwoli ci wyprowadzić tekst zaraz po przeczytaniu wiersza.
steinar
132
System.Diagnostics.Process.Start("c:\\batchfilename.bat");

ta prosta linia uruchomi plik wsadowy.

TSSathish
źródło
3
jak mogę przekazać parametry i odczytać wynik wykonania polecenia?
Janatbek Sharsheyev
@JanatbekSharsheyev Zobacz, czy o to prosisz ...
To nie było mnie
1
@JanatbekSharsheyev możesz przekazać jako argumenty .. Zobacz poniższy przykład ProcessStartInfo info = new ProcessStartInfo ("c: \\ batchfilename.bat"); info.Arguments = "-parametr"; Process.Start (informacje)
sk1007
17

Po wielkiej pomocy steinara, oto co mi zadziałało:

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.
źródło
1
W moim przypadku plik wsadowy wywoływał inny plik wsadowy przy użyciu ~%dp0. Dodanie ProcessInfo.WorkingDirectorynaprawionego.
Sonata
1
Po co przekazywać a, commandjeśli bezpośrednio wywołujesz plik BAT?
sfarbota
@sfarbota Argumenty dla pliku BAT?
sygnatura
@sigod Nie jestem pewien, czy zadajesz mi pytanie lub sugerujesz moją możliwą odpowiedź. Tak, pliki wsadowe mogą przyjmować argumenty. Ale jeśli sugerujesz, że commandparametry mogą być używane do wysyłania argumentów do pliku BAT, to nie jest to, co pokazuje tutaj kod. W rzeczywistości nie jest używany. A gdyby tak było, prawdopodobnie należałoby go argumentszamiast tego nazwać .
sfarbota
@sfarbota To było założenie. Nawiasem mówiąc, commandjest używany w new ProcessStartInforozmowie.
sigod
13

To działa dobrze. Przetestowałem to tak:

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

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

Skomentowałem wyłączenie okna, żeby zobaczyć, jak działa.

Steve Wellens
źródło
Dziękuję za przykład, który wyjaśnił parę początkowo mylących punktów. Przekształcenie poprzednich przykładów w metodę wielokrotnego użytku wymaga kilku dodatkowych kroków, a parametr „string command” we wcześniejszych przykładach powinien być nazwany args lub parameters, ponieważ to właśnie jest w nim przekazywane.
Deweloper
7

Oto przykładowy kod C #, który wysyła 2 parametry do pliku bat / cmd, aby odpowiedzieć na to pytanie .

Komentarz: jak mogę podać parametry i odczytać wynik wykonania polecenia?

/ przez @Janatbek Sharsheyev

Opcja 1: bez ukrywania okna konsoli, przekazywania argumentów i bez pobierania danych wyjściowych

using System;
using System.Diagnostics;


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

Opcja 2: ukrycie okna konsoli, przekazywanie argumentów i pobieranie danych wyjściowych


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

To nie byłem ja
źródło
3

Poniższy kod działał dobrze dla mnie

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
źródło
Musiałem przypisać CAŁĄ ścieżkę w FileName, aby działała (nawet jeśli WorkingDirectory ma tę samą ścieżkę główną…). Jeśli
pominę
Sprawdź ścieżkę, co komponuje i sprawdź, czy istnieje lub nie ręcznie. Pomoże to rozwiązać problem.
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
źródło
Musiałem przypisać CAŁĄ ścieżkę w FileName, aby działała (nawet jeśli WorkingDirectory ma tę samą ścieżkę główną…). Jeśli
pominę
1

Czy próbowałeś uruchomić go jako administrator? Uruchom program Visual Studio jako administrator, jeśli go używasz, ponieważ praca z .batplikami wymaga tych uprawnień.

Kristifor
źródło
0

Chciałem czegoś, co byłoby bardziej bezpośrednio użyteczne bez specyficznych dla organizacji wartości ciągów zakodowanych na stałe. Oferuję następujące elementy jako fragment kodu do bezpośredniego ponownego wykorzystania. Drobną wadą jest konieczność określenia i przekazania folderu roboczego podczas wykonywania połączenia.

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

Nazywa się tak:

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

W tym przykładzie z poziomu programu Visual Studio 2017 w ramach uruchomienia testowego chcę uruchomić plik wsadowy resetowania środowiska przed wykonaniem niektórych testów. (SpecFlow + xUnit). Zmęczyły mnie dodatkowe kroki do ręcznego uruchamiania pliku bat oddzielnie i chciałem po prostu uruchomić plik bat jako część kodu konfiguracji testu C #. Plik wsadowy resetowania środowiska przenosi pliki przypadków testowych z powrotem do folderu wejściowego, czyści foldery wyjściowe itp., Aby uzyskać odpowiedni stan początkowy testu do testowania. Metoda QuotesAround po prostu umieszcza cudzysłowy wokół wiersza poleceń na wypadek, gdyby w nazwach folderów były spacje („Program Files”, ktoś?). Wszystko, co w nim jest, to: prywatny ciąg QuotesAround (ciąg wejściowy) {return "\" "+ input +" \ "";}

Mam nadzieję, że niektórzy uznają to za przydatne i zaoszczędzą kilka minut, jeśli Twój scenariusz jest podobny do mojego.

Deweloper63
źródło
0

W przypadku wcześniej proponowanych rozwiązań zmagałem się z wykonaniem wielu poleceń npm w pętli i wyświetleniem wszystkich wyników w oknie konsoli.

W końcu zaczęło działać po połączeniu wszystkiego z poprzednich komentarzy, ale przestawiłem przepływ wykonywania kodu.

Zauważyłem, że subskrybowanie zdarzeń zostało wykonane zbyt późno (po rozpoczęciu procesu) i dlatego niektóre dane wyjściowe nie zostały przechwycone.

Poniższy kod wykonuje teraz następujące czynności:

  1. Subskrybuje zdarzenia przed rozpoczęciem procesu, zapewniając w ten sposób, że żadne wyjście nie zostanie pominięte.
  2. Rozpoczyna odczytywanie wyjść natychmiast po rozpoczęciu procesu.

Kod został przetestowany pod kątem zakleszczeń, chociaż jest on synchroniczny (wykonanie jednego procesu na raz), więc nie mogę zagwarantować, co by się stało, gdyby było to uruchamiane równolegle.

    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();
    }
Admir Tuzović
źródło
0

Korzystanie z CliWrap :

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

var exitCode = result.ExitCode;
var stdOut = result.StandardOutput;
Tyrrrz
źródło
-1

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

Wiem, że to zadziała dla pliku wsadowego i parametrów, ale nie ma pomysłów, jak uzyskać wyniki w C #. Zwykle dane wyjściowe są definiowane w pliku wsadowym.

macunix
źródło