none
capturar o retorno de uma execução em console (outro software) RRS feed

  • Pergunta

  • Opa. 
    Conforme me foi orientado pelos colegas estou abrindo uma discussão sobre o meu caso.
    Preciso iniciar executar um exe que faz backup no firebird. Roda em console.

    to fazendo assim:


     Public Function backup() As Boolean

            Try
                Dim processo As New Process
     'caminho do arquivo que executa o backup
                Dim ArquivoGbak="C:\Arquivos de programas\Firebird\Firebird_2_1\bin\gbak.exe"

                'parametros que passo para o gbak para fazer o backup
                Dim Parametros As String = "-v -b -user sysdba -password masterkey "
                
                'Concateno os nomes dos backups
                Parametros += "D:\lDB\meubanco.FDB C:\arqbkp.fbk"
                
                'executo o processo
                processo = System.Diagnostics.Process.Start(ArquivoGbak, Parametros)

                processo.WaitForExit()

                processo.Close()

                Return True

            Catch ex As Exception
                Throw ex
            End Try

        End Function


    isso ta funcionando. Mas quero retornar as mensagens que ele apresenta no prompt do DOS num list ou grid.
    Alguma dica?

    vlw. ah, to usando vb 2008



    terça-feira, 30 de dezembro de 2008 17:21

Todas as Respostas

  • Declare no escopo do Form:

     

    private delegate void AtualizaListBoxDelegate(string valor);
    private Process processo;

     

    Inclua estes três métodos no Form, sendo dois deles handlers para eventos do objeto Process que assinaremos e o terceiro um método auxiliar para atualização do ListBox (chamado listBox1, no exemplo).:

     

    private void processo_OutputDataReceived(object sender, DataReceivedEventArgs e)
    {
        // Atualizamos o ListBox se houver informação
        // válida no parâmetro DataReceivedEventArgs.
        if (!String.IsNullOrEmpty(e.Data))
        {
            AtualizaListBox(e.Data);
        }
    }
    
    private void AtualizaListBox(string valor)
    {
        // Estamos na thread da UI?
        if (this.InvokeRequired)
        {
            // Não! Invocamos então este método na thread da UI passando-lhe
            // o parâmetro valor.
            this.BeginInvoke(new AtualizaListBoxDelegate(AtualizaListBox), new object[] { valor });
        }
        else
        {
            // Sim! Atualizamos o ListBox.
            listBox1.Items.Add(valor);
            listBox1.SelectedIndex = listBox1.Items.Count - 1;
        }
    }
    
    private void processo_Exited(object sender, EventArgs e)
    {
        // Limpeza.
        processo.OutputDataReceived -= new DataReceivedEventHandler(processo_OutputDataReceived);
        processo.Exited -= new EventHandler(processo_Exited);
        processo.Close();
        processo.Dispose();
        processo = null;
    
    }

     

    Finalmente, execute o programa externo:

     

    processo = new Process();
    
    ////caminho do arquivo que executa o backup
    //string ArquivoGbak = "C:\\Arquivos de programas\\Firebird\\Firebird_2_1\\bin\\gbak.exe";
    //caminho do arquivo que executa o backup
    string ArquivoGbak = @"C:\Documents and Settings\Administrador\Configurações locais\Dados de aplicativos\Temporary Projects\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe";
    //parametros que passo para o gbak para fazer o backup
    string Parametros = "-v -b -user sysdba -password masterkey ";
    
    //Concateno os nomes dos backups
    Parametros += "D:\\lDB\\meubanco.FDB C:\\arqbkp.fbk";
    
    // Configurações.
    ProcessStartInfo si = new ProcessStartInfo();
    si.FileName = ArquivoGbak;
    si.Arguments = Parametros;
    si.UseShellExecute = false;
    si.RedirectStandardOutput = true;
    si.CreateNoWindow = true;
    
    //executo o processo
    processo = System.Diagnostics.Process.Start(si);
    
    // Configurações do processo.
    processo.OutputDataReceived += new DataReceivedEventHandler(processo_OutputDataReceived);
    processo.Exited += new EventHandler(processo_Exited);
    processo.EnableRaisingEvents = true;
    processo.BeginOutputReadLine();

     

    terça-feira, 30 de dezembro de 2008 23:25
  • Opa.

    Traduzi para vb.net mas não rolou. Nada acontece... nem msg de erro.

    código abaixo: vlw.

     

    Public Class Form2

    Private Delegate Sub AtualizaListBoxDelegate(ByVal valor As String)

    Dim processo As Process

     

     

    Private Sub processo_OutputDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs)

    'Atualizamos o ListBox se houver informa‡Æo

    ' valida no parametro DataReceivedEventArgs.

    If Not String.IsNullOrEmpty(e.Data) Then

    AtualizaListBox(e.Data)

    End If

    End Sub

     

    Private Sub AtualizaListBox(ByVal valor As String)

    ' Estamos na thread da UI?

    If Me.InvokeRequired Then

    ' Nao! Invocamos entao este metodo na thread da UI passando-lhe

    ' o parametro valor.

    Me.BeginInvoke(New AtualizaListBoxDelegate(AddressOf AtualizaListBox), New Object() {valor})

    Else

    ' Sim! Atualizamos o ListBox.

    listBox1.Items.Add(valor)

    listBox1.SelectedIndex = (listBox1.Items.Count - 1)

    End If

    End Sub

     

    Private Sub processo_Exited(ByVal sender As Object, ByVal e As EventArgs)

    'Limpeza

    RemoveHandler processo.OutputDataReceived, AddressOf Me.processo_OutputDataReceived

    RemoveHandler processo.Exited, AddressOf Me.processo_Exited

    processo.Close()

    processo.Dispose()

    processo = Nothing

    End Sub

    Protected Sub Executar()

    processo = New Process

    'caminho do arquivo que executa o backup

    Dim ArquivoGbak As String = "C:Arquivos de programas\Firebird\Firebird_2_1\bin\gbak.exe"

    'parametros que passo para o gbak para fazer o backup

    Dim Parametros As String = "-v -b -user sysdba -password masterkey "

    'Concateno os nomes dos backups

    Parametros += "c:\\meubanco.fdb C:\testebkp.fbk"

    'Configura‡äes.

    Dim si As New ProcessStartInfo

    si.FileName = ArquivoGbak

    si.Arguments = Parametros

    si.UseShellExecute = False

    si.RedirectStandardOutput = True

    si.CreateNoWindow = True

    'executo o processo

    processo = System.Diagnostics.Process.Start(si)

    ' Configura‡äes do processo.

    AddHandler processo.OutputDataReceived, AddressOf Me.processo_OutputDataReceived

    AddHandler processo.Exited, AddressOf Me.processo_Exited

    processo.EnableRaisingEvents = True

    processo.BeginOutputReadLine()

     

    End Sub

     

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

    Executar()

    End Sub

    End Class

     

    quarta-feira, 31 de dezembro de 2008 13:12
  • Angus, o bkp é executado ok, só que não chama os eventos para listar no list.

     

    vlw.

     

    quarta-feira, 31 de dezembro de 2008 13:36
  • Ah, você está fazendo em VB. Preciso prestar mais atenção

     

    Fiz um teste aqui, criando este programa para console:

     

    Option Strict On
    Option Explicit On
    
    Imports System
    
    Public Class Programa
        Public Shared Sub Main(ByVal args As String())
            For i As Integer = 1 To 1000
                Console.WriteLine("Iteração: {0}", i)
            Next
        End Sub
    End Class

     

    Depois de compilado o programa (com o nome ProgramaTeste.exe) invoquei-o com este código:

     

    Private Delegate Sub AtualizaListBoxDelegate(ByVal valor As String)
    Private m_processo As Process
    
    Private Sub processo_OutputDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
        'Atualizamos o ListBox se houver informação
        ' valida no parametro DataReceivedEventArgs.
        If Not String.IsNullOrEmpty(e.Data) Then
            AtualizaListBox(e.Data)
        End If
    End Sub
    
    ' Converte entre diferentes codificações.
    Public Shared Function ConvertEncoding(ByVal value As String, ByVal src As System.Text.Encoding, ByVal trg As System.Text.Encoding) As String
        Dim dec As System.Text.Decoder = src.GetDecoder()
        Dim b As Byte() = trg.GetBytes(value)
        Dim len As Integer = dec.GetCharCount(b, 0, b.Length)
        Dim c As Char() = New Char(len - 1) {}
        dec.GetChars(b, 0, b.Length, c, 0)
        Return New String(c)
    End Function
    
    Private Sub AtualizaListBox(ByVal valor As String)
        ' Estamos na thread da UI?
        If Me.InvokeRequired Then
            ' Não! Invocamos entao este método na thread da UI passando-lhe
            ' o parâmetro valor.
            Me.BeginInvoke(New AtualizaListBoxDelegate(AddressOf AtualizaListBox), New Object() {valor})
        Else
            ' Sim! Atualizamos o ListBox.
    
            ' Obs. Code Page 850 (CP850) = DOSLatin1:
            ' http://aspell.net/charsets/codepages.html
            ListBox1.Items.Add(ConvertEncoding(valor, System.Text.Encoding.GetEncoding("CP850"), System.Text.Encoding.Default))
            ListBox1.SelectedIndex = (ListBox1.Items.Count - 1)
        End If
    End Sub
    
    Private Sub processo_Exited(ByVal sender As Object, ByVal e As EventArgs)
        'Limpeza
        RemoveHandler m_processo.OutputDataReceived, AddressOf Me.processo_OutputDataReceived
        RemoveHandler m_processo.Exited, AddressOf Me.processo_Exited
        m_processo.Close()
        m_processo.Dispose()
        m_processo = Nothing
    End Sub
    
    Protected Sub Executar()
        m_processo = New Process()
        'caminho do arquivo que executa o backup
        Dim ArquivoGbak As String = "C:Arquivos de programas\Firebird\Firebird_2_1\bin\gbak.exe"
        'parametros que passo para o gbak para fazer o backup
        Dim Parametros As String = "-v -b -user sysdba -password masterkey "
        'Concateno os nomes dos backups
        Parametros += "c:\\meubanco.fdb C:\testebkp.fbk"
        'Configurações.
        Dim si As New ProcessStartInfo()
        si.FileName = "D:\ProgramaTeste.exe" 'ArquivoGbak
        si.Arguments = Parametros
        si.UseShellExecute = False
        si.RedirectStandardOutput = True
        si.CreateNoWindow = True
        'executo o processo
        m_processo = System.Diagnostics.Process.Start(si)
        ' Configurações do processo.
        AddHandler m_processo.OutputDataReceived, AddressOf Me.processo_OutputDataReceived
        AddHandler m_processo.Exited, AddressOf Me.processo_Exited
        m_processo.EnableRaisingEvents = True
        m_processo.BeginOutputReadLine()
    End Sub

     

    Tudo funcionou como esperado. Você tem certeza que o programa está sendo executado?

     

    Comente a propriedade CreateNoWindow do objeto ProcessStartInfo para ver se a janela do DOS é aberta etc. Também experimente trocar o método que faz a invocação da thread da UI de BeginInvoke() por Invoke() (não acho que possa ter algo a ver, mas não custa testar).

     

    PS: Como poderá ver pelo código, incluí um método para fazer a conversão da saída do console para que caracteres como o cedilha apareçam corretamente num sistema em português etc.

    quarta-feira, 31 de dezembro de 2008 15:33
  • Dae Angus.
    Não tem jeito de retornar no list o que o resultado do gbak. Tenho certeza que está sendo executado pois o arquivo
    testebkp.fbk é criado no C:\;

    fiz um console também e ai funciona normalmente.

    tirando o a linha
    CreateNoWindow = True aparece a janela console só que vazia...
    ai que está a diferença. Deveria estar listando várias linhas de status.

    fiz de uma outra forma e ai no console aparece, mas nao consido inserir no list, veja abaixo:
    também tem a questão da thread que não está ai.... vlw.


    Public Function backup() As Boolean

    Try
               'caminho do arquivo que executa o backup
    Dim ArquivoGbak As String = "C:Arquivos de programas\Firebird\Firebird_2_1\bin\gbak.exe"
    'parametros que passo para o gbak para fazer o backup
    Dim Parametros As String = "-v -b -user sysdba -password masterkey "

    Parametros += "c:\\meubanco.fdb C:\testebkp.fbk"

    processo = System.Diagnostics.Process.Start(ArquivoGbak, Parametros)

    processo.WaitForExit()

    processo.Close()

    Return True

    Catch ex As Exception
    Throw ex
    End Try

    End Function
    sexta-feira, 2 de janeiro de 2009 12:15
  •  Cássio wrote:

    Não tem jeito de retornar no list o que o resultado do gbak. Tenho certeza que está sendo executado pois o arquivo

     

    Olá Cássio,

     

    Dependendo de como o gbak foi desenvolvido, talvez ele não esteja retornando os códigos de retorno corretos para que sejam disparados os eventos... Ainda, pode ser que esteja retornando algum código de erro, e as mensagem estejam indo parar no StandardError, ao invés de irem para a saída padrão (StandardOutput).

     

    Sugiro que você faça um teste simples, redirecionando as duas saídas: StandardOutput e StandardError, para ver se consegue ler alguma informação dessas duas saídas.

     

    Veja um exemplo:

     

    Code Snippet

     

     'Caminho da aplicação que executa o backup (gbak)

     Dim arquivoGbak As String

     arquivoGbak = "C:\Arquivos de programas\Firebird\Firebird_2_1\bin\gbak.exe"

     

     'Parametros que serão enviados para o gbak, para fazer o backup

     Dim parametros As String

     parametros = "-v -b -user sysdba -password masterkey c:\\meubanco.fdb C:\testebkp.fbk"

     

     Dim processo As New Process()

     Using (processo)

    'Define o caminho da aplicação que será executada e os parâmetros

    processo.StartInfo.FileName = arquivoGbak

    processo.StartInfo.Arguments = parametros

     

    'Redireciona a saída padrão e também os erros que possam acontecer

    processo.StartInfo.RedirectStandardOutput = True

    processo.StartInfo.RedirectStandardError = True

     

    'Desabilita o ShellExecute por causa do redirecionamento (exigência)

    processo.StartInfo.UseShellExecute = False

     

    'Define que a janela do novo processo deve ficar escondida

    processo.StartInfo.CreateNoWindow = True

     

    'Executa o processo

    processo.Start()

     

    Dim linha As String

     

    'Tenta ler as linhas da saída padrão

    Do

    'Tenta ler a próxima linha

    linha = processo.StandardOutput.ReadLine()

     

    'Acabaram as linhas?

    If linha Is Nothing Then

    'Sim... Então abandona o loop

    Exit Do

    End If

     

    'Adiciona a linha em um ListBox, para você acompanhar

    Me.SeuListBox.Items.Add(linha)

    Loop

     

     

    'Tenta ler as linhas da saída de erros

    Do

    'Tenta ler a próxima linha

    linha = processo.StandardOutput.ReadLine()

     

    'Acabaram as linhas?

    If linha Is Nothing Then

    'Sim... Então abandona o loop

    Exit Do

    End If

     

    'Adiciona a linha em um ListBox, para você acompanhar

    Me.SeuListBox.Items.Add(linha)

    Loop

     

    'Aguarda que o processo seja finalizado

    processo.WaitForExit()

     End Using

     

 

 

Vale lembrar que o ideal seria utilizar os eventos, como sugeriu o Angus, mas se você diz que os eventos não são disparados, então é provável que a aplicação gbak não tenha sido desenvolvida corretamente, então o jeito é ler o Stream diretamente, conforme os dados forem chegando.

 

Abraços,
Caio Proiete




Caio Proiete
http://www.caioproiete.com
sexta-feira, 2 de janeiro de 2009 14:00
Moderador
  • vlw as dicas ai.. mas não funcionou com o gbak.. vou ter que estudar outra forma de fazer... com este exemplo do caio, quando vai na linha

    linha = processo.StandardOutput.Readline

    pindura e não sai. como se entrasse num loop infinito.

    pior de tudo é que no exemplo do angus o backup é executado, gera o arquivo de backup, mas não dá o retorno desejado.


    vlw mais uma vez.

    []s
    sexta-feira, 2 de janeiro de 2009 16:04
  • Hoje tive acesso a uma máquina com o Firebird instalado e testei a execução do programa gbak.exe e realmente por algum motivo o objeto Process não obtém a saída de console do dito programa.

     

    Eu consegui, entretanto, fazer com que a saída de console fosse obtida com o uso APIs.

     

     

    Declarações e Métodos:

     

    Private Const STARTF_USESHOWWINDOW As Integer = &H1
    Private Const STARTF_USESTDHANDLES As Integer = &H100
    Private Const SW_HIDE As Integer = &H0
    
    <StructLayout(LayoutKind.Sequential)> _
    Private Structure PROCESS_INFORMATION
        Public hProcess As IntPtr
        Public hThread As IntPtr
        Public ProcessId As Integer
        Public ThreadId As Integer
    End Structure
    
    <StructLayout(LayoutKind.Sequential)> _
    Private Structure SECURITY_ATTRIBUTES
        Public nLength As Integer
        Public lpSecurityDescriptor As IntPtr
        Public bInheritHandle As Integer
    End Structure
    
    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
    Private Structure STARTUPINFO
        Public cb As Integer
        Public lpReserved As String
        Public lpDesktop As String
        Public lpTitle As String
        Public dwX As Integer
        Public dwY As Integer
        Public dwXSize As Integer
        Public dwYSize As Integer
        Public dwXCountChars As Integer
        Public dwYCountChars As Integer
        Public dwFillAttribute As Integer
        Public dwFlags As Integer
        Public wShowWindow As Short
        Public cbReserved2 As Short
        Public lpReserved2 As Integer
        Public hStdInput As Integer
        Public hStdOutput As Integer
        Public hStdError As Integer
    End Structure
    
    <DllImport("kernel32.dll")> _
    Private Shared Function ReadFile( _
        ByVal hFile As IntPtr, _
        ByVal lpBuffer As Byte(), _
        ByVal nNumberOfBytesToRead As UInteger, _
        ByRef lpNumberOfBytesRead As UInteger, _
        ByVal lpOverlapped As IntPtr) As Integer
    End Function
    
    <DllImport("kernel32.dll")> _
    Private Shared Function CreatePipe( _
        ByRef hReadPipe As IntPtr, _
        ByRef hWritePipe As IntPtr, _
        ByRef lpPipeAttributes As SECURITY_ATTRIBUTES, _
        ByVal nSize As UInteger) As Integer
    End Function
    
    <DllImport("kernel32.dll", SetLastError:=True)> _
    Private Shared Function CloseHandle( _
        ByVal hObject As IntPtr) As Integer
    End Function
    
    <DllImport("kernel32.dll")> _
    Private Shared Function CreateProcess( _
        ByVal lpApplicationName As String, _
        ByVal lpCommandLine As String, _
        ByVal lpProcessAttributes As IntPtr, _
        ByVal lpThreadAttributes As IntPtr, _
        ByVal bInheritHandles As Boolean, _
        ByVal dwCreationFlags As UInteger, _
        ByVal lpEnvironment As IntPtr, _
        ByVal lpCurrentDirectory As String, _
        ByRef lpStartupInfo As STARTUPINFO, _
        ByRef lpProcessInformation As PROCESS_INFORMATION) As Boolean
    End Function
    
    Private Shared Sub ExecAndCapture(ByVal sCommandLine As String, _
                              ByVal cListBox As ListBox, _
                              ByVal sStartInFolder As String)
    
        Const BUFSIZE As Integer = 1024 * 10
        Dim hPipeRead As IntPtr = IntPtr.Zero
        Dim hPipeWrite As IntPtr = IntPtr.Zero
        Dim sa As SECURITY_ATTRIBUTES
        Dim si As STARTUPINFO = Nothing
        Dim pi As PROCESS_INFORMATION = Nothing
        Dim baOutput(BUFSIZE) As Byte
        Dim sOutput As String
        Dim lBytesRead As UInteger
    
        With sa
            .nLength = Marshal.SizeOf(sa)
            .bInheritHandle = 1
        End With
    
        If CreatePipe(hPipeRead, hPipeWrite, sa, 0UI) = 0 Then
            Return
        End If
    
        With si
            .cb = Marshal.SizeOf(si)
            .dwFlags = STARTF_USESHOWWINDOW Or STARTF_USESTDHANDLES
            .wShowWindow = SW_HIDE
            .hStdOutput = hPipeWrite.ToInt32()
            .hStdError = hPipeWrite.ToInt32()
        End With
    
        Dim ret As Boolean = _
            CreateProcess(Nothing, _
            sCommandLine, _
            IntPtr.Zero, _
            IntPtr.Zero, _
            True, _
            0UI, _
            IntPtr.Zero, _
            sStartInFolder, _
            si, _
            pi)
    
        If ret Then
            Call CloseHandle(hPipeWrite)
            Call CloseHandle(pi.hThread)
            hPipeWrite = IntPtr.Zero
            While True
                If ReadFile(hPipeRead, baOutput, BUFSIZE, lBytesRead, IntPtr.Zero) = 0 Then
                  Exit While
                End If
                ' Obs. Code Page 850 (CP850) = DOSLatin1:
                ' http://aspell.net/charsets/codepages.html
                sOutput = ConvertEncoding(baOutput, System.Text.Encoding.GetEncoding("CP850"), System.Text.Encoding.Default)
                sOutput = sOutput.Substring(0, Convert.ToInt32(lBytesRead))
                cListBox.Items.AddRange(sOutput.Split(New String() {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries))
                ' O código abaixo foi incluído para mostrar a atualização do ListBox
                ' conforme ela acontece.
                ' Seria mais interessante, entretanto, se este método fosse executado
                ' numa thread secundária e a inclusão de itens no ListBox 
                ' feita com o uso de delegadas e dos métodos
                ' BeginInvoke()/Invoke() dos controles.
                ' Veja exemplos:
                ' http://forums.microsoft.com/MSDN-BR/ShowPost.aspx?PostID=4226058&SiteID=21
                ' http://forums.microsoft.com/msdn-br/ShowPost.aspx?PostID=1847203&SiteID=21  
                cListBox.SelectedIndex = cListBox.Items.Count - 1
                Application.DoEvents()
            End While
            Call CloseHandle(pi.hProcess)
        End If
        ' Fecha os handles.
        Call CloseHandle(hPipeRead)
        Call CloseHandle(hPipeWrite)
    End Sub
    
    ' Converte entre diferentes codificações.
    Private Shared Function ConvertEncoding(ByVal value As Byte(), ByVal src As System.Text.Encoding, ByVal trg As System.Text.Encoding) As String
        ' Obs. ver codepages em:
        ' http://aspell.net/charsets/codepages.html
        Dim dec As System.Text.Decoder = src.GetDecoder()
        Dim len As Integer = dec.GetCharCount(value, 0, value.Length)
        Dim c As Char() = New Char(len - 1) {}
        dec.GetChars(value, 0, value.Length, c, 0)
        Return New String(c)
    End Function

     

    Uso:

     

    Dim linhaComando As String = _
        "C:\Arquivos de programas\Firebird\Firebird_2_1\bin\gbak.exe " & _
        "-v -b -user sysdba -password masterkey " & _
        "D:\FireBirdDataB\SeuBanco.FDB D:\testebkp.fbk"
    Call ExecAndCapture(linhaComando, ListBox1, Nothing)

     

     

    O código foi convertido e adaptado a partir do exemplo encontrado aqui:

     

    http://www.vb-helper.com/howto_capture_console_stdout.html

    sexta-feira, 2 de janeiro de 2009 17:17
  • Funcionou! Muito obrigado pela atenção. Realmente vocês são show de bola!!

    abraço!
    sexta-feira, 2 de janeiro de 2009 19:49
  • Olhando novamente o código agora a noite com mais tempo e calma eu peguei um bug na minha conversão. Já editei lá em cima e corrigi. Pegue o código novamente.

     

    sexta-feira, 2 de janeiro de 2009 22:09
  • certo. Ali na atualização do list eu tinha usado list.refresh. Estava funcionando também.

    Eu preciso agora tratar o erro. Pensei em monitorar cada linha escrita no list e caso achar a palavra ERROR eu gerar uma exceção. Existe uma classe do vs.net que eu possa usar para fazer esta localização?


    vlw.
    sábado, 3 de janeiro de 2009 17:55
  • Há várias coisas que você pode fazer. Mas olha só que história interessante...

     

    Eu iria sugerir que você criasse uma classe para as rotinas, criasse dois pipes, um para as mensagens de StdOut e outro para as mensagens de StdError ao invés de um único pipe para StdOut e StdError (como estava no exemplo) etc. e disparasse um evento ou gerasse uma exceção na sua classe quando recebesse algo em StdError. Eis que já preparado o novo exemplo em boa parte, pipe extra criado, StdError redirecionado na estrutura STARTUPINFO para o handle de escrita do novo pipe etc., eu percebo que só StdError estava recebendo mensagens; nada em StdOut.

     

    Naturalmente, lembrei-me imediatamente da sugestão do nosso cologa Caio Proiete acima, de que você tentasse redirecionar StdError no objeto Process. Como na sua resposta a ele você disse que não havia funcionado, que havia ocorrido um travamento no exemplo etc, eu não me interessei em testar mais a idéia por mim mesmo: Quando tive acesso à máquina com o Firebird instalado testei apenas StdOut como no meu primeiro exemplo, isto é, usando a abordagem de trabalhar com os eventos do objeto Process etc. Mas agora, com o comportamento observado na rotina com as APIs, fui olhar o código de exemplo de perto e testá-lo.

     

    Havia dois problemas. (1) Realmente há um travamento quando o método ReadLine() da propriedade StandardOutput do objeto Process é invocado com o processo doprograma gbak.exe e (2) há um pequeno erro no laço de StdError no exemplo, que na verdade está idêntico ao laço de StdOut, ou seja a propriedade de StdOut do objeto Process (StandardOutput) está sendo usada ao invés da propriedade de StdError do objeto Process (StandardError):

     

    'Tenta ler as linhas da saída de erros
    Do
        'Tenta ler a próxima linha
        linha = processo.StandardOutput.ReadLine()
    
        'Acabaram as linhas?
        If linha Is Nothing Then
            'Sim... Então abandona o loop
            Exit Do
        End If
    
        'Adiciona a linha em um ListBox, para você acompanhar
        Me.SeuListBox.Items.Add(linha)
    Loop

     

     

    Porém, eliminado-se o laço de StdOut e corrigindo-se o laço de StdError tudo... funcionou:

     

    'Caminho da aplicação que executa o backup (gbak)
    Dim arquivoGbak As String
    arquivoGbak = "C:\Arquivos de programas\Firebird\Firebird_2_1\bin\gbak.exe"
    
    'Parametros que serão enviados para o gbak, para fazer o backup
    Dim parametros As String
    parametros = "-v -b -user sysdba -password masterkey D:\SeuBanco.FDB D:\testebkp.fbk"
    
    Dim processo As New Process()
    
    Using (processo)
        'Define o caminho da aplicação que será executada e os parâmetros
        processo.StartInfo.FileName = arquivoGbak
        processo.StartInfo.Arguments = parametros
    
        'Redireciona a saída padrão e também os erros que possam acontecer
        processo.StartInfo.RedirectStandardError = True
    
        'Desabilita o ShellExecute por causa do redirecionamento (exigência)
        processo.StartInfo.UseShellExecute = False
    
        'Define que a janela do novo processo deve ficar escondida
        processo.StartInfo.CreateNoWindow = True
    
        'Executa o processo
        processo.Start()
    
        Dim linha As String
    
        'Tenta ler as linhas da saída de erros
        Do
            'Tenta ler a próxima linha
            linha = processo.StandardError.ReadLine()
    
            'Acabaram as linhas?
            If linha Is Nothing Then
                'Sim... Então abandona o loop
                Exit Do
            End If
    
            'Adiciona a linha em um ListBox, para você acompanhar
            Me.ListBox1.Items.Add(linha)
        Loop
    
        'Aguarda que o processo seja finalizado
        processo.WaitForExit()
    End Using

     

    Desnecessário dizer, o método anterior com os eventos funcionou maravilhosamente quando modificado para receber eventos de StdError também:

     

    Private Delegate Sub AtualizaListBoxDelegate(ByVal valor As String)
    Private m_processo As Process
    
    Private Sub processo_OutputDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
        'Atualizamos o ListBox se houver informação
        ' valida no parametro DataReceivedEventArgs.
        If Not String.IsNullOrEmpty(e.Data) Then
            Call AtualizaListBox(e.Data)
        End If
    End Sub
    
    Private Sub processo_ErrorDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
        'Atualizamos o ListBox se houver informação
        ' valida no parametro DataReceivedEventArgs.
        If Not String.IsNullOrEmpty(e.Data) Then
            Call AtualizaListBox(e.Data)
        End If
    End Sub
    
    ' Converte entre diferentes codificações.
    Public Shared Function ConvertEncoding(ByVal value As String, ByVal src As System.Text.Encoding, ByVal trg As System.Text.Encoding) As String
        ' Obs. ver codepages em:
        ' http://aspell.net/charsets/codepages.html 
        Dim dec As System.Text.Decoder = src.GetDecoder()
        Dim b As Byte() = trg.GetBytes(value)
        Dim len As Integer = dec.GetCharCount(b, 0, b.Length)
        Dim c As Char() = New Char(len - 1) {}
        dec.GetChars(b, 0, b.Length, c, 0)
        Return New String(c)
    End Function
    
    Private Sub AtualizaListBox(ByVal valor As String)
        ' Estamos na thread da UI?
        If Me.InvokeRequired Then
            ' Não! Invocamos entao este método na thread da UI passando-lhe
            ' o parâmetro valor.
            Me.BeginInvoke(New AtualizaListBoxDelegate(AddressOf AtualizaListBox), New Object() {valor})
        Else
            ' Sim! Atualizamos o ListBox.
    
            ' Obs. Code Page 850 (CP850) = DOSLatin1:
            ' http://aspell.net/charsets/codepages.html
            ListBox1.Items.Add(ConvertEncoding(valor, System.Text.Encoding.GetEncoding("CP850"), System.Text.Encoding.Default))
            ListBox1.SelectedIndex = (ListBox1.Items.Count - 1)
        End If
    End Sub
    
    Private Sub processo_Exited(ByVal sender As Object, ByVal e As EventArgs)
        'Limpeza
        RemoveHandler m_processo.OutputDataReceived, AddressOf Me.processo_OutputDataReceived
        RemoveHandler m_processo.ErrorDataReceived, AddressOf Me.processo_ErrorDataReceived
        RemoveHandler m_processo.Exited, AddressOf Me.processo_Exited
        m_processo.Close()
        m_processo.Dispose()
        m_processo = Nothing
    End Sub
    
    Protected Sub Executar()
        m_processo = New Process()
        'caminho do arquivo que executa o backup
        Dim ArquivoGbak As String = "C:\Arquivos de programas\Firebird\Firebird_2_1\bin\gbak.exe"
        'parametros que passo para o gbak para fazer o backup
        Dim Parametros As String = "-v -b -user sysdba -password masterkey "
        'Concateno os nomes dos backups
        Parametros += "D:\SeuBanco.fdb D:\testebkp.fbk"
        'Configurações.
        Dim si As New ProcessStartInfo()
        si.FileName = ArquivoGbak
        si.Arguments = Parametros
        si.UseShellExecute = False
        si.RedirectStandardError = True
        si.CreateNoWindow = True
        'executo o processo
        m_processo = System.Diagnostics.Process.Start(si)
        ' Configurações do processo.
        ' Vamos assinar o evento OutputDataReceived também, afinal esse comportamento
        ' errado do programa gbak.exe poderá ser corrigido no futuro.
        AddHandler m_processo.OutputDataReceived, AddressOf Me.processo_OutputDataReceived
        AddHandler m_processo.ErrorDataReceived, AddressOf Me.processo_ErrorDataReceived
        AddHandler m_processo.Exited, AddressOf Me.processo_Exited
        m_processo.EnableRaisingEvents = True
        m_processo.BeginErrorReadLine()
    End Sub

     

    Arrrrgh...

     

     

    Assim, acredito que você possa esquecer as APIs e usar o objeto Process...

     

    Quanto à sua pergunta, há varias coisas que você pode fazer, dependendo de como queira fazer (se usando-se a abordagem de trabalhar com os eventos do objeto Process etc.); o comportamento do programa de enviar todas as mensagens para StdError também deve ser levando em consideração e limita um pouco as coisas (se o programa funcionasse como deveria você poderia reagir ao evento disparado quando as mensagens de StdError fossem recebidas).

     

    Usando-se a abordagem de trabalhar com os eventos do objeto Process, que é a melhor opção, você poderia, numa abordagem bem simples, setar um flag quando percebesse a palavra "ERROR" na propriedade Data do objeto. Algo assim:

     

     

    Declarações e Métodos:

     

    Private Delegate Sub AtualizaListBoxDelegate(ByVal valor As String)
    Private m_processo As Process
    ' Flag para indicar a ocorrência de erros.
    Private m_statusExecucao As Boolean = True
    ' StringBuilder para armazenar as mensagem de erro.
    Private m_sbErro As StringBuilder
    
    Private Sub processo_OutputDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
        ' Atualizamos o ListBox se houver informação
        ' valida no parametro DataReceivedEventArgs.
        If Not String.IsNullOrEmpty(e.Data) Then
            Call AtualizaListBox(e.Data)
        End If
    End Sub
    
    Private Sub processo_ErrorDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
        ' Atualizamos o ListBox se houver informação
        ' valida no parametro DataReceivedEventArgs.
        If Not String.IsNullOrEmpty(e.Data) Then
            ' Atualizamos o ListBox com todas as mensagens, 
            ' inclusive as de erro.  
            Call AtualizaListBox(e.Data)
    
            ' A propriedade Data contém a palavra "ERROR" seja em caixa
            ' alta como em caixa baixa? (No exemplo palavras contendo
            ' a substrng "ERROR", como "ERROR", também preencherão
            ' a condição e serão armazenadas no StringBuilder
            ' Este comportamento pode não ser desejável. Se não for
            ' altere a verificação etc.).
            If e.Data.ToUpper().Contains("ERROR") Then
                ' SIM! Marcamos o flag que indica a ocorrência 
                ' de erros e armazenamos as mensagens apenas das
                ' linhas que contêm a palavra "ERRO"
                m_statusExecucao = False
                ' Armazenamos a mensagem de erro.
                m_sbErro.AppendLine(e.Data)
            End If
        End If
    End Sub
    
    ' Converte entre diferentes codificações.
    Public Shared Function ConvertEncoding(ByVal value As String, ByVal src As System.Text.Encoding, ByVal trg As System.Text.Encoding) As String
        ' Obs. ver codepages em:
        ' http://aspell.net/charsets/codepages.html 
        Dim dec As System.Text.Decoder = src.GetDecoder()
        Dim b As Byte() = trg.GetBytes(value)
        Dim len As Integer = dec.GetCharCount(b, 0, b.Length)
        Dim c As Char() = New Char(len - 1) {}
        dec.GetChars(b, 0, b.Length, c, 0)
        Return New String(c)
    End Function
    
    Private Sub AtualizaListBox(ByVal valor As String)
        ' Estamos na thread da UI?
        If Me.InvokeRequired Then
            ' Não! Invocamos entao este método na thread da UI passando-lhe
            ' o parâmetro valor.
            Me.BeginInvoke(New AtualizaListBoxDelegate(AddressOf AtualizaListBox), New Object() {valor})
        Else
            ' Sim! Atualizamos o ListBox.
    
            ' Obs. Code Page 850 (CP850) = DOSLatin1:
            ' http://aspell.net/charsets/codepages.html
            ListBox1.Items.Add(ConvertEncoding(valor, System.Text.Encoding.GetEncoding("CP850"), System.Text.Encoding.Default))
            ListBox1.SelectedIndex = (ListBox1.Items.Count - 1)
        End If
    End Sub
    
    Private Sub processo_Exited(ByVal sender As Object, ByVal e As EventArgs)
        'Limpeza
        RemoveHandler m_processo.OutputDataReceived, AddressOf Me.processo_OutputDataReceived
        RemoveHandler m_processo.ErrorDataReceived, AddressOf Me.processo_ErrorDataReceived
        RemoveHandler m_processo.Exited, AddressOf Me.processo_Exited
        m_processo.Close()
        m_processo.Dispose()
        m_processo = Nothing
    End Sub
    
    Protected Sub Executar()
        m_sbErro = New StringBuilder()
    
        m_processo = New Process()
        'caminho do arquivo que executa o backup
        Dim ArquivoGbak As String = "C:\Arquivos de programas\Firebird\Firebird_2_1\bin\gbak.exe"
        'parametros que passo para o gbak para fazer o backup
        Dim Parametros As String = "-v -b -user sysdba -password masterkey "
        'Concateno os nomes dos backups
        Parametros += "D:\SeuBanco.fdb D:\testebkp.fbk"
        'Configurações.
        Dim si As New ProcessStartInfo()
        si.FileName = ArquivoGbak
        si.Arguments = Parametros
        si.UseShellExecute = False
        si.RedirectStandardError = True
        si.CreateNoWindow = True
        'executo o processo
        m_processo = System.Diagnostics.Process.Start(si)
        ' Configurações do processo.
        ' Vamos assinar o evento OutputDataReceived também, afinal esse comportamento
        ' errado do programa gbak.exe poderá ser corrigido no futuro.
        AddHandler m_processo.OutputDataReceived, AddressOf Me.processo_OutputDataReceived
        AddHandler m_processo.ErrorDataReceived, AddressOf Me.processo_ErrorDataReceived
        AddHandler m_processo.Exited, AddressOf Me.processo_Exited
        m_processo.EnableRaisingEvents = True
        m_processo.BeginErrorReadLine()
        m_processo.WaitForExit()
    End Sub

     

    Uso:

     

    Call Executar()
    ' Verificamnos se houve algum erro durante a execução
    ' do método Executar() e se houve exebimos as mensagens.
    ' armazenadas no StringBuilder.
    If Not m_statusExecucao Then
         MessageBox.Show(m_sbErro.ToString())
    End If

     

    O ideal (eu diria, correto) seria escrever uma classe. Você criaria nessa classe propriedades para receber e retornar os diversos valores em que pudesse estar interessado, como o caminho para o programa a ser executado, argumentos, retorno de erros etc.), criaria sobrecargas de construtores para receber também o caminho do arquivo a ser executado e os argumentos (ajustando as propriedades nos construtores), faria o método Executar() retornar um valor booleano (verdadeiro/falso) para determinar se a execução do programa externo foi bem-sucedida etc.

     

     

     

    Nota para mim mesmo: Sempre testar todas as idéias eu mesmo :-)
    sábado, 3 de janeiro de 2009 23:09