Executando arquivo em lote em c #


139

Estou tentando executar um arquivo em lotes em c #, mas não estou tendo sorte em fazê-lo.

Encontrei vários exemplos na Internet, mas não está funcionando para mim.

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

A cadeia de comandos contém o nome do arquivo em lotes (armazenado em system32) e alguns arquivos que devem ser manipulados. (Exemplo:) txtmanipulator file1.txt file2.txt file3.txt. Quando executo o arquivo em lotes manualmente, ele funciona corretamente.

Ao executar o código, isso me dá uma **ExitCode: 1** (Catch all for general errors)

O que estou fazendo de errado?


4
Você não mostra o que commandé. Se ele contiver caminhos com espaços, você precisará colocar aspas em torno deles.
Jon

@ Jon eu fiz isso, esse não é o problema. Obrigado pela sua contribuição!
Wessel T.

Há algo no seu arquivo em lotes falhando? Você pode definir o WorkingDirectory (ou qualquer que seja essa propriedade chamada) para o seu processo.
Jonas1

Bem, quando executo o código no comando manualmente (Iniciar -> Executar), ele é executado corretamente. Eu adicionei o WorkingDirectory agora e configurá-lo para system32, mas eu ainda obter o ErrorCode: 1
Wessel T.

Respostas:


191

Isso deve funcionar. Você pode tentar despejar o conteúdo dos fluxos de saída e erro para descobrir o que está acontecendo:

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

* EDIT *

Dadas as informações extras em seu comentário abaixo, consegui recriar o problema. Parece haver alguma configuração de segurança que resulta nesse comportamento (não a investigamos em detalhes).

Isso faz trabalho se o arquivo de lote não está localizado em C:\Windows\System32. Tente movê-lo para outro local, por exemplo, o local do seu executável. Observe que manter os arquivos em lotes personalizados ou executáveis ​​no diretório do Windows é uma prática recomendada de qualquer maneira.

* EDIT 2 * Ele gira para fora que, se os fluxos são lidos de forma síncrona, um impasse pode ocorrer, quer através da leitura de forma síncrona antes WaitForExitou lendo tanto stderre stdoutde forma síncrona um após o outro.

Isso não deve acontecer se você usar os métodos de leitura assíncrona, como no exemplo a seguir:

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
Obrigado! Agora eu realmente posso ver qual é o erro. "O C: \ Windows \ System32 \ txtmanipulator.bat não é reconhecido como um comando, programa ou arquivo de lote interno ou externo" (Traduzido do holandês) O que é estranho. Porque quando eu executo o txtmanipulator a partir da linha de comando, ele executa perfeitamente.
Wessel T.

2
Consegui recriar seu problema, confira a adição à resposta.
steinar

Esta abordagem não é aplicável quando eu corro "pg_dump ...> dumpfile", que despeja um banco de dados de 27 GB para dumpfile
Paul

Como posso pegar os dados da saída padrão / erro para evitar a acumulação (dado o lote pode ser executado por anos e eu quero ver os dados como se trata?)
Dani

O uso dos métodos de leitura assíncrona (consulte a edição 2) permitirá a saída de texto assim que uma linha for lida.
steinar 25/08/15

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

essa linha simples executará o arquivo em lotes.


3
como posso passar parâmetros e ler um resultado da execução de comandos?
Janatbek Sharsheyev

@JanatbekSharsheyev Veja se é isso que você pede ...
Não era eu

1
@JanatbekSharsheyev, você pode passar como argumentos. Veja abaixo o exemplo ProcessStartInfo info = new ProcessStartInfo ("c: \\ batchfilename.bat"); info.Arguments = "-parameter"; Process.Start (info)
sk1007 16/03

17

Depois de uma grande ajuda da steinar, foi isso que funcionou para mim:

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
No meu caso, um arquivo em lotes estava chamando outro arquivo em lotes usando ~%dp0. Adicionando o ProcessInfo.WorkingDirectoryfixo.
Sonata

1
Por que passar um commandse você está chamando o arquivo BAT diretamente?
Sfarbota

@sfarbota Argumentos para o arquivo BAT?
25915 sigl

@sigod Não sei se você está me fazendo uma pergunta ou sugerindo uma possível resposta para a minha. Sim, arquivos em lote podem receber argumentos. Mas se você está sugerindo que os commandparâmetros possam ser usados ​​para enviar argumentos para o arquivo BAT, não é isso que o código aqui mostra. Na verdade, não é usado. E se fosse, provavelmente deveria ser nomeado arguments.
sfarbota

@sfarbota Era uma suposição. By the way, commandé usado na new ProcessStartInfochamada.
sigod

13

Funciona bem. Eu testei assim:

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

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

Eu comentei desligar a janela para que eu pudesse vê-lo funcionar.


Obrigado pelo exemplo que esclareceu alguns pontos inicialmente confusos. São necessárias algumas etapas extras para transformar os exemplos anteriores em um método reutilizável, e o parâmetro "comando de cadeia" nos exemplos anteriores deveria ter sido nomeado args ou parâmetros, pois é isso que está sendo passado nele.
Developer63

7

Aqui está um exemplo de código c # que está enviando 2 parâmetros para um arquivo bat / cmd para responder a esta pergunta .

Comentário: como posso passar parâmetros e ler um resultado da execução de comandos?

/ de @Janatbek Sharsheyev

Opção 1: sem ocultar a janela do console, passando argumentos e sem obter as saídas

using System;
using System.Diagnostics;


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

Opção 2: ocultando a janela do console, transmitindo argumentos e obtendo resultados


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

O código abaixo funcionou bem para mim

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

Eu precisava atribuir TODO caminho no FileName para fazê-lo funcionar (mesmo que o WorkingDirectory tenha o mesmo caminho raiz ...). Se eu pular o caminho raiz Recebo exceção que não existe tal arquivo
Hawlett

Verifique o caminho, o que está compondo e verifique se existe ou não manualmente. Isso ajudará a descobrir o 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());
    }
}

Eu precisava atribuir TODO caminho no FileName para fazê-lo funcionar (mesmo que o WorkingDirectory tenha o mesmo caminho raiz ...). Se eu pular o caminho raiz Recebo exceção que não existe tal arquivo
Hawlett

1

Você já tentou iniciá-lo como administrador? Inicie o Visual Studio como administrador, se você o usar, porque trabalhar com .batarquivos requer esses privilégios.


0

Eu queria algo que fosse mais diretamente utilizável, sem valores de string codificados específicos da organização. Ofereço o seguinte como um pedaço de código diretamente reutilizável. A menor desvantagem é a necessidade de determinar e passar a pasta de trabalho ao fazer a chamada.

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

Chamado assim:

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

Neste exemplo, no Visual Studio 2017, como parte de uma execução de teste, desejo executar um arquivo em lotes de redefinição do ambiente antes de executar alguns testes. (SpecFlow + xUnit). Eu me cansei de etapas extras para executar manualmente o arquivo bat separadamente e queria apenas executá-lo como parte do código de configuração do teste C #. O arquivo em lote de redefinição do ambiente move os arquivos de caso de teste de volta para a pasta de entrada, limpa as pastas de saída etc. para obter o estado inicial de teste adequado para o teste. O método QuotesAround simplesmente coloca aspas na linha de comando, caso haja espaços nos nomes das pastas ("Arquivos de Programas", alguém?). Tudo o que há nele é: string privada QuotesAround (entrada de string) {return "\" "+ input +" \ "";}

Espero que alguns achem isso útil e economize alguns minutos se o seu cenário for semelhante ao meu.


0

Com as soluções propostas anteriormente, lutei para obter vários comandos npm executados em loop e obter todas as saídas na janela do console.

Finalmente, começou a funcionar depois que eu combinei tudo dos comentários anteriores, mas reorganizei o fluxo de execução do código.

O que notei é que a assinatura do evento foi feita tarde demais (após o processo já ter sido iniciado) e, portanto, algumas saídas não foram capturadas.

O código abaixo agora faz o seguinte:

  1. Assina os eventos antes do início do processo, garantindo, assim, que nenhuma saída seja perdida.
  2. Começa a ler as saídas assim que o processo é iniciado.

O código foi testado contra os deadlocks, embora seja síncrono (uma execução de processo por vez), portanto não posso garantir o que aconteceria se isso fosse executado em 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 o CliWrap :

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

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

-1

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

Eu sei que isso funcionará para arquivos e parâmetros em lote, mas nenhuma idéia de como obter os resultados em c #. Geralmente, as saídas são definidas no arquivo em lotes.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.