El Blog de Ana Buigues » .NET http://anabuigues.com Wed, 07 Dec 2011 16:07:47 +0000 es-ES hourly 1 http://wordpress.org/?v=3.4 Servicios Windows instalables con NAnt http://anabuigues.com/2010/01/08/servicios-windows-instalables-con-nant/ http://anabuigues.com/2010/01/08/servicios-windows-instalables-con-nant/#comments Fri, 08 Jan 2010 22:21:48 +0000 Ana Buigues http://anabuigues.com/?p=41 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.

]]>
http://anabuigues.com/2010/01/08/servicios-windows-instalables-con-nant/feed/ 11