Exécution d'un fichier batch en C #

140

J'essaye d'exécuter un fichier batch en C #, mais je n'ai pas de chance de le faire.

J'ai trouvé plusieurs exemples de le faire sur Internet, mais cela ne fonctionne pas pour moi.

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 chaîne de commande contient le nom du fichier de commandes (stocké dans system32) et certains fichiers qu'il doit manipuler. (Exemple:) txtmanipulator file1.txt file2.txt file3.txt. Lorsque j'exécute le fichier de commandes manuellement, cela fonctionne correctement.

Lors de l'exécution du code, cela me donne un **ExitCode: 1** (Catch all for general errors)

Qu'est-ce que je fais mal?

Wessel T.
la source
4
Vous ne montrez pas ce que commandc'est. S'il contient des chemins avec des espaces, vous devrez les mettre entre guillemets.
Jon
@Jon j'ai fait ça, ce n'est pas le problème. Merci pour votre contribution!
Wessel T.
Quelque chose dans votre fichier batch échoue? Vous souhaiterez peut-être définir le WorkingDirectory (ou le nom de cette propriété) pour votre processus.
Jonas
Eh bien, lorsque j'exécute le code dans la commande manuellement (Démarrer -> Exécuter), il fonctionne correctement. J'ai ajouté le WorkingDirectory maintenant et l'ai défini sur system32, mais j'obtiens toujours le ErrorCode: 1
Wessel T.

Réponses:

192

Cela devrait fonctionner. Vous pouvez essayer de vider le contenu des flux de sortie et d'erreur afin de savoir ce qui se passe:

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

* ÉDITER *

Compte tenu des informations supplémentaires contenues dans votre commentaire ci-dessous, j'ai pu recréer le problème. Il semble y avoir un paramètre de sécurité qui entraîne ce comportement (je n'ai pas étudié cela en détail).

Cela ne fonctionne si le fichier batch ne se trouve pas dans C:\Windows\System32. Essayez de le déplacer vers un autre emplacement, par exemple l'emplacement de votre exécutable. Notez que conserver des fichiers batch ou exécutables personnalisés dans le répertoire Windows est de toute façon une mauvaise pratique.

* EDIT 2 * Il s'avère que si les flux sont lus de manière synchrone, un blocage peut se produire, soit en lisant de manière synchrone avant WaitForExitou en lisant les deux stderret de manière stdoutsynchrone l'un après l'autre.

Cela ne devrait pas se produire si vous utilisez les méthodes de lecture asynchrones à la place, comme dans l'exemple suivant:

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
la source
1
Merci! maintenant, je peux voir quelle est l'erreur. "C: \ Windows \ System32 \ txtmanipulator.bat n'est pas reconnu comme une commande, un programme ou un fichier batch interne ou externe" (Traduit du néerlandais) Ce qui est étrange. Parce que lorsque je lance txtmanipulator à partir de la ligne de commande, il s'exécute parfaitement.
Wessel T.
2
J'ai pu recréer votre problème, consultez l'ajout à la réponse.
steinar
Cette approche n'est pas applicable lorsque j'exécute "pg_dump ...> dumpfile" qui vide une base de données de 27 Go dans un fichier de vidage
Paul
Comment puis-je récupérer les données de la sortie / erreur standard pour éviter de s'accumuler (étant donné que le lot peut fonctionner pendant des années et que je veux voir les données telles qu'elles viennent?)
Dani
L'utilisation des méthodes de lecture asynchrone (voir edit 2) vous permettra de sortir du texte dès qu'une ligne a été lue.
steinar
132
System.Diagnostics.Process.Start("c:\\batchfilename.bat");

cette simple ligne exécutera le fichier batch.

TSSathish
la source
3
comment puis-je passer des paramètres et lire le résultat de l'exécution de la commande?
Janatbek Sharsheyev
@JanatbekSharsheyev Voyez si c'est ce que vous demandez ...
Ce n'était pas moi le
1
@JanatbekSharsheyev vous pouvez passer comme arguments. Voir l'exemple ci-dessous ProcessStartInfo info = new ProcessStartInfo ("c: \\ batchfilename.bat"); info.Arguments = "-paramètre"; Process.Start (info)
sk1007
17

Après une aide précieuse de steinar, voici ce qui a fonctionné pour moi:

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.
la source
1
Dans mon cas, un fichier de commandes appelait un autre fichier de commandes en utilisant ~%dp0. Ajout du ProcessInfo.WorkingDirectorycorrigé.
Sonate
1
Pourquoi passer un commandsi vous appelez directement le fichier BAT?
sfarbota
@sfarbota Arguments pour le fichier BAT?
sigod
@sigod Je ne sais pas si vous me posez une question ou si vous suggérez une réponse possible à la mienne. Oui, les fichiers batch peuvent accepter des arguments. Mais si vous suggérez que les commandparamètres peuvent être utilisés pour envoyer des arguments au fichier BAT, ce n'est pas ce que le code montre ici. Il n'est pas du tout utilisé en fait. Et si c'était le cas, il devrait probablement être nommé à la argumentsplace.
sfarbota
@sfarbota C'était une hypothèse. En passant, commandest utilisé dans l' new ProcessStartInfoappel.
sigod
13

Ça fonctionne bien. Je l'ai testé comme ceci:

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

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

J'ai commenté éteindre la fenêtre pour pouvoir la VOIR fonctionner.

Steve Wellens
la source
Merci pour l'exemple qui a clarifié quelques points initialement confus. Il faut quelques étapes supplémentaires pour transformer les exemples précédents en une méthode réutilisable, et le paramètre "string command" dans les exemples précédents aurait dû être nommé args ou parameters, car c'est ce qui y est passé.
Developer63
7

Voici un exemple de code c # qui envoie 2 paramètres à un fichier bat / cmd pour répondre à cette question .

Commentaire: comment puis-je passer des paramètres et lire le résultat de l'exécution d'une commande?

/ par @Janatbek Sharsheyev

Option 1: sans masquer la fenêtre de la console, en passant des arguments et sans obtenir les sorties

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: masquer la fenêtre de la console, passer des arguments et prendre des sorties


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

Ce n'était pas moi
la source
3

Le code ci-dessous a bien fonctionné pour moi

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
la source
J'avais besoin d'assigner le chemin ENTIER dans FileName pour le faire fonctionner (même si WorkingDirectory a le même chemin racine…). Si je saute le chemin racine, je reçois une exception selon laquelle il n'y a pas de tel fichier
Hawlett
Vérifiez le chemin, ce qui compose et vérifiez-le, existe ou non manuellement. Cela aidera à résoudre le problème.
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
la source
J'avais besoin d'assigner le chemin ENTIER dans FileName pour le faire fonctionner (même si WorkingDirectory a le même chemin racine…). Si je saute le chemin racine, je reçois une exception selon laquelle il n'y a pas de tel fichier
Hawlett
1

Avez-vous essayé de le démarrer en tant qu'administrateur? Démarrez Visual Studio en tant qu'administrateur si vous l'utilisez, car travailler avec des .batfichiers nécessite ces privilèges.

Kristifor
la source
0

Je voulais quelque chose qui soit plus directement utilisable sans valeurs de chaîne codées en dur spécifiques à l'organisation. Je propose ce qui suit sous forme de morceau de code directement réutilisable. L'inconvénient mineur est de devoir déterminer et transmettre le dossier de travail lors de l'appel.

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

Appelé comme ceci:

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

Dans cet exemple, à partir de Visual Studio 2017, dans le cadre d'une exécution de test, je souhaite exécuter un fichier de commandes de réinitialisation d'environnement avant d'exécuter certains tests. (SpecFlow + xUnit). J'étais fatigué des étapes supplémentaires pour exécuter manuellement le fichier bat séparément, et je voulais simplement exécuter le fichier bat dans le cadre du code de configuration du test C #. Le fichier batch de réinitialisation de l'environnement déplace les fichiers de cas de test dans le dossier d'entrée, nettoie les dossiers de sortie, etc. pour obtenir l'état de démarrage du test approprié pour le test. La méthode QuotesAround met simplement des guillemets autour de la ligne de commande au cas où il y aurait des espaces dans les noms de dossier ("Program Files", quelqu'un?). Tout ce qu'il contient est ceci: chaîne privée QuotesAround (string input) {return "\" "+ input +" \ "";}

J'espère que certains trouveront cela utile et gagneront quelques minutes si votre scénario est similaire au mien.

Développeur63
la source
0

Avec les solutions proposées précédemment, j'ai eu du mal à exécuter plusieurs commandes npm en boucle et à obtenir toutes les sorties sur la fenêtre de la console.

Cela a finalement commencé à fonctionner après avoir tout combiné des commentaires précédents, mais réorganisé le flux d'exécution du code.

Ce que j'ai remarqué, c'est que l'abonnement aux événements a été effectué trop tard (après le début du processus) et que certaines sorties n'ont donc pas été capturées.

Le code ci-dessous effectue maintenant les opérations suivantes:

  1. S'abonne aux événements avant le démarrage du processus, s'assurant ainsi qu'aucune sortie n'est manquée.
  2. Commence la lecture des sorties dès que le processus est lancé.

Le code a été testé contre les blocages, bien qu'il soit synchrone (une exécution de processus à la fois), donc je ne peux pas garantir ce qui se passerait si cela était exécuté en parallèle.

    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();
    }
Amiral Tuzović
la source
0

Utilisation de CliWrap :

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

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

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

Je sais que cela fonctionnera pour le fichier batch et les paramètres, mais aucune idée sur la façon d'obtenir les résultats en C #. Habituellement, les sorties sont définies dans le fichier batch.

macunix
la source