Servicios Windows instalables con NAnt

8 enero, 2010 por Ana Buigues Dejar una respuesta »

Para los que estamos acostumbrados a utilizar herramientas como Ant para realizar releases, ejecutar los test etc…  en nuestros proyectos Java, cuando programamos en .NET también podemos seguir la misma filosofia, para ello tenemos NAnt y NAntContrib que es una extensión. Tanto Ant y NAnt son una herramienta de código abierto para automatizar procesos de construcción de software.

Si tienes conocimientos de Ant, te darás cuenta de que hay muchas cosas que son parecidas en NAnt, por lo que no te costará mucho hacerte con la dinámica; si no conoces NAnt tampoco es muy difícil, la documentación está bastante bien. NAnt se basa en archivos de configuración escritos en XML, con extensión .build y están compuestos por cuatro tipos de ítems: Tasks, Targets, Properties y Projects.

La tarea que vamos a automatizar es la creación de un instalador (.msi), que además, en el proceso de instalación del msi, instalará un servicio de windows en el sistema. La ventaja de automatizar este tipo de tarea es que nos ahorramos tener que cambiar el nombre del servicio de windows cada vez, modificar de la versión del instalador y demás…

NAntContrib nos permite, entre otras cosas, generar instaladores mediante el task msi, en si la difucultad no está en realizar el msi, sino en programar las acciones para que cuando ejecutemos el instalador, instale además el servicio windows.

Primero compilamos la solución donde tenemos el código del servicio que queremos incluir en instalador, para ello hacemos uso del task solution de NAnt:

[codesyntax lang="xml" title="Compilación de la solución"]

<target name="compile">
  <solution configuration="${config}" outputdir="${release.dir}">
    <projects>
     <include name="${src.dir}/Prj1/*.vbproj" />
    </projects>
  </solution>
</target>

[/codesyntax]

Ahora pasamos a generar el msi con el task de NAntContrib. Las partes de <summaryinformation>, <properties>, <directories> y <features> las configuramos según nuestras necesitades.

Para poder instalar el servicio necesitamos incluir en el instalador 3 binarios en la parte que hace referencia a <binaries>, los podemos encontrar haciendo una búsqueda en nuestro equipo.

[codesyntax lang="xml" title="Definición de los binarios"]

<binaries>
   <binary name="InstallUtil" value="${src.dir}/InstallUtilLib.dll" />
   <binary name="MSVBDPCADLL" value="${src.dir}/dpca.dll" />
   <binary name="VSDNETCFG" value="${src.dir}/dpapp.config" />
</binaries>

[/codesyntax]

Cuando añadimos los <components>, tenemos que tener en cuenta que si tenemos archivos que no son .dll, tenemos que hacer uso del forceid, para asignar una GUID a cada fichero, sino al generar el msi les asigna una de forma aleatoria y después no hay forma de hacer referencia a ellos. Para generar las GUID podemos hacer uso del task script para mediante código generar GUID’s nuevas cada vez. Decir que estos GUID’s no tienen guiones de separación, por lo que tendremos que quitar los guiones a los GUID’s generados.

[codesyntax lang="xml" title="Generar las GUID's"]

<script language="C#" prefix="guid-serviceID" >
  <code>
    <![CDATA[
      [Function("guid-func")]
        public static string getGUID(  ) {
	       System.Guid  guid = System.Guid.NewGuid ();
	       return "_"+guid.ToString().Replace("-","").ToUpper();
	    }
      ]]>
  </code>
</script>

[/codesyntax]

[codesyntax lang="xml" container="pre_valid" title="Definición de los componentes"]

<components>
  <component name="C_${ServiceID}" id="{0BA47093-CC41-412E-ABAD-EA43CCDB44FF}" directory="TARGETDIR" attr="0" feature="DefaultFeature" keepsubdirs="true">
    <key file="Service.exe"/>
    <fileset basedir="${release.dir}">
      <include name="Service.exe" />
      <include name="BinaryData.dll" />
    </fileset>
    <forceid file="Service.exe" id="${ServiceID}" />
  </component>
  <component name="C_${File1TxtID}" id="{AE3203AE-2F3C-4d6d-8A14-1175596C9782}" directory="TARGETDIR" attr="0" feature="DefaultFeature" keepsubdirs="true">
    <key file="File1.txt"/>
    <fileset basedir="${release.dir}">
      <include name="File1.txt" />
    </fileset>
    <forceid file="File1.txt" id="${File1TxtID}" />
  </component>
</components>

[/codesyntax]

Ahora definimos las <customactions> para el servicio, necesitamos programar las acciones de install, uninstall, commit y rollback además las inicializaciones de los binarios que hemos incluido anteriormente. Lo difícil de esta parte es identificar el tipo de la acción, que viene determinado por el parámetro type de la acción.

[codesyntax lang="xml" container="pre_valid" title="Definición de las customactions"]

<customactions>
  <customaction action="${InstallService}.install" type="1025" source="InstallUtil" target="ManagedInstall" />
  <customaction action="${InstallService}.install.SetProperty" type="51" source="${InstallService}.install" target="/installtype=notransaction /action=install /LogFile= "[#${ServiceID}]" "[VSDFxConfigFile]"" />
  <customaction action="${CommitService}.commit" type="1537" source="InstallUtil" target="ManagedInstall" />
  <customaction action="${CommitService}.commit.SetProperty" type="51" source="${CommitService}.commit" target="/installtype=notransaction /action=commit /LogFile= "[#${ServiceID}]" "[VSDFxConfigFile]"" />
  <customaction action="${RollbackService}.rollback" type="1281" source="InstallUtil" target="ManagedInstall" />
  <customaction action="${RollbackService}.rollback.SetProperty" type="51" source="${RollbackService}.rollback" target="/installtype=notransaction /action=rollback /LogFile= "[#${ServiceID}]" "[VSDFxConfigFile]"" />
  <customaction action="${UnistallService}.uninstall" type="1025" source="InstallUtil" target="ManagedInstall" />
  <customaction action="${UnistallService}.uninstall.SetProperty" type="51" source="${UnistallService}.uninstall" target="/installtype=notransaction /action=uninstall /LogFile= "[#${ServiceID}]" "[VSDFxConfigFile]"" />
  <customaction action="DIRCA_TARGETDIR" type="307" source="TARGETDIR" target="[ProgramFilesFolder][Manufacturer]\[ProductName]" />
  <customaction action="DIRCA_CheckFX" type="1" source="MSVBDPCADLL" target="CheckFX" />
  <customaction action="ERRCA_UIANDADVERTISED" type="19" source="" target="[VSDUIANDADVERTISED]" />
</customactions>

[/codesyntax]

Y tenemos añadir las siguientes entradas en <sequences> que define la secuencia de ejecución, aquí lo importante es poner en el orden correcto las secuencias, el orden viene definido por el parámetro value de la secuencia.

[codesyntax lang="xml" container="pre_valid" title="Definición de las secuencias"]

<sequences>
  <sequence type="installexecute" action="${InstallService}.install" condition="$C_${ServiceID}>2" value="5999" />
  <sequence type="installexecute" action="${InstallService}.install.SetProperty" condition="$C_${ServiceID}>2" value="5998" />
  <sequence type="installexecute" action="${CommitService}.commit" condition="$C_${ServiceID}>2" value="5995" />
  <sequence type="installexecute" action="${CommitService}.commit.SetProperty" condition="$C_${ServiceID}>2" value="5994" />
  <sequence type="installexecute" action="${RollbackService}.rollback" condition="$C_${ServiceID}>2" value="5997" />
  <sequence type="installexecute" action="${RollbackService}.rollback.SetProperty" condition="$C_${ServiceID}>2" value="5996" />
  <sequence type="installexecute" action="${UnistallService}.uninstall" condition="$C_${ServiceID}=2" value="1699" />
  <sequence type="installexecute" action="${UnistallService}.uninstall.SetProperty" condition="$C_${ServiceID}=2" value="1698" />
  <sequence type="installexecute" action="DIRCA_TARGETDIR" condition='TARGETDIR=""' value="750" />
  <sequence type="installexecute" action="DIRCA_CheckFX" value="1" />
  <sequence type="adminexecute" action="DIRCA_TARGETDIR" condition='TARGETDIR=""' value="750" />
  <sequence type="adminui" action="DIRCA_TARGETDIR" condition='TARGETDIR=""' value="750" />
  <sequence type="installui" action="DIRCA_TARGETDIR" condition='TARGETDIR=""' value="750" />
  <sequence type="installui" action="DIRCA_CheckFX" value="1" />
  <sequence type="installui" action="ERRCA_UIANDADVERTISED" condition="ProductState=1" value="1" />
</sequences>

[/codesyntax]

Pues ya tenemos todo lo necesario para generar nuestro instalador con el servicio de windows desde NAnt. Podeís descargar el ejemplo completo aquí.

Podemos utilizar Orca para editar los archivos del instalador, también nos muestra toda la estructura del instalador, es muy útil en caso de que no tengamos algo bien configurado en la creación del msi y nos de errores al instalar.

Bueno, esta ha sido mi parra mental de hoy xDD. Espero que le sirva de ayuda a alguién.

11 comentarios

  1. Juan dice:

    Hola, me parece muy bien la información.

    Ahora, como podría realizar una prueba de integridad de módulos¿?

  2. Ana Buigues dice:

    Hola Juan, a qué te refieres con la prueba de integridad de módulos? a cómo incluirlo en tu proyecto?

    Un saludo.

  3. Juan dice:

    Claro, Sucede estoi recién aprendiendo C# y me encargaron de realizar pruebas integrales con herramientas automatizadas y he leído al respecto, pero no entiendo mucho sobre ello.

    Lo que debo hacer es realizar pruebas comenzando por unir módulos.(Testearlos).

    Eso es lo que trae problemas, el no saber hacerlo y con la lectura no llego a ningún lado.

    Saludos…

  4. Ana Buigues dice:

    Hola Juan, para realizar pruebas puedes utilizar NUnit, y luego utilizar NAnt para ejecutar los test de forma automática.

    Un saludo.

  5. Juan dice:

    Hola Ana,

    Tengo entendido que Nunit es para pruebas Unitarias, y también que Nant es para pruebas integrales.

    La situación que me complica, es que buscando por la red, he encontrado manera de hacerlos, pero es complicado poder hacer una prueba integral por la gran configuración que se requiere…

  6. frdgt dice:

    Buena info Ana. He estado compilado soluciones usando el Task de NAnt. Cuando los proyectos en la solucion no tienen ninguna referencia a CrystalReport todo marcha bien, pero al agregar el menor cotrol de crystal en cualquier form de cualquier proyecto este ya no compila, he tratado compiar las dll a los respectivos /bin/debug pero sigo con el mismo problema. Que crees que podra ser, he tratado tambien compilarlo con el task pero me da el mismo herror. El error es: [solution] ….\Form1.Designer.vb(48,0): Error BC30002: El tipo ‘CrystalDecisions.Windows.Forms.CrystalReportViewer’ no está definido. Cualquier ayuda sera agradecida.

  7. Hi there, I discovered your web site by means of Google even as looking for a related topic, your site came up, it seems to be great. I have bookmarked to my favourites|added to my bookmarks.

Trackbacks /
Pingbacks

  1. Trucos Semilla Minecraft
  2. via
  3. clay peck

Deja un comentario