Herramientas de actualización remotas a través de NT con VFP

 

 

 

 

 

          

 Por José Joaquín de Haro

http://www.fpress.com/revista/Num9912/d9912.zip

 

 

Descripción

 

            La idea consiste en actualizar el software de los usuarios de una LAN con uno o varios servidores NT desde un puesto de administrador.

 

 

El fichero de comandos en el NT

 

            Se incluye en la login script de todos los usuarios de los servidores NT un fichero de comandos (inicio.bat en mi ejemplo) situado en c:\winnt\system32\Repl\Import\Scripts\  que realiza los siguientes pasos :

 

1.- Chequea si existe un fichero(vacío) que se utiliza como semáforo para habilitar/inhabilitar la actualización en ese servidor NT (ej.- para realizar labores de single user en los datos inhabilitamos la actualización), si no existe sale del fichero de comandos .

 

2.- Chequea si existe el fichero C:\WINDOWS\SYSTEM\VFP6RUN.EXE (componente del runtime de VFP) y si no es así, copia el runtime de VFP completo (VFPRUN.EXE, VFP6R.DLL y VFPRESN.DLL) en la carpeta C:\WINDOWS\SYSTEM

 

3.- Ejecuta el programa VFP ejecutable (en el ejemplo update.exe) pasándole un fichero config.fpw que contiene SCREEN=OFF para que no se visualice la pantalla principal de VFP.


 


[inicio.bat]

@ECHO OFF

IF NOT EXIST \\<SERVERNT>\<PATHSEMAFORO>\SERVERNT.SI GOTO FINAL

IF EXIST C:\WINDOWS\SYSTEM\VFP6RUN.EXE GOTO EJECUTA

@ECHO ON

ECHO ACTUALIZANDO SU SISTEMA...

COPY \\<SERVERNT>\<PATHRUNTIMEVFP>\*.*  C:\WINDOWS\SYSTEM

@ECHO OFF

:EJECUTA

\\<SERVERNT>\<PATHEJECUTABLE>\UPDATE.EXE  ‑C \\PALACIO\DATOS\UPDATE\CONFIG.FPW

:FINAL

 

 

El programa que se ejecuta en el cliente

 

            El programa de actualización debe obtener el usuario con el que se ha iniciado la sesión en el dominio NT :

 

FUNCTION get_user

PUBLIC lpUserIDBuffer, ;

       nBufferSize, ;

       RetVal

RetVal         = 0

lpUserIDBuffer = SPACE(25)

** Return buffer for user ID string

nBufferSize    = 25      

** Size of user ID return buffer

 

DECLARE INTEGER GetUserName IN Win32API:

 AS GetName ;

        STRING  @lpUserIDBuffer, ;

        INTEGER @nBufferSize

RetVal=GetName(@lpUserIDBuffer,;

    @nBufferSize)

RETURN LEFT(lpUserIDBuffer,nBufferSize‑1)

ENDFUNC

 

            Con el usuario obtenido, consultamos en una tabla previamente cargada con otro programa que comentaré más tarde (el que usará el administrador para preparar las actualizaciones), y si existe alguna actualización pendiente para ese usuario,  ponemos visible la aplicación  _SCREEN.visible=.T. y sacamos un formulario por pantalla que pregunta si queremos actualizar nuestro software y si es así actuamos en consecuencia ejecutando la aplicación indicada.

 

SET EXCLUSIVE OFF

SET DEFAULT TO "\\<SERVERNT>\<PATHPROGRAMA>"

SET SYSMENU TO

SET DELETED ON

LOCAL lcuser, lcprograma, lnresult, llsalir

lcuser=''

lcprograma=''

lnresult=0

llsalir=.F.

lcuser=get_user() && Obtengo el login del usuario en el dominio

USE programas IN 0 SHARED

SELECT programas

** Consulto si queda alguna actualización ** pendiente

SCAN FOR UPPER(ALLTRIM(lcuser))==UPPER(ALLTRIM(;

programas.cuser)) AND ;

programas.lactualiza AND !llsalir

       lcprograma=ALLTRIM(programas.cprograma)

** Si queremos que pregunte al usuario

       IF programas.lpreguntar

              WITH _SCREEN

                     .PICTURE='dipu.bmp'

                     .CAPTION='Actualización de su;

 software'

                     .CLOSABLE=.F.

                     .VISIBLE=.T.

                     .WINDOWSTATE=2

              ENDWITH      

              DO FORM update1 TO lnresult

              REPLACE programas.df_act WITH DATETIME()

              IF lnresult = 3

                     REPLACE programas.lactualiza;

 WITH .F.

                     REPLACE programas.lnoquiere;

 WITH .T.

              ENDIF

              IF lnresult = 1

                     REPLACE programas.lactualiza;

 WITH .F.

       IF programas.lreiniciar

              RUN /N &lcprograma && Ejecuto asíncronamente el programa

              llsalir=.T.

       ELSE

              RUN &lcprograma

       ENDIF

ENDIF

ELSE

       REPLACE programas.df_act WITH DATETIME()

              REPLACE programas.lactualiza WITH .F.

              IF programas.lreiniciar

                     RUN /N &lcprograma

                     llsalir=.T.

              ELSE

                     RUN &lcprograma

              ENDIF

       ENDIF

ENDSCAN

USE IN programas

 


 La tabla contiene los campos :

 


cuser : login del usuario a actualizar.

Cprograma: camino completo del programa a ejecutar.

lactualiza : Indica si la actualización está pendiente o no.

cdesc : Instrucciones para el usuario.

lreiniciar : Indica si la actualización se va a realizar en modo asíncrono respecto a la login script.

lnoquiere : Indica si el usuario respondió que no quería actualizarse.

lpreguntar : Indica si visualizar o no el formulario que pregunta al usuario si desea o no actualizarse

df_act :Fecha y hora en la que el usuario a sido actualizado

df_cre : Fecha y hora en la que se creó la actualización por parte del administrador

 

 

El programa de administración

 

           

El programa de administración es un formulario que usa el control ADSI v2.5 para acceder a los servicios de directorio de NT y así obtener los usuarios del mismo; para los que indicaré que programa quiero que ejecuten al iniciar una sesión en el servidor, así como si deseo que se les pregunte si quieren actualizarse o no y si la ejecución del programa será síncrona o asíncrona con el  login script.

 

            Incluimos los usuarios en el listbox de la derecha y rellenamos el campo del programa  con el camino completo del programa a actualizar, y el campo descripción con una descripción de lo que vamos a hacer; pulsamos procesar y automáticamente se darán de alta los registros correspondientes en la tabla programas.dbf.

 

            Para obtener los usuarios del NT con el control ADSI(v2.5) y meterlos en un control Treeview hacemos :

 

[treeview1.enumobjects] Extrae los objetos (usuarios, grupos, servicios,...) y los coloca en el control treeview a partir del nodo que recibe como parámetro, el código está extraído de la Knowledge Base de Microsoft y adaptado al ejemplo .

 

 

LPARAMETERS NODE

 

*‑‑ Enable the Stop button

ThisForm.cmdStop.Enabled = .T.

 

liCounter = 0 && local child object counter for DOEVENTS

 

*‑‑ Initialize objects

oADSobj = NULL

oCurrentADSobj = NULL

 

oADSobj = GETOBJECT(ALLTRIM(THISFORM.;

Text1.VALUE))

oCurrentADSobj = GETOBJECT(gsPath)

 

IF THISFORM.Treeview1.Nodes.COUNT = 0 

** Treeview is not initialized

       oContainer = oADSobj

      

       *‑‑ Top‑level object

       NODE = THISFORM.Treeview1.Nodes.ADD(,,;

      oADSobj.ADSPath, oADSobj.NAME,2 )

       NODE.TAG = oADSobj.ADSPath+;

        "/"+oADSobj.NAME

       NODE.SORTED = .T.

       Node.Expanded = .T.

 

       FOR EACH CHILD IN oContainer

 

              *‑‑ This could take a while, so give the user a chance to abort

              liCounter = liCounter + 1

              IF liCounter = 50 && Check for every 50 objects

                     liCounter = 0

                     DOEVENTS

                     IF glStop

                            ThisForm.cmdStop.Enabled = .F.

                           glStop = .F.

                           RETURN

                     ENDIF

              ENDIF

 

              IF llAll OR (lsUsers $ CHILD.CLASS ;

                     OR lsComputers $ CHILD.CLASS ;

                     OR lsPrinters $ CHILD.CLASS ;

                     OR lsGroups $ CHILD.CLASS ;

                     OR lsServices $ CHILD.CLASS)

 

             NodeChild = THISFORM.Treeview1.Nodes.ADD(NODE, 4,;

                 , CHILD.CLASS + ": " + CHILD.NAME,1 )

 

               *‑‑ Store the paths and object name so that we can GETOBJECT()

               *-- based on     

               *-- information

                     *‑‑ in the node.

                      NodeChild.TAG = CHILD.ADSPath

                      NodeChild.SORTED = .T.

 

                     *‑‑ Set up an empty node in case the object is a container

                     *‑‑ Check to see if object is a container

                     *‑‑ If it is a container, add a placeholder node

                     IF Child.Class <> "Schema" && Can't get the schema for a schema

                           oSchema = GETOBJECT(CHILD.Schema)

                     ELSE

                           oSchema = .F.

                     ENDIF

                     IF VARTYPE(oSchema) = "O"

                          IF oSchema.CONTAINER

                             ChildNode = THISFORM.Treeview1.Nodes.ADD(NodeChild, 4, ,;

                                 "Empty",2)

                           ENDIF

                     ENDIF  && VARTYPE(oSchema) = "O"

              ENDIF  && NOT llAll

       ENDFOR  && EACH Child IN oContainer

       THISFORM.Treeview1.Nodes(1).Expanded = .T.

ELSE

       NODE.SORTED = .T.

 

       *‑‑ Check to make sure we even have an object.

       *‑‑ If the network path is not found, oCurrentADSobj will be null.

              IF VARTYPE(oCurrentADSobj) <> "O"

              RETURN

       ENDIF

      

       *‑‑ Check to see if object is a container

       IF oCurrentADSobj.Class <> "Schema" && Can't get the schema for a schema

              oSchema = GETOBJECT(oCurrentADSobj.Schema)

       ELSE

              oSchema = .F.

       ENDIF

      

       IF VARTYPE(oSchema) = "O"

              IF oSchema.CONTAINER

                     FOR EACH CHILD IN oCurrentADSobj

 

              *‑‑ This could take a while, so give the user a chance to abort

                           liCounter = liCounter + 1

                           IF liCounter = 50

      ** Check for every 50 objects

                                  liCounter = 0

                                  DOEVENTS

                           IF glStop

                           ThisForm.cmdStop.Enabled = .F.

                                  glStop = .F.

                                         RETURN

                                  ENDIF

                           ENDIF 

                            NewNode = THISFORM.Treeview1.;

                              Nodes.ADD(NODE, 4, , CHILD.;

                              CLASS + ": " + CHILD.NAME,1)

                            NewNode.TAG = CHILD.ADSPath

                            NewNode.SORTED = .T.

 

*‑‑ Set up an empty node in case the object *-- is a container

*‑‑ Check to see if object is a container

*‑‑ If it is a container, add a placeholder *-- node

IF Child.Class <> "Schema" && Can't get the schema for a schema

       oChildSchema = GETOBJECT(CHILD.Schema)

ELSE

       oChildSchema = .F.

ENDIF

IF VARTYPE(oChildSchema) = "O"

IF oChildSchema.CONTAINER

NewChildNode = THISFORM.Treeview1.;

Nodes.ADD(NewNode, 4, , "Empty",1)

       ENDIF

ENDIF  && VARTYPE(oChildSchema) = "O"

ENDFOR  && EACH Child in oCurrentADSobj

ENDIF  && oSchema.Container

ENDIF  && VARTYPE(oSchema) = "O"

ENDIF  && ThisForm.Treeview1.Nodes.;

Count = 0

ThisForm.cmdStop.Enabled = .F.

 

            Esta herramienta esta siendo usada en Diputación de Albacete(España) con 5 servidores NT y más de 100 usuarios y funciona perfectamente, y es altamente flexible ya que el programa a ejecutar puede ser cualquiera, incluso otro ejecutable hecho con VFP, VB, etc.

 

José Joaquín de Haro Navarro

Sección Sistemas

Servicio de Informática

Diputación de Albacete