Sockets
Programa para ejecutar programas en m�quinas remotas mediante
'sockets'
Fecha: 02/Oct/98 (15/Dic/97)
Autor: Nacho Cassou [email protected]
Hola Guille,
No te dejo en paz eh?
Te env�o una colaboraci�n que, adem�s de �til te puede servir
para quedarte con alg�n compa�ero de trabajo.
Se trata de lo siguiente : - Un programa (SocketListen) que se
ejecuta en 'background' (o sea, oculto) y que 'escucha' por
sockets, y que es capaz de ejecutar el programa que se le ordene
desde otra m�quina remota, ya sea en modo s�ncrono o
as�ncrono.
Por cierto, para conseguir lo de 's�ncrono' intent� utilizar tu
c�digo pero no me funcion�, as� que busqu� yo mismo en el CD
del MSDN y encontr� el c�digo, muy similar al tuyo pero
utilizando la API CreateProcess en vez de OpenProcess. Funciona
bien, con la salvedad de que si la aplicaci�n que quieres
ejecutar es el Explorador de Windows, la rutina se comporta como
si ya hubiese terminado. Se te ocurre por qu�?
- Un EXE ActiveX (SocketTalk) que es el que se encarga de
enviarle las �rdenes a SocketListen.
- Un programita de ejemplo de su utilizaci�n (UsaSocketTalk).
Con esto se puede conseguir ejecutar un programa en una m�quina
remota, lo �nico que hace falta es instalarle el programita
SocketListen y manipular el registro para que se cargue siempre
que arranque Windows (tambi�n se puede cargar incluy�ndolo en
el grupo 'Inicio', pero queda m�s feo).
Tambi�n puedes, como dec�a antes, hacer que en el PC de un
compa�ero se abra de repente el Notepad mientras est�
trabajando, con lo que le dejar�s 'pasmao'.
Funcionan bajo VB5, pero se pueden utilizar desde VB4 si se
generan los archivos de instalaci�n y se instala SocketTalk o
SocketListen en el PC donde se quieran emplear.
Te gusta la idea?
Salu2,
Nacho.
Aqu� tienes los listados de los diferentes m�dulos:
SocketListen: el programa que se ejecuta en "background", esperando que el socket le diga algo.
'M�dulo slMod1.bas
Private Sub Main()
'Damos el n�mero de puerto TCP que
'queremos usar. Se puede cambiar,
'pero hay que ver que no sea un puerto
'usado por otras aplicaciones (ver archivo
'Services en el directorio Windows), y
'si se cambia, hay que hacer lo propio con
'SocketTalk.
slForm.Winsock1.LocalPort = 1027
slForm.Winsock1.Listen
End Sub
'Formulario slForm.frm
'El form s�lo tiene un control WinSock
Private Type STARTUPINFO
cb As Long
lpReserved As String
lpDesktop As String
lpTitle As String
dwX As Long
dwY As Long
dwXSize As Long
dwYSize As Long
dwXCountChars As Long
dwYCountChars As Long
dwFillAttribute As Long
dwFlags As Long
wShowWindow As Integer
cbReserved2 As Integer
lpReserved2 As Long
hStdInput As Long
hStdOutput As Long
hStdError As Long
End Type
'Valores para dwFlags
Private Const STARTF_USESHOWWINDOW = &H1
Private Const STARTF_USESIZE = &H2
Private Const STARTF_USEPOSITION = &H4
Private Const STARTF_USECOUNTCHARS = &H8
Private Const STARTF_USEFILLATTRIBUTE = &H10
Private Const STARTF_RUNFULLSCREEN = &H20 ' se ignora para plataformas que no sean x86
Private Const STARTF_FORCEONFEEDBACK = &H40
Private Const STARTF_FORCEOFFFEEDBACK = &H80
Private Const STARTF_USESTDHANDLES = &H100
Private Type PROCESS_INFORMATION
hProcess As Long
hThread As Long
dwProcessID As Long
dwThreadID As Long
End Type
Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal _
hHandle As Long, ByVal dwMilliseconds As Long) As Long
Private Declare Function CreateProcessA Lib "kernel32" (ByVal _
lpApplicationName As Long, ByVal lpCommandLine As String, ByVal _
lpProcessAttributes As Long, ByVal lpThreadAttributes As Long, _
ByVal bInheritHandles As Long, ByVal dwCreationFlags As Long, _
ByVal lpEnvironment As Long, ByVal lpCurrentDirectory As Long, _
lpStartupInfo As STARTUPINFO, lpProcessInformation As _
PROCESS_INFORMATION) As Long
Private Declare Function CloseHandle Lib "kernel32" (ByVal _
hObject As Long) As Long
Private Const NORMAL_PRIORITY_CLASS = &H20&
Private Const INFINITE = -1&
Public objfrm As slForm
Public WithEvents sckobjl As Winsock
Public WithEvents sckobja As Winsock
Private Sub Winsock1_Close()
Winsock1.Close
Winsock1.Listen
End Sub
Private Sub Winsock1_ConnectionRequest(ByVal requestID As Long)
'Recibimos una petici�n de conexi�n
'y la aceptamos.
If Winsock1.State = sckConnected Or Winsock1.State = sckListening Then
Winsock1.Close
End If
Winsock1.Accept requestID
End Sub
Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
Dim datos As String
Dim swLanzado As Boolean
Dim ModoVentana As Integer
Dim Posicion As Integer
Dim LongTrozo As Integer
'Han llegado datos por el socket. Vamos a ver qu�
'encontramos.
datos = String(bytesTotal, Chr$(0))
'Recuperamos los datos recibidos.
Winsock1.GetData datos
'Aqu� interpretamos el montaje que
'hemos hecho en SocketTalk para
'saber qu� hemos de ejecutar y
'c�mo.
Posicion = InStr(4, datos, "#ModoVentana=")
If Posicion > 0 Then
LongTrozo = Len(Mid$(datos, Posicion))
Posicion = Posicion + 13
ModoVentana = Val(Mid$(datos, Posicion))
Else
ModoVentana = vbNormalFocus
End If
Select Case UCase(Mid$(datos, 1, 3))
Case "EA#"
swLanzado = ExecCmd(Mid$(datos, 4, Len(datos) - LongTrozo - 3), ModoVentana, False)
'Contestamos a SocketTalk y le decimos si ha
'ido bien o no.
'La respuesta he preferido darla como string
'en vez de como boolean porque en el entorno
'donde yo voy a emplear SocketTalk no tengo
'muy claro que reciba correctamente los booleanos.
If swLanzado Then
Winsock1.SendData "Ok"
Else
Winsock1.SendData "NoOk"
End If
Case "ES#"
swLanzado = ExecCmd(Mid$(datos, 4, Len(datos) - LongTrozo - 3), ModoVentana, True)
'Igual que en el caso as�ncrono, contestamos.
If swLanzado Then
Winsock1.SendData "Ok"
Else
Winsock1.SendData "NoOk"
End If
Case Else
'Si lo que nos han enviado no es ninguna
'orden de ejecuci�n (algo que empiece por
'EA# o ES#) devolvemos los datos recibidos
'tal cual.
Winsock1.SendData datos
End Select
End Sub
Private Function ExecCmd(cmdline As String, ModoVentana As Integer, swWait As Boolean) As Boolean
Dim proc As PROCESS_INFORMATION
Dim start As STARTUPINFO
Dim ret As Long
' Initialize the STARTUPINFO structure:
start.cb = Len(start)
' Le decimos que haga caso de lo que indique wShowWindow
start.dwFlags = STARTF_USESHOWWINDOW
' Le indicamos el modo en que se abrir� la
' nueva ventana.
start.wShowWindow = ModoVentana
'He de reconocer que esto lo he sacado de
'Microsoft, aunque he a�adido cosas de
'mi cosecha, pues en el ejemplo de MS no
'explicaba como especificar el modo de
'presentaci�n de la ventana.
' Start the shelled application:
ret = CreateProcessA(0&, cmdline, 0&, 0&, 1&, _
NORMAL_PRIORITY_CLASS, 0&, 0&, start, proc)
If ret = 0 Then
ExecCmd = False
Else
ExecCmd = True
If swWait Then
'Atenci�n, si la aplicaci�n que lanzamos es el
'Explorador de Windows no conseguiremos que
'WaitForSingeObject se espere, sino que actuar�
'como si la aplicaci�n hubiese terminado. No s�
'por qu�, pero si alguien lo averigua me gustar�
'que me lo diga.
'S� funciona correctamente con la mayor�a de
'programas, y tambi�n con los programas
'hechos en VB.
' Wait for the shelled application to finish:
ret = WaitForSingleObject(proc.hProcess, INFINITE)
ret = CloseHandle(proc.hProcess)
End If
End If
End Function
SocketTalk: el programa que le envia las ordenes a SocketListen.
'M�dulo stMod1.bas
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Public sw_Host_Responded As Boolean
Public BytesReceived As Integer
Public Function Now_plus_nseconds(ByVal nseconds As Integer) As String
Dim final_time As String
final_time = DateAdd("s", nseconds, Now)
Now_plus_nseconds = Format$(final_time, "yyyy/mm/dd hh:MM:ss")
End Function
Public Function Wait_For_Host_Response(final_time As String) As Boolean
sw_Host_Responded = False
Do While sw_Host_Responded = False And Format$(Now, "yyyy/mm/dd hh:MM:ss)") <= final_time
DoEvents
Sleep 100
Loop
Wait_For_Host_Response = sw_Host_Responded
End Function
'Clase: stClass (stClass.cls) con la propiedad Instancing a 5-MultiUse
Public Property Get HostRemoto() As String
'Devolvemos informaci�n del host remoto
HostRemoto = stForm.Winsock1.RemoteHost
End Property
Public Property Let HostRemoto(ByVal vNewValue As String)
'Aceptamos informaci�n sobre el host remoto
If stForm.Winsock1.State = sckConnected Then
stForm.Winsock1.Close
End If
stForm.Winsock1.RemoteHost = vNewValue
stForm.Winsock1.RemotePort = 1027
End Property
Public Function Conectar() As String
Dim final_time As String
'Intentamos conectar con el host remoto
stForm.Winsock1.Connect
Do While stForm.Winsock1.State <> sckConnected And stForm.Winsock1.State <> sckError And stForm.Winsock1.State <> sckClosed
DoEvents
Loop
If stForm.Winsock1.State = sckConnected Then
Conectar = "Ok"
Else
Conectar = "NoOk"
End If
End Function
Public Sub Cerrar()
stForm.Winsock1.Close
End Sub
Public Function EjecutarRemoto(Datos As String, ModoVentana As Integer) As String
Dim final_time As String
Dim Respondio As Boolean
Dim respuesta As String
If stForm.Winsock1.State <> sckConnected Then
EjecutarRemoto = "No Conectado"
Else
'Aqu� hacemos un montaje para indicarle a SocketListen
'como queremos que ejecute el programa y
'le enviamos la informaci�n usando el socket.
stForm.Winsock1.SendData "EA#" & Datos & "#ModoVentana=" & Str$(ModoVentana)
'Esperamos un tiempo prudencial la respuesta
'de SocketListen, y si no hemos recibido nada,
'asumimos que no se ha enterado y pasamos de �l.
'Ver que la variable sw_Host_Responded (en el
'procedimiento Wait_For_Host_Response) se actualiza
'cuando se activa el evento de recepci�n
'de datos en el Socket.
final_time = Now_plus_nseconds(ByVal 10)
Respondio = Wait_For_Host_Response(final_time)
If Respondio Then
respuesta = String(BytesReceived, Chr$(0))
stForm.Winsock1.GetData respuesta
EjecutarRemoto = respuesta
Else
EjecutarRemoto = "NoOk"
End If
End If
End Function
Public Function EjecutarRemotoSincrono(Datos As String, ModoVentana As Integer) As String
Dim final_time As String
Dim Respondio As Boolean
Dim respuesta As String
If stForm.Winsock1.State <> sckConnected Then
EjecutarRemotoSincrono = "No Conectado"
Else
sw_Host_Responded = False
'Aqu� hacemos un montaje para indicarle a SocketListen
'como queremos que ejecute el programa y
'le enviamos la informaci�n usando el socket.
stForm.Winsock1.SendData "ES#" & Datos & "#ModoVentana=" & Str$(ModoVentana)
'Esperamos indefinidamente la respuesta
'de SocketListen.
'Ver que la variable sw_Host_Responded
'se actualiza cuando se activa el evento de recepci�n
'de datos en el Socket.
Do While sw_Host_Responded = False And stForm.Winsock1.State = sckConnected
DoEvents
Sleep 100
Loop
If sw_Host_Responded Then
respuesta = String(BytesReceived, Chr$(0))
stForm.Winsock1.GetData respuesta
EjecutarRemotoSincrono = respuesta
Else
EjecutarRemotoSincrono = "NoOk"
End If
End If
End Function
Public Function EnviarString(Datos As String) As String
Dim final_time As String
Dim Respondio As Boolean
Dim respuesta As String
If stForm.Winsock1.State <> sckConnected Then
EnviarString = "No Conectado"
Else
'Simplemente enviamos el string tal cual
stForm.Winsock1.SendData Datos
'Esperamos un tiempo prudencial la respuesta
'de SocketListen, y si no hemos recibido nada,
'asumimos que no se ha enterado y pasamos de �l.
'Ver que la variable sw_Host_Responded (en el
'procedimiento Wait_For_Host_Response) se actualiza
'cuando se activa el evento de recepci�n
'de datos en el Socket.
final_time = Now_plus_nseconds(ByVal 10)
Respondio = Wait_For_Host_Response(final_time)
If Respondio Then
respuesta = String(BytesReceived, Chr$(0))
stForm.Winsock1.GetData respuesta
EnviarString = respuesta
Else
EnviarString = "NoOk"
End If
End If
End Function
Private Sub Class_Terminate()
stForm.Winsock1.Close
'He puesto el END porque me ha ocurrido
'que alguna vez, despu�s de acabar la
'ejecuci�n del programa principal,
'el proceso SocketTalk continuaba activo.
End
End Sub
'Formulario: stForm.frm (s�lo tiene un control WinSock)
Private Sub Winsock1_Close()
'MsgBox "Socket listen desconectado, vuelvo a escuchar"
Winsock1.Close
Winsock1.Listen
End Sub
Private Sub Winsock1_ConnectionRequest(ByVal requestID As Long)
'MsgBox "Connection Requested"
If Winsock1.State = sckConnected Or Winsock1.State = sckListening Then
Winsock1.Close
End If
Winsock1.Accept requestID
End Sub
Private Sub Winsock1_DataArrival(ByVal bytesTotal As Long)
BytesReceived = bytesTotal
sw_Host_Responded = True
End Sub
UsaSocketTalk:
un programa de prueba.
Este programa tiene una referencia a: SocketTalk.exe
Formulario:
UsaSocketTalk.frm
Contiene un TextBox para la orden a ejecutar, otro para mostrar
los resultados y tres botones para las distintas opciones que
pone a disposici�n del "ejecutador", es decir:
Ejecutar, Ejecutar S�ncrono y Enviar String.
'En Proyecto->Referencias, seleccionar 'Enviar �rdenes por sockets'
'Si se quiere hacer debug, hay que arrancar el proyecto
'SocketTalk.vbp y ejecutarlo, entonces, deberemos seleccionar
'como referencia la l�nea 'Enviar �rdenes por sockets'
'pero en este caso la que corresponda a
'SocketTalk.vbp.
'Otra cosa obvia es que hay que tener activado
'el protocolo TCP/IP.
'Por otro lado, hay que ejecutar el programa
'SocketListen.exe (queda oculto, pero si
'presionamos ctrl-alt-del lo veremos en la
'lista de procesos) o bien cargar el proyecto
'y ejecutarlo.
Private sockobj As SocketTalk.stClass
Private Conectado As String
Private Sub Command1_Click()
Text2.Text = ""
If Conectado = "Ok" Then
'Ejecuci�n as�ncrona, es decir, no esperamos
'a que el proceso lanzado acabe.
'El primer par�metro es la l�nea
'de comando a ejecutar
'(ej. "notepad.exe c:\autoexec.bat).
'El segundo par�metro es el modo en que
'queremos que se visualice la nueva ventana
'(ver lista de posibilidades en la ayuda
'de la funci�n shell de VB).
Text2.Text = sockobj.EjecutarRemoto(Text1.Text, vbMinimizedNoFocus)
Else
Text2.Text = "NoConectado"
End If
End Sub
Private Sub Command2_Click()
Text2.Text = ""
If Conectado = "Ok" Then
'Ejecuci�n s�ncrona, es decir, esperamos
'a que el proceso lanzado acabe.
'Para los respectivos par�metros se
'aplica lo mismo que para la as�ncrona.
Text2.Text = sockobj.EjecutarRemotoSincrono(Text1.Text, vbNormalNoFocus)
Else
Text2.Text = "NoConectado"
End If
End Sub
Private Sub Command3_Click()
Text2.Text = sockobj.EnviarString(Text1.Text)
End Sub
Private Sub Form_Load()
Set sockobj = New SocketTalk.stClass
'Le damos el nombre de la m�quina en la que se
'est� ejecutando SocketListen.
'Para poderlo probar todo en una misma m�quina
'le damos como host remoto nuestra propia m�quina
'(localhost).
sockobj.HostRemoto = "localhost"
Conectado = sockobj.Conectar
End Sub
Private Sub Form_Unload(Cancel As Integer)
sockobj.Cerrar
Set sockobj = Nothing
End Sub
Bajate los ficheros con el c�digo de ejemplo (SocketsNC.zip 26.8 KB con tres ficheros zip con cada uno de los programas usados.)