"Después del juego es antes del juego"
Sepp Herberger

viernes, 5 de diciembre de 2014

Montaje de un servidor WPKG

Para mantener actualizado el software de los Windows utilizo una herramienta llamada wpkg. Podría utilizar puppet como hacemos en los Linux, pero IMHO:

  1. Está muy verde.
  2. Se basa en ruby y mis Windows no están para esos trotes.

Wpkg permite instalar/actualizar/desinstalar programas y mantener algunas configuraciones aunque sea de forma rudimentaria. El esquema de funcionamiento en que se basa en wpkg es el siguiente:

  • Tenemos un servicio llamado WPKG Service que se ejecuta en cada arranque del ordenador.
  • Tenemos una carpeta compartida desde un Linux por samba, donde está el script principal wpkg.js (que es un completísimo Windows Script que hace todo el trabajo duro), la configuración con las órdenes a aplicar en un conjunto de ficheros .xml y los ejecutables y scripts de instalación y configuración que queremos aplicar a nuestros Windows.
  • El servicio ejecuta el script wpkg.js, que interpreta los .xml y aplica lo que en ellos dice sobre el PC donde se ejecuta.

Vamos a ver todos los pasos necesarios para configurarlo:

Paso 1. Entorno básico de servidor para wpkg.

Lo primero, es  crear una carpeta compartida samba en un servidor Linux donde estará todo para que puedan leerlo los Windows desde el servicio WPKG. Los pasos para configurarlo son:

1) Crear un usuario llamado "administrador" en el servidor Linux.
2) Instalar, sino lo está, el servicio Samba en el servidor Linux.
3) Crear un usuario llamado "administrador" en el servidor Samba, en coincidencia con el usuario "administrador" de Windows, usando:

smbpasswd -a administrador

4) Crear una carpeta donde alojar todo, en mi caso: /datos/trastero/wpkg y hacer "administrador" propietario de la misma.
5) Editar smb.conf y añadir:

[trastero]
path = /datos/trastero
writable = no
guest ok = no
valid users = administrador
public = no
browseable = no

El nombre del recurso es a gusto del consumidor, yo he elegido "trastero".

6) Reiniciar el servicio Samba

Paso 2. Ficheros de configuración de wpkg.

1) Descargamos http://wpkg.org/files/stable/1.3.x/wpkg-1.3.0-bin.zip y lo descomprimimos en /datos/trastero/wpkg
2) Se crea un árbol de directorios y una serie de ficheros que tendremos que retocar, con una estructura similar a esta:

├── config.xml
├── documents
│   ├── Changelog
│   ├── CHANGES
│   ├── CONTRIBUTORS
│   ├── GPL-2
│   ├── INSTALL
│   ├── LICENSE
│   ├── README
│   └── USAGE
├── files
│   ├── firefox
│   │   ├── 7za.exe
│   │   ├── config-firefox.txt
│   │   ├── CopyFiles.vbs
│   │   ├── Firefox Setup 29.0.1.exe
│   │   ├── mozilla.cfg
│   │   └── policies.js
│   ├── geogebra
│   │   ├── GeoGebra-Windows-Installer-3-2-47-0.exe
│   │   └── installer.properties
├── hosts
├── hosts.xml
├── packages
│   ├── firefox.xml
│   └── geogebra.xml
├── packages.xml
├── profiles
├── profiles.xml
├── tools
│   ├── 3rd-party
│   │   └── install-status-report
│   │       ├── README.txt
│   │       └── report.pl
│   ├── 64-bit-wrapper
│   │   └── wrapper.js
│   ├── execute-elevated
│   │   ├── execute-elevated.js
│   │   └── readme.txt
│   └── README.TXT
├── wpa.dbl
├── wpkg.js
└── xsd
├── hosts.xsd
├── packages.xsd
└── profiles.xsd

Nota: originalmente, el contenido de las carpetas packages y files está vacio. Eso lo añadimos nosotros luego.

Veamos los ficheros mas importantes que debemos configurar:

  • hosts.xml: este fichero asocia los nombres de PC a distintos perfiles. Por ejemplo:
<?xml version="1.0" encoding="UTF-8"?>
<wpkg>
<host name="SPRO-O[0-9]+" profile-id="default">
      <profile id="salaprofesores"/>
</host>
<host name=".+" profile-id="default">
</host>
</wpkg>

En el ejemplo anterior se asigna a los equipos que empiezen por SPRO-O y siga con números los perfiles default y salaprofesores.

  • profiles.xml: este fichero asocia un perfil a una serie de paquetes, por ejemplo el mio es:
<?xml version="1.0" encoding="UTF-8"?>
<profiles>
<profile id="default">
  <variable name="SOFTWARE" value="\\tercero\trastero\wpkg\files"/>
  <package package-id="firefox" />
  <package package-id="setup-firefox" />
  <package package-id="enlaces-ies" />
  <package package-id="Basicos" />
  <package package-id="foxitreader" />
  <package package-id="controlies" />
</profile>
<profile id="salaprofesores">
  <variable name="SOFTWARE" value="\\tercero\trastero\wpkg\files"/>
  <package package-id="geogebra" />
</profile>
</profiles>

El perfil default define la variable SOFTWARE con el valor "\\tercero\trastero\wpkg\files" y aplica los siguientes paquetes: firefox, setup-firefox, enlaces-ies, Basicos, foxitreader y controlies. "tercero" es el nombre del servidor Linux donde tengo la carpeta compartida y "trastero" es el nombre del recurso compartido Samba que la referencia. "wpkg" y "files" son ya carpetas dentro de dicho recurso, como vimos anteriormente.

El perfil salaprofesores define la variable SOFTWARE con el valor "\\tercero\trastero\wpkg\files" y aplica el paquete geogebra.

  • packages.xml: este es el que determina los distintos paquetes de software y como se instalan. Como este fichero puede crecer bastante es mas sencillo dejarlo como está y usar el directorio /datos/trastero/wpkg/packages, con distintos XML donde vamos a meter los paquetes. Al final WPKG coge todos esos XML y los procesa como uno solo, dando el mismo resultado que si todo estuviese en packages.xml, pero al estar fraccionado resulta mucho mas legible. Por ejemplo, en \\tercero\trastero\wpkg\packages\firefox.xml tenemos:
<?xml version="1.0" encoding="UTF-8"?>
<packages>
   <package
    id="firefox"
    name="Mozilla Firefox"
    revision="%version%"
    reboot="false"
    execute="once"
    priority="10">

    <variable name="version" value="29.0.1" />
    <variable name="architecture" value="x86" />
    <variable name="locale" value="es-ES" />
    <variable architecture="x86" name="PROGFILES" value="%PROGRAMFILES%" />
    <variable architecture="x64" name="PROGFILES" value="%PROGRAMFILES(X86)%" />

    <check type="uninstall" condition="exists" path="Mozilla Firefox %version% (%architecture% %locale%)" />

    <install cmd="taskkill /F /IM Firefox.exe">
        <exit code="0" />
        <exit code="-1073741515" />
        <exit code="128" />
    </install>

    <install cmd='"%SOFTWARE%\firefox\Firefox Setup %version%.exe" -ms' />

    <upgrade cmd="taskkill /F /IM Firefox.exe">
        <exit code="0" />
        <exit code="128" />
        <exit code="-1073741515" />
    </upgrade>

    <upgrade cmd='"%SOFTWARE%\firefox\Firefox Setup %version%.exe" -ms' />

    <remove cmd="taskkill /F /IM Firefox.exe">
        <exit code="0" />
        <exit code="128" />
        <exit code="-1073741515" />
    </remove>

    <remove cmd='%COMSPEC% /C if exist "%PROGFILES%\Mozilla Firefox\uninstall\helper.exe" "%PROGFILES%\Mozilla Firefox\uninstall\helper.exe" -ms' />

   </package>

   <package
        id="setup-firefox"
        name="Configuracion por defecto en firefox"
        revision="1"
        priority="10"
        execute="once"
        reboot="false">

        <install cmd='cscript "%SOFTWARE%\firefox\CopyFiles.vbs" "%SOFTWARE%\firefox\config-firefox.txt"' />
        <upgrade include='install' />

  </package>

</packages>

Antes de proseguir, dos aclaraciones:

  • En la pagina de wpkg hay una base de datos enormes sobre la sintaxis a usar para instalar multitud de aplicaciones y algunas configuraciones de Windows, en la ruta de este enlace.
  • Como vemos, la sintaxis de un package es bastante mas rica que la de hosts.xml y profiles.xml. Se escapa a esta entrada explicar todos los detalles, pero básicamente consiste en una serie de condiciones y una serie de acciones a ejecutar si se cumplen dichas condiciones. Todas las posibilidades vienen completamente detalladas en este enlace. De todas maneras, el 90% de las veces cogeremos paquete ya hechos por terceros y simplemente crearemos el XML del package a partir de ellos.

Dentro de files debemos poner los ficheros referenciados por packages. Por ejemplo:

<install cmd='"%SOFTWARE%\firefox\Firefox Setup %version%.exe" -ms' />

Se expande a:

<install cmd='"\\tercero\trastero\wpkg\files\firefox\Firefox Setup 29.0.1.exe" -ms' />

Lo que quiere decir: para instalar el paquete Firefox 29.0.1 ejecuta el comando:

           "\\tercero\trastero\wpkg\files\firefox\Firefox Setup 29.0.1.exe" -ms

El parámetro -ms significa "instalación silenciosa", es decir, una instalación no interactiva que coge las opciones por defecto y no pregunta nada al usuario.

Paso 3. Configuración de clientes Windows.

Por ultimo, hay que instalar el cilente wpkg en los Windows y configurarlos para que se conecten al servidor samba, miren que paquetes tienen que aplicarse, actualizarse o desinstalarse sobre ellos y realicen las acciones correspondientes.

El cliente Windows XP de wpkg está en este enlace .Se instala como un servicio de Windows y una vez instalado hay que configurarlo para que tome los packages de la carpeta compartida del servidor, las claves de acceso, y varias cosas mas. La configuracion en cada puesto se hace ejecutando C:\Archivos de programa\wpkg\wpkginst.exe, que nos permite configurar todo a mano en un puesto y luego podemos exportar los parámetros a un XML para ser importado en los restantes puestos sin necesidad de rellenar de nuevo todos los campos. Veamos la configuración pantalla a pantalla:

Indicamos:

  • Ruta del fichero wpkg.js
  • Parámetros de ejecución de wpkg.js, lo dejamos tal cual está.
  • Usuario y contraseña para acceder a la carpeta compartida.
  • Usuario y contraseña que ejecuta las tareas wpkg en el pc local. Lo dejo como SYSTEM, que es el usuario mas poderoso en un entorno Windows.

Indicamos:

  • Variable de entorno que luego usamos en packages.xml con la ubicación de los ficheros necesarios para la aplicación de los mismos.
  • Existe la posibilidad de ejecutar un programa o script antes o después de wpkg.js. En mi caso antes ejecuto el script compname.bat, cuyo contenido es:
@echo off
compname /d ?d >  c:\windows\temp\nombredns.txt
compname /d  >  c:\windows\temp\nombrewin.txt
set /p nombrewin=<"c:\windows\temp\nombrewin.txt"
set /p nombredns=<"c:\windows\temp\nombredns.txt"
if %nombrewin% == %nombredns% goto fin
compname /c ?d
:fin

Que usa la utilidad compname.exe. Ambos ficheros (tanto el .bat como el .exe) los he  copiado previamente al c:\windows\system32 de cada Windows. Lo que hacen es comparar el nombre que tiene el host Windows y el nombre que le corresponde según el DNS. Si no coinciden, cambia el nombre del Windows para que se corresponda con el fijado en DNS. De esta manera consigo renombrar los Windows automáticamente simplemente con darlos de alta en el servidor DNS de la red al inicio del servicio Wpkg.

La dejo como está.

La dejo como está.

Indico el nombre del servidor Wpkg, para que antes de empezar compruebe si está operativo mediante un ping y en caso contrario no ejecute wpkg.js.

La dejo como está.

Una vez definido todo procedemos a:

  • Guardarlo con save.
  • Test: comprobar que están bien los parámetros y se accede a las carpetas Samba sin problema.
  • Export settings: genera un XML con toda la información de configuración que luego podremos importar en otros equipos con Import Settings, para evitar teclear la misma configuración en todos los equipos.

Y ya está, con el siguiente reinicio se ejecutará el servicio WPKG, el cual ejecutará el script wpkg.js, que mirará en \\tercero\trastero\wpkg\hosts.xml, determinando a partir del nombre del host que profiles de profiles.xml se aplican en dicho host, y desde esos profiles la lista de packages a ejecutar.

Cada package tiene 3 partes:

  • install: se ejecuta la primera vez que el package aparece en ese host. Su finalidad suele ser instalar un programa o aplicar una configuración.
  • upgrade: se ejecuta cuando cambia el atributo revision="XX" del package para ese host. Su finalidad suele ser actualizar un programa.
  • remove: se ejecuta cuando el package desaparece de ese host. Su finalidad suele ser desinstalar un programa.

Paso 4. Depuración.

Como todo se ejecuta como un servicio, cuando hay problemas (y es muy normal que los haya hasta que todo esté engrasado) es díficil depurarlo. En ese caso lo primero es mirar el Visor de Sucesos de las Herramientas Administrativas de Windows, ya que allí el servicio puede dejar mensajes de error de por qué no se ejecuta.

También es útil tener un script para re-ejecutar el servicio sin tener que reiniciar el pc:

restart-wpkg.bat:

@echo off
echo "Reiniciando servicio wpkg..."
net stop  wpkgservice
net start wpkgservice
echo "Hecho."

O,mejor aún, ejecutar directamente wpkg.js en una ventana de comandos y ver toda la monstruosa salida que genera, entre cuyas líneas estarán los mensajes de error:

cscript \\tercero\trastero\wpkg\wpkg.js /synchronize /debug

Luego, en el equipo local hay 2 ficheros de log que también pueden resultarnos útiles:

  • c:\windows\system32\wpkg.xml : es un XML con todos los paquetes aplicados en ese host en la última ejecución de servicio. Si lo borramos la próxima vez se aplicarán de nuevo todas las reglas desde cero. Este fichero sirve además para que wpkg.js sepa que parte de los packages debe aplicar: install, upgrade o remove, ya que le sirve de base para comparar los paquetes de la última ejecución con los paquetes determinados por la ejecución actual.
  • c:\windows\temp\wpkg-<host>.txt : log de la ejecución del servicio WPKG.

Paso 5. Addenda.

En el package Firefox mostrado antes hay un par de ficheros hechos por mi que son muy útiles en diversas tareas de configuración: el script CopyFiles.vbs y config-firefox.txt.

<install cmd='cscript "%SOFTWARE%\firefox\CopyFiles.vbs" "%SOFTWARE%\firefox\config-firefox.txt"' />

El primero es un script que utilizo mucho con wpkg para copiar ficheros en ubicaciones concretas del sistema Windows, en ese caso concreto para copiar una configuración por defecto de Firefox. Adicionalmente necesitamos que el programa 7za.exe esté en el mismo directorio. Su código es:

CopyFiles.vbs:

Const ForReading = 1

rem Tenemos de entrada un fichero de texto, con rutas de ficheros, una en cada linea.
rem Ejemplo: c:\WINDOWS\system32\basicos.zip
rem          C:\prueba\aqui\scripts.zip,c:\prueba
rem Y lo que hacemos es buscar en el mismod directorio donde está el fichero entrada el fichero basicos.zip.
rem Comprobamos que ese basicos.zip es igual en tamaño a c:\windows\system32\basicos.zip
rem Si son iguales, se pasa al siguiente
rem Si no son iguales se copia basicos.zip a c:\WINDOWS\system32\basicos.zip y se descomprime sobre c:\WINDOWS\system32\
rem Si el fichero no es zip, no se descomprime, solo se copia
rem Si la linea tiene dos rutas, separadas por coma, la segunda se usa como destino para descomprimir.

file = WScript.Arguments.Item(0)

Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.OpenTextFile(file, ForReading)

rem Obtenemos el directorio donde está el fichero de texto que usamos como indice
pathsource=objFSO.GetParentFolderName(objFSO.GetAbsolutePathName(file))+"\"

i = 0
Do Until objFile.AtEndOfStream
   lineprocess = objFile.ReadLine
   aprocess=split(lineprocess,",")
   fileprocess=aprocess(0)
   pos=InStrRev(fileprocess,"\")
   pathdst=Left(fileprocess,pos)
   filenamedst=mid(fileprocess,pos+1)

   rem Si hay un segundo valor en la linea, tras la coma, se toma como directorio destino para el contenido del zip
   if ubound(aprocess) > 0 then
        dstuncompress=aprocess(1)
   else
        dstuncompress=pathdst
   end if

   rem Vemos si el fichero existe en el destino, y su tamaño (0 si no existe).
   if objFSO.FileExists(fileprocess) then
      set objFileDst = objFSO.GetFile(fileprocess)
      sizedst=objFileDst.size
   else
      sizedst=0
   end if   

   rem Ahora cogemos el origen y vemos su tamaño
   set objFileSrc=objFso.GetFile(pathsource+filenamedst)
   sizesrc=objFileSrc.size

   rem Si no existe el path de destino, lo creamos. Es recursivo por si hay varias carpetas anidadadas.
   If Not objFso.FolderExists(pathdst) Then
      BuildFullPath pathdst
   End If

   rem Si el fichero ha cambiado de tamaño, lo copia de nuevo al destino.
   rem eso se hacia antes, ahora siempre lo copiamos, no valoramos si ha cambiado de tamaño.
   rem if sizedst <> sizesrc then       

   'Sobreescribe el fichero
   objFileSrc.copy fileprocess,true
   'Si el fichero es ZIP, lo descomprime
   if ucase(objFso.GetExtensionName(objFileSrc))="ZIP" then
       retorno=Unzip (fileprocess, dstuncompress)
       if retorno <> "OK"  then
           rem sale devolviendo un exitcode <> 0
           WScript.Quit 1
       end if
   end if        

Loop
objFile.Close

Sub BuildFullPath(ByVal FullPath)

   If Not objFso.FolderExists(FullPath) Then
          BuildFullPath objFso.GetParentFolderName(FullPath)
          objFso.CreateFolder FullPath
   End If

End Sub

Function Unzip(ByVal sArchiveName, ByVal sLocation)

 'This script is provided under the Creative Commons license located
  'at http://creativecommons.org/licenses/by-nc/2.5/ . It may not
  'be used for commercial purposes with out the expressed written consent
  'of NateRice.com

  Set oFSO = WScript.CreateObject("Scripting.FileSystemObject")
  Set oShell = WScript.CreateObject("Wscript.Shell")

  '--------Find Working Directory--------
  aScriptFilename = Split(Wscript.ScriptFullName, "\")
  sScriptFilename = aScriptFileName(Ubound(aScriptFilename))
  sWorkingDirectory = Replace(Wscript.ScriptFullName, sScriptFilename, "")

  '--------------------------------------

  '-------Ensure we can find 7za.exe------
  If oFSO.FileExists(sWorkingDirectory & "\" & "7za.exe") Then
    s7zLocation = sWorkingDirectory
  ElseIf oFSO.FileExists("C:\Archivos de Programa\7-Zip\7z.exe") Then
    s7zLocation = "C:\Archivos de Programa\7-Zip\"
  Else
    UnZip = "Error: Couldn't find 7z.exe"
    Exit Function
  End If
  '--------------------------------------
  '-Ensure we can find archive to uncompress-
  If Not oFSO.FileExists(sArchiveName) Then
    UnZip = "Error: File Not Found."
    Exit Function
  End If
  '--------------------------------------

  exitCode=oShell.Run ("""" & s7zLocation & "7za.exe"" x -y -o""" & sLocation & """ """ & sArchiveName & """", 0, True)

  if exitCode = 0 then
    salida="OK"
  else
    salida="ERROR"
  end if
  UnZip = salida

End Function

El segundo es el fichero de texto simple que sirve de entrada al script anterior para que sepa que ficheros copiar y en que ruta dejarlos. Su contenido es:

config-firefox.txt:

c:\Archivos de Programa\Mozilla Firefox\mozilla.cfg
c:\Archivos de Programa\Mozilla Firefox\defaults\pref\policies.js

Como se puede ver, los ficheros mozilla.cfg y policies.js están en originalmente en /datos/trastero/wpkg/files/firefox. El script CopyFiles.vbs coge el nombre de fichero (es decir, lo que hay después del último \) y lo busca en el directorio donde está config-firefox.txt, una vez encontrado lo copia a la ruta indicada por la línea concreta . Adicionalmente, si el fichero es un .zip lo descomprime en esa ruta, extrayendo todo su contenido allí.

Por último, mi WPKG está configurado para que al acabar envíe datos a controlies, de tal manera que tengo centralizado en una aplicación web (que se usa además para muchísimas otras cosas) el resultado de todas las últimas ejecuciones de WPKG. Para ello hace falta colocar un wpkg.js "tuneado" e instalar un paquete llamado controlies.zip en cada host que queramos tener monitorizado. Ambos están en este enlace.

Otra posibilidad para centralizar la información si no tenemos montado controlies en nuestra red es usar WPKGExpress.

Paso 6. Epílogo.

Bueno, pues con esto tenemos montado nuestro sistema de gestión de software y configuraciones para los Windows de nuestra red. Unos comentarios finales:

  • Esto no es un camino de rosas. Las instalaciones automáticas en Windows son sumamente problemáticas, casi siempre por motivos estrambóticos, y nos encontraremos que hay paquetes que dan problemas en unos equipos si y en otros no. El esfuerzo merece la pena porque una vez lo tengamos funcionando sabremos que todos los equipos tienen la misma versión del software y la homogeneidad de software en una organización es fundamental.
  • Es una pena que la mayoría de las configuraciones de Windows no puedan hacerse mediante línea de comandos y estén pensadas para hacerse a golpe de ratón o mediante políticas aplicadas desde un controlador de dominio. Eso limita mucho lo que podemos configurar, pero muchas veces hay alguna argucia que debemos explorar y poner en producción mediante copiado de ficheros a una ruta, retoques del registro importando o modificando claves o la ejecución de scripts vbs, js, bat, powershell o kix.

Y eso es todo, amigos. Hasta la próxima entrada....

No hay comentarios:

Publicar un comentario