lunes, 21 de julio de 2014

VBscript: ejecutar comandos limitando su duración máxima

Cuando utilizamos VBscript para ejecutar comandos de MS-DOS estos se ejecutan en un proceso aparte diferente del proceso del script original. Si el comando ejecutado no termina correctamente es muy probable que su proceso permanezca abierto indefinidamente.

En el caso de scripts de monitorización, que se ejecutan a intervalos regulares, es necesario controlar estos procesos secundarios para así evitar que multiples procesos secundarios no terminen y queden abiertos en el sistema. Este control podemos hacerlo desde el propio script.

El código siguiente es un ejemplo de como realizarlo para el caso de un script que testea una conexión a una base de datos. El comando que se ejecuta es "sqlplus.exe" y queda parado sin terminar porque la linea "exit" del fichero que ejecuta esta comentada. De esta forma simulamos un problema que tendrá que resolver el script:

'-----------------------------------------------------------------------
' Constantes 
'-----------------------------------------------------------------------
Const ORA_SID="XE"
Const ORA_PORT="1521"
Const operuser="sys"
Const operpwd="manager"
'-----------------------------------------------------------------------

On Error Resume Next

IdProceso= ScriptProcessID()

Set objFSO = CreateObject("Scripting.FileSystemObject")
Set WshShell = CreateObject("WScript.Shell")

scriptPath=WshShell.CurrentDirectory

PathFileConexSQL=scriptPath & "\conexion.sql"
PathFileConexLog=scriptPath & "\conexion.log"

If objFSO.FileExists(PathFileConexLog) Then objFSO.DeleteFile(PathFileConexLog)
If objFSO.FileExists(PathFileConexSQL) Then objFSO.DeleteFile(PathFileConexSQL)

Set objPathFileConexSQL = objFSO.CreateTextFile(PathFileConexSQL)
objPathFileConexSQL.WriteLine("spool " & PathFileConexLog & ";")
objPathFileConexSQL.WriteLine("column host_name format A20;")
objPathFileConexSQL.WriteLine("select host_name, instance_name, status from v$instance;")
objPathFileConexSQL.WriteLine("spool off;")
'objPathFileConexSQL.WriteLine("exit;")
objPathFileConexSQL.Close

comando="sqlplus -L """ & operuser & "/" & operpwd & "@" & ORA_SID & " as sysdba"" @" & PathFileConexSQL
WshShell.Run(comando),2,false

wscript.sleep 10000

Set colProcessList=Nothing
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
Set colProcessList = objWMIService.ExecQuery("SELECT * FROM Win32_Process WHERE Name = 'sqlplus.exe' And ParentProcessId = " & IdProceso)

If colProcessList.count>0 Then
  For Each objProcess in colProcessList
    MsgBox("Terminando proceso bloqueado ...")
    objProcess.Terminate()
  Next
End If

'If objFSO.FileExists(PathFileConexLog) Then objFSO.DeleteFile(PathFileConexLog)
'If objFSO.FileExists(PathFileConexSQL) Then objFSO.DeleteFile(PathFileConexSQL)

WScript.Quit

'-----------------------------------------------------------------------
' Función obtener el processID de este script
'-----------------------------------------------------------------------
Function ScriptProcessID()

  Set objSWbemServices = GetObject ("WinMgmts:Root\Cimv2")
  Set colProcess = objSWbemServices.ExecQuery("Select * From Win32_Process Where Name='wscript.exe' Or Name='cscript.exe'")

  For Each objProcess In colProcess
    if instr(objProcess.CommandLine,WScript.ScriptFullName)<>0 Then
      ScriptProcessID=objProcess.ProcessID
    End If
  Next

End Function
'-----------------------------------------------------------------------

Lo primero es identificar el ID del proceso de sistema del propio script; esto se hace en la linea:

IdProceso= ScriptProcessID()

La función ScriptProcessID utiliza el ScriptFullName y una consulta WMI para encontrar dicho ID.

Generamos el fichero PathFileConexSQL con la consulta SQL que lanzaremos a la base de datos una vez conectados; dejamos la sentencia "exit" comentada para simular un proceso que no termina:

spool C:\Oracle\conexion.log;
column host_name format A20;
select host_name, instance_name, status from v$instance;
spool off;

Después generamos el comando que vamos a ejecutar, un sqlplus con su cadena de conexión generada a partir de las variables definidas al inicio del script y que una vez conectado a la base de datos ejecutara el fichero anterior:

comando="sqlplus -L """ & operuser & "/" & operpwd & "@" & ORA_SID & " as sysdba"" @" & PathFileConexSQL

Lanzamos el comando y damos un tiempo de espera (10 segundos):

WshShell.Run(comando),2,false
wscript.sleep 10000

Es el momento de confirmar que nuestro comando ha terminado. Lo hacemos mediante una consulta WMI que utiliza el nombre del comando ejecutado, sqlplus.exe, y el ID del proceso padre que lo lanzo, que es el ID de nuestro script obtenido al principio:

Set colProcessList=Nothing
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
Set colProcessList = objWMIService.ExecQuery("SELECT * FROM Win32_Process WHERE Name = 'sqlplus.exe' And ParentProcessId = " & IdProceso)

En caso de que la consulta obtenga algún resultado tenemos que matar dicho proceso, en este ejemplo hemos añadido un mensaje que en un entorno real no tendría sentido incluir:

If colProcessList.count>0 Then
  For Each objProcess in colProcessList
    MsgBox("Terminando proceso bloqueado ...")
    objProcess.Terminate()
  Next
End If

La ejecución del script da este resultado:














Si quitamos el comentario a la linea que hacia el "exit" en la conexión a la base de datos el script funciona perfectamente sin ningún problema:

objPathFileConexSQL.WriteLine("exit;")

Un saludo

No hay comentarios:

Publicar un comentario