EJECUTAR CMD EN APLICACIÓN .NET

Iniciado por rochro, 11 Noviembre 2014, 15:39 PM

0 Miembros y 4 Visitantes están viendo este tema.

rochro

Buen día a todos, quisiera que me apoyen en poder ver cual es mi error ya que al momento de ejecutar la app se marca error en la linea txtResults.Text = SR.ReadToEnd.
La idea es que en vez que se muestre en la ventana del cmd, figure en el cuadro de texto txtResults y sería mucho mejor si fuera continuo o sea el resultado de ping 127.0.0.1 -t figure en el cuadro de texto.

Código (vbnet) [Seleccionar]
Private Sub btnSend_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSend.Click
        Dim CMDThread As New Threading.Thread(AddressOf CMDAutomate)
        CMDThread.Start()
    End Sub
    Private Sub CMDAutomate()
        Dim PROCESO As New Process
        Dim INFO As New System.Diagnostics.ProcessStartInfo
        INFO.FileName = "cmd"
        INFO.RedirectStandardInput = True
        INFO.RedirectStandardOutput = True
        INFO.UseShellExecute = False
        PROCESO.StartInfo = INFO
        PROCESO.Start()
        Dim SR As System.IO.StreamReader = PROCESO.StandardOutput
        Dim SW As System.IO.StreamWriter = PROCESO.StandardInput
        Dim comandoping As String = String.Concat("ping ", TextBox1.Text, ".", TextBox2.Text, ".", TextBox3.Text, ".", TextBox4.Text)
        SW.WriteLine(comandoping)
        SW.WriteLine("exit")
        txtResults.Text = SR.ReadToEnd
        SW.Close()
        SR.Close()
    End Sub


Gracias por todo. Saludos.

Eleкtro

#1
Cita de: rochro en 11 Noviembre 2014, 15:39 PMal momento de ejecutar la app se marca error en la linea txtResults.Text = SR.ReadToEnd.

1. Cuando tengas un error, por favor da los detalles necesarios para poder ayudarte. El mensaje exacto de error que te indica el debugger de VisualStudio. ya que de lo contrario estamos haciendo el paripé sin tener información precisa.

Sea cual sea el error que te indique imagino que es porque estás intentando modificar un control desde un Thread distinto al Thread donde creaste el cotnrol. es decir, tienes el control txtResults en el thread "X" e intentas modificarlo desde el thread "Y", no puedes modificar un control así como así desde otro thread, primero debes comprobar si el control necesita ser invocado, y después, invocarlo.

Ejemplo:
Código (vbnet) [Seleccionar]
Imports System.Threading.Tasks

Public Class Form1

   ''' <summary>
   ''' The CMD <see cref="System.Diagnostics.Process"/> instance.
   ''' </summary>
   Private WithEvents cmdProcess As New Process With
       {
           .EnableRaisingEvents = True,
           .StartInfo = New ProcessStartInfo With
                            {
                               .FileName = "cmd.exe",
                               .Arguments = String.Empty,
                               .RedirectStandardInput = False,
                               .RedirectStandardOutput = True,
                               .RedirectStandardError = True,
                               .UseShellExecute = False,
                               .CreateNoWindow = True
                            }
       }

   ''' <summary>
   ''' Gets the ping commandline arguments.
   ''' </summary>
   Private ReadOnly Property PingArguments As String
       Get
           Return String.Format("ping.exe ""{0}.{1}.{2}.{3}""",
                               TextBox1.Text, TextBox2.Text,
                               TextBox3.Text, TextBox4.Text)
       End Get
   End Property

   Private Sub btnSend_Click(ByVal sender As Object, ByVal e As EventArgs) _
   Handles btnsend.Click

       Task.Factory.StartNew(AddressOf CMDAutomate)

   End Sub

   Private Sub CMDAutomate()

       With Me.cmdProcess

           .StartInfo.Arguments = String.Format("/C ""{0}""", Me.PingArguments)
           .Start()
           .BeginOutputReadLine()
           .BeginErrorReadLine()
           .WaitForExit()

       End With

   End Sub

   ''' <summary>
   ''' Occurs when an application writes to its redirected <see cref="System.Diagnostics.Process.StandardOutput"/> stream.
   ''' Occurs when an application writes to its redirected <see cref="System.Diagnostics.Process.StandardError"/>  stream.
   ''' </summary>
   Private Sub cmdProcess_OutputDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs) _
   Handles cmdProcess.OutputDataReceived,
           cmdProcess.ErrorDataReceived

       Select Case txtResults.InvokeRequired

           Case True
               txtResults.Invoke(Sub() txtResults.AppendText("" & e.Data))
               txtResults.Invoke(Sub() txtResults.AppendText(Environment.NewLine))

           Case Else
               txtResults.AppendText(e.Data)
               txtResults.AppendText(Environment.NewLine)

       End Select

#If DEBUG Then
       ' Debug.WriteLine(e.Data)
#End If

   End Sub

   ''' <summary>
   ''' Occurs when a <see cref="System.Diagnostics.Process"/> exits.
   ''' </summary>
   Private Sub cmdProcess_Exited(ByVal sender As Object, ByVal e As EventArgs) _
   Handles cmdProcess.Exited

       Debug.WriteLine(String.Format("cmdProcess has exited with exit code: {0}",
                                     DirectCast(sender, Process).ExitCode))

   End Sub

End Class


Saludos








rochro

Cita de: Eleкtro en 11 Noviembre 2014, 18:29 PM

Sea cual sea el error que te indique imagino que es porque estás intentando modificar un control desde un Thread distinto al Thread donde creaste el cotnrol. es decir, tienes el control txtResults en el thread "X" e intentas modificarlo desde el thread "Y", no puedes modificar un control así como así desde otro thread, primero debes comprobar si el control necesita ser invocado, y después, invocarlo.


Woww.. en serio eres un capo en esto. Muchas gracias por tu ayuda. Era exactamente lo que queria. Prometo que seré mas cuidadosa con mis publicaciones y poner la información completa.

Gracias por todo.  :D

Eleкtro

#3
De nada, para eso estamos, de todas formas esto que te expliqué es quizás lo más básico de la programación asíncrona en .Net.

Se me olvidó comentar algo importante para que no te pierdas en el largo código que te mostré (en caso de que te hayas perdido un poco xD), todo ese código es solo una mejora del código que tú publicaste... por si lo quieres usar de esa manera (suscribiéndote a los eventos de la instancia del proceso), pero lo importante del código es lo siguiente, donde llamo a la propiedad .Invokerequired y el método .Invoke desde el subproceso/thread secundario:

Cita de: Eleкtro
Código (vbnet) [Seleccionar]
...
       Select Case txtResults.InvokeRequired

          Case True
              txtResults.Invoke(Sub() txtResults.AppendText("" & e.Data))
              txtResults.Invoke(Sub() txtResults.AppendText(Environment.NewLine))

          Case Else
              txtResults.AppendText(e.Data)
              txtResults.AppendText(Environment.NewLine)

      End Select
...


Control.InvokeRequired Property - MSDN
Control.Invoke(Lambda) Method - MSDN

Saludos








rochro

Cita de: Eleкtro en 11 Noviembre 2014, 19:41 PM
De nada, para eso estamos, de todas formas esto que te expliqué es quizás lo más básico de la programación asíncrona en .Net.


Tengo un inconveniente y quisiera que me ayudes. En el código que me entregaste sólo aumenté el -t al comando ping pero al crear un linklabel para que vacie los campos, el comando ping se sigue ejecutando. Como lo puedo parar?

Código (vbnet) [Seleccionar]
...
Private ReadOnly Property PingArguments As String
        Get
            Return String.Format("ping.exe ""{0}.{1}.{2}.{3}"" -t",
                                TextBox1.Text, TextBox2.Text,
                                TextBox3.Text, TextBox4.Text)
        End Get
    End Property
...

Private Sub LinkLabel1_LinkClicked(sender As Object, e As LinkLabelLinkClickedEventArgs) Handles LinkLabel1.LinkClicked
        Dim x As Control
        For Each x In Me.Controls
            If TypeOf x Is System.Windows.Forms.TextBox Then x.Text = ""
        Next

    End Sub


Eleкtro

#5
Cita de: rochro en 12 Noviembre 2014, 17:56 PMComo lo puedo parar?

Depende.

1) ¿Al clickar en el linklabel es cuando quieres detener Ping?

Debes detener la ejecución del thread que está corriendo la cmd, si estás usando la Class Thread entonces puedes llamar al método Thread.Abort, si estás usando la class Tasks entonces puedes llamar al método Task.Cancel usando un token de cancelación para esa Task.

O también puedes hacerlo desde la winAPi, con la función TerminateThread: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686717%28v=vs.85%29.aspx

2) ¿Estás usando la misma Task del código que te mostré?;
Muestra el resto del código relevante, es decir, la declaración del thread y el método del thread (donde ejecutas la cmd)

EDITO: De todas formas hace tiempo escribí un ejemplo de uso de la class Tasks para cancelar una Task, te podría servir hasta nueva respuesta:

Código (vbnet) [Seleccionar]
#Region " TASK Example "

Public Class Form1

   ' NORMAL TASK USAGE:
   ' ------------------
   Private Task1 As Threading.Tasks.Task
   Private Task1CTS As New Threading.CancellationTokenSource
   Private Task1CT As Threading.CancellationToken = Task1CTS.Token

   Private Sub MyTask1(ByVal CancellationToken As Threading.CancellationToken)

       For x As Integer = 0 To 9999

           If Not CancellationToken.IsCancellationRequested Then
               Debug.Print("Task1: " & x)
           Else
               MsgBox(String.Format("Task1 Canceled at ""{0}""", x))
               Exit Sub
           End If

       Next x

   End Sub

   ' ANONYMOUS TASK METHOD:
   ' ---------------------
   Private Task2 As Threading.Tasks.Task
   Private Task2CTS As New Threading.CancellationTokenSource
   Private Task2CT As Threading.CancellationToken = Task2CTS.Token

   Private Delegate Function Task2Delegate(ByVal CancellationToken As Threading.CancellationToken)

   Private MyTask2 As Task2Delegate =
     Function(CancellationToken As Threading.CancellationToken) As Boolean

         For x As Integer = 0 To 9999

             If Not CancellationToken.IsCancellationRequested Then
                 Debug.Print("Task2: " & x)
             Else
                 MsgBox(String.Format("Task2 Canceled at ""{0}""", x))
                 Return False
             End If

         Next x

         Return True

     End Function

   Private Sub TaskTest() Handles MyBase.Shown

       ' Run an asynchronous Task.
       Task1 = Threading.Tasks.Task.Factory.StartNew(Sub() MyTask1(Task1CT), Task1CT)

       ' Wait 2 seconds (Just to demonstrate this example)
       Threading.Thread.Sleep(2 * 1000)

       ' Cancel the Task.
       Task1CTS.Cancel()

       ' Wait for the Task to finish the being cancelled.
       Task1.Wait()

       ' Show the task status
       MsgBox(Task1.Status.ToString) ' Result: RanToCompletion

       ' ReStart the Task1.
       Task1 = Threading.Tasks.Task.Factory.StartNew(Sub() MyTask1(Task1CT), Task1CT)

       ' Start the Task2
       Task2 = Threading.Tasks.Task.Factory.StartNew(Of Boolean)(Function() MyTask2(Task2CT), Task2CT)

       ' Wait for both Tasks to finish their execution.
       Threading.Tasks.Task.WaitAll()

   End Sub

End Class

#End Region


Saludos








rochro

#6
Cita de: Eleкtro en 12 Noviembre 2014, 18:24 PM
Depende.

1) ¿Al clickar en el linklabel es cuando quieres detener Ping?


Si. Quiero que al hacer clic en el linklabel se cancele el task. Trato de entender el código pero ya me confundí :(

Disculpa por tanta molestia elektro.

Eleкtro

#7
No es ninguna molestía, ayudar es mi hobbie favorito (y más cuando se trata de VB.Net) :)

He añadido el método StartTask, CancelTask, y el linklabel al código que mostré arriba, aquí esta todo lo necesario:

Código (vbnet) [Seleccionar]
Imports System.Threading
Imports System.Threading.Tasks

Public Class Form1

   Private cmdTask As Task
   Private cmdTaskCTS As New CancellationTokenSource
   Private cmdTaskCT As CancellationToken

   ''' <summary>
   ''' The CMD <see cref="System.Diagnostics.Process"/> instance.
   ''' </summary>
   Private WithEvents cmdProcess As New Process With
       {
           .EnableRaisingEvents = True,
           .StartInfo = New ProcessStartInfo With
                            {
                               .FileName = "cmd.exe",
                               .Arguments = String.Empty,
                               .RedirectStandardInput = False,
                               .RedirectStandardOutput = True,
                               .RedirectStandardError = True,
                               .UseShellExecute = False,
                               .CreateNoWindow = True
                            }
       }

   ''' <summary>
   ''' Gets the ping commandline arguments.
   ''' </summary>
   Private ReadOnly Property PingArguments As String
       Get
           Return String.Format("ping.exe -t ""{0}.{1}.{2}.{3}""",
                               TextBox1.Text, TextBox2.Text,
                               TextBox3.Text, TextBox4.Text)
       End Get
   End Property

   Private Sub btnSend_Click(ByVal sender As Object, ByVal e As EventArgs) _
   Handles btnsend.Click

       Me.StartTask()

   End Sub

   Private Sub CMDAutomate()

       With Me.cmdProcess

           .StartInfo.Arguments = String.Format("/C ""{0}""", Me.PingArguments)
           .Start()
           .BeginOutputReadLine()
           .BeginErrorReadLine()
           .WaitForExit(Integer.MaxValue)

       End With

   End Sub

   ''' <summary>
   ''' Occurs when an application writes to its redirected <see cref="System.Diagnostics.Process.StandardOutput"/> stream.
   ''' Occurs when an application writes to its redirected <see cref="System.Diagnostics.Process.StandardError"/>  stream.
   ''' </summary>
   Private Sub cmdProcess_OutputDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs) _
   Handles cmdProcess.OutputDataReceived,
           cmdProcess.ErrorDataReceived

       Select Case txtresults.InvokeRequired

           Case True
               txtresults.Invoke(Sub() txtresults.AppendText("" & e.Data))
               txtresults.Invoke(Sub() txtresults.AppendText(Environment.NewLine))

           Case Else
               txtresults.AppendText(e.Data)
               txtresults.AppendText(Environment.NewLine)

       End Select

#If DEBUG Then
       ' Debug.WriteLine(e.Data)
#End If

   End Sub

   ''' <summary>
   ''' Occurs when a <see cref="System.Diagnostics.Process"/> exits.
   ''' </summary>
   Private Sub cmdProcess_Exited(ByVal sender As Object, ByVal e As EventArgs) _
   Handles cmdProcess.Exited

       Debug.WriteLine(String.Format("cmdProcess has exited with exit code: {0}",
                                     DirectCast(sender, Process).ExitCode))

   End Sub

   Private Sub LinkLabel1_LinkClicked(sender As Object, e As LinkLabelLinkClickedEventArgs) _
   Handles LinkLabel1.LinkClicked

       ' limpio el texto de cada textbox.
       For Each tb As TextBox In Me.Controls.OfType(Of TextBox)()
           tb.Clear()
       Next tb

       ' cancelo la tarea en segundo plano.
       Me.CancelTask()

   End Sub

   Private Sub StartTask()

       Me.cmdTask = Task.Factory.StartNew(AddressOf CMDAutomate)
       Me.cmdTaskCTS = New Threading.CancellationTokenSource
       Me.cmdTaskCT = cmdTaskCTS.Token

   End Sub

   Private Sub CancelTask()

       ' Si el proceso no se ha detenido...
       If Not Me.cmdProcess.HasExited Then

           With Me.cmdProcess
               ' cancelo la lectura de los outputs.
               .CancelOutputRead()
               .CancelErrorRead()
               .Kill() ' mato el proceso (cmd.exe)
           End With

           ' Cancelo la tarea en segundo plano.
           Me.cmdTaskCTS.Cancel()
           ' Espero a que la tarea se haya cancelado.
           Me.cmdTask.Wait()

       End If

   End Sub

End Class


EDITO: Fíjate que también cambié esto en el método CMDAutomate, para que funcionase del modo esperado:
Citar
Código (vbnet) [Seleccionar]
.WaitForExit(Integer.MaxValue)

Saludos.








rochro

Cita de: Eleкtro en 13 Noviembre 2014, 16:54 PM
No es ninguna molestía, ayudar es mi hobbie favorito (y más cuando se trata de VB.Net) :)

Gracias Elektro. Como que ya voy entendiendo un poco este tema de los tasks. Ahora no se como hacer, ya que como se va avanzando con esta pequeña app se me ocurre si hay alguna manera de que grabe los datos del textbox y por mas que se cierre y vuelva a abrir aparece con esos valores hasta que se cambie.

Gracias por tu atención :)

Eleкtro

#9
Cita de: rochro en 18 Noviembre 2014, 17:57 PMse me ocurre si hay alguna manera de que grabe los datos del textbox y por mas que se cierre y vuelva a abrir aparece con esos valores hasta que se cambie.

Hay muchas maneras de hacer eso, lo que más sencillo te resultará es utilizar la infrastructura de My.Settings.

Puedes crear la propiedad desde las opciones del proyecto, y luego acceder a dicha propiedad en tiempo de ejecución para guardar/cargar valores.

Es muy sencillo, pero lee un poco acerca de ello para enterarte:

Managing Application Settings
Using My.Settings in Visual Basic 2005
How to: Change User Settings in Visual Basic

Saludos