NSIS

En varias ocasiones he mencionado el instalador NSIS (Nullsoft Scriptable Install System), y aunque ya hace bastante tiempo de ello, no había tenido la oportunidad de dedicarle un artículo completo.

Sin embargo a raíz del nuevo instalador de FileOptimizer y algunos proyectos profesionales, he vuelto a trabajar con él.


NSIS

NSIS es un instalador basado en scripts, con un lenguaje de programación que mezcla primitivas de muy alto nivel, con otras de muy bajo nivel. De modo que algunas operaciones son realmente sencillas, y otras bastante tediosas.

Sus orígenes se remontan a 1998 cuando Justin Frankel, lo desarrolló como herramienta de instalación para su Winamp. Mucho antes de ser comprado por AOL, y por supuesto de que dejara de existir.

Es una herramienta de código abierto para Windows, que genera unos instaladores muy compactos, gracias a lo ligero de sun runtime, del orden de 50 Kb., y lo eficaz de la compresión LZMA, aunque si se quiere puede ser Deflate.

Su desarrollo ha ido bastante a trompicones, de manera que estuvimos anclados a la versión 2.46 desde 2009. Sin embargo en 2013, el proyecto se retomó, por lo que ahora tenemos ya la beta 0 de la versión 3, que después de 3 alfas, nos ofrece soporte unicode, y Windows 8.x. Aunque ya es capaz de generar instaladores compatibles Win64, se espera permita generar también instaladores nativos para dicha plataforma.

La filosofía de trabajo se basa en la linea de comandos (makensis), o bien en un IDE (makensisw), que no obstante, siempre requieren escribir el código desde un editor de textos externo. La sintaxis del lenguaje es parecida a C, incluso con su preprocesador.

Como decía antes, el trabajo con el contenido del instalador, es sencillo, con funciones para extraer a una carpeta, gestionar directorios, crear accesos directos, etc. Crear el desinstalador es un proceso que hace automáticamente el programa, nosotros solamente tendremos que indicarle los procesos a realizar. A partir de ahí, todo se complica. Desde crear la entrada correspondiente en Agregar y quitar programas del Panel de control, a detectar si estamos en un entorno x86 o x64. Como la mejor forma de explicarlo es con un ejemplo, os lo muestro a continuación la última versión del FileOptimizerSetup.nsi, el script usado en FileOptimizer.

; -------------------------------------------------------------------------------------------------
SetCompressor /SOLID /FINAL lzma 
SetCompressorDictSize 128
Unicode true



; -------------------------------------------------------------------------------------------------
!include "MUI.nsh"
!define MUI_ABORTWARNING
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
!include x64.nsh



; -------------------------------------------------------------------------------------------------
!define APP_NAME 	"FileOptimizer"
!define APP_DESCRIPTION	"Lossless file size optimizer"
!define APP_COMPANY	"Javier Gutiérrez Chamorro (Guti)"
!define APP_COPYRIGHT	"© Copyright 2012-2014 by Javier Gutiérrez Chamorro (Guti)"
!define APP_WEB		"https://nikkhokkho.sourceforge.io/static.php?page=FileOptimizer"
!define APP_VERSION 	"6.9.0.0"



; -------------------------------------------------------------------------------------------------
Icon "..\${APP_NAME}.ico"
;UninstallIcon "..\${APP_NAME}.ico"
OutFile "..\${APP_NAME}Setup.exe"
Name "${APP_NAME}"
InstallDir "$PROGRAMFILES\${APP_NAME}"
InstallDirRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "InstallDir"



; -------------------------------------------------------------------------------------------------
Function .onInit
        ${If} ${RunningX64}
        	${EnableX64FSRedirection}
		StrCpy '$INSTDIR' "$PROGRAMFILES64\${APP_NAME}"
        ${EndIf}
FunctionEnd



; -------------------------------------------------------------------------------------------------
Section "Files"
	CreateDirectory "$SMPROGRAMS\FileOptimizer"

	SetOutPath $INSTDIR
	File ..\Win32\Release\*.chm
	CreateDirectory "$SMPROGRAMS\${APP_NAME}";
	${If} ${RunningX64}
		File ..\Win32\Release\*64.exe
		SetOutPath $INSTDIR\Plugins64
		File /r ..\Win32\Release\Plugins64\*.*
		CreateShortCut "$SMPROGRAMS\${APP_NAME}\Launch ${APP_NAME}.lnk" "$INSTDIR\${APP_NAME}64.exe" "" "" "" SW_SHOWNORMAL "" "Launch ${APP_NAME}"
		CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${APP_NAME}64.exe" "" "" "" SW_SHOWNORMAL "CONTROL|ALT|x" "Launch ${APP_NAME}"
		CreateShortCut "$QUICKLAUNCH\${APP_NAME}.lnk" "$INSTDIR\${APP_NAME}64.exe" "" "" "" SW_SHOWNORMAL "CONTROL|ALT|x" "Launch ${APP_NAME}"
	${Else}
		File ..\Win32\Release\*32.exe
		SetOutPath $INSTDIR\Plugins32
		File /r ..\Win32\Release\Plugins32\*.*
		CreateShortCut "$SMPROGRAMS\${APP_NAME}\Launch ${APP_NAME}.lnk" "$INSTDIR\${APP_NAME}32.exe" "" "" "" SW_SHOWNORMAL "" "Launch ${APP_NAME} x86"
		CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${APP_NAME}32.exe" "" "" "" SW_SHOWNORMAL "CONTROL|ALT|x" "Launch ${APP_NAME} x86"
		CreateShortCut "$QUICKLAUNCH\${APP_NAME}.lnk" "$INSTDIR\${APP_NAME}32.exe" "" "" "" SW_SHOWNORMAL "CONTROL|ALT|x" "Launch ${APP_NAME} x86"
	${EndIf}

	WriteUninstaller "$INSTDIR\Uninstall.exe"

	CreateShortCut "$SMPROGRAMS\${APP_NAME}\Help.lnk" "$INSTDIR\${APP_NAME}.chm" "" "" "" SW_SHOWNORMAL "" "View ${APP_NAME} Help"
	CreateShortCut "$SMPROGRAMS\${APP_NAME}\Uninstall.lnk" "$INSTDIR\Uninstall.exe" "" "" "" SW_SHOWNORMAL "" "Uninstall ${APP_NAME}"
	CreateShortCut "$FAVORITES\${APP_NAME} Home Page.lnk" "${APP_WEB}" "" "" "" "" "" "Visit ${APP_NAME} Home Page"

	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "DisplayName" "${APP_NAME}"
	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "Publisher" "${APP_COMPANY}"
	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "DisplayVersion" "${APP_VERSION}"

	${If} ${RunningX64}
		WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "DisplayIcon" "$\"$INSTDIR\${APP_NAME}64.exe$\""
	${Else}
		WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "DisplayIcon" "$\"$INSTDIR\${APP_NAME}32.exe$\""
	${EndIf}

	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "ProductID" "${APP_NAME} ${APP_VERSION}"
	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "RegCompany" "${APP_COMPANY}"
	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "RegOwner" "${APP_COPYRIGHT}"
	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "URLInfoAbout" "${APP_WEB}"
	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "URLUpdateInfo" "${APP_WEB}"
	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "HelpLink" "${APP_WEB}"
	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "Comments" "${APP_DESCRIPTION}"
	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "UninstallString" "$\"$INSTDIR\Uninstall.exe$\""
	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "InstallDir" "($INSTDIR)"
	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "NoModify" "1"
	WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "NoRepair" "1"
 	!include "FileFunc.nsh"
	${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
	IntFmt $0 "0x%08X" $0
	WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" "EstimatedSize" "$0"
SectionEnd



; -------------------------------------------------------------------------------------------------
Section "Uninstall"
	RMDir /r "$INSTDIR"
	RmDir /r "$SMPROGRAMS\${APP_NAME}"
	Delete "$PROFILE\${APP_NAME}.ini"
	Delete "$DESKTOP\${APP_NAME}.lnk"
	Delete "$QUICKLAUNCH\${APP_NAME}.lnk"
	Delete "$FAVORITES\${APP_NAME} Home Page.lnk"
	DeleteRegKey HKCU "Software\${APP_NAME}"	;Not needed because we do not write on the registry.
	DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}"
SectionEnd



; -------------------------------------------------------------------------------------------------
VIProductVersion "${APP_VERSION}"
	VIAddVersionKey "ProductName" "${APP_NAME}"
	VIAddVersionKey "InternalName" "${APP_NAME}.exe"
	VIAddVersionKey "Comments" "Get more info at ${APP_WEB}"
	VIAddVersionKey "CompanyName" "${APP_COMPANY}"
	VIAddVersionKey "LegalTrademarks" "${APP_COPYRIGHT}"
	VIAddVersionKey "LegalCopyright" "${APP_COPYRIGHT}"
	VIAddVersionKey "FileDescription" "${APP_DESCRIPTION}"
	VIAddVersionKey "FileVersion" "${APP_VERSION}"

Vemos bastantes cosas destacables, como el manifest con la versión del instalador (VIProductVersion); el uso de la cabecera FileFunc.nsh para obtener el tamaño total de los archivos instalados, …

Para el manejo de la interfaz de usuario, te recomiendo incluir MUI.nsh, que da acceso al Modern User Interface, no solamente es más atractivo, sino también mucho más fácil de utilizar que los diálogos tradicionales.

Lo tienes disponible en la página de descargas. Luego podrás consultar la documentación online, o bien desde el archivo CHM incluido. Si necesitas más ejemplos, plugins, o macros, las tienes en el centro de desarrollo.


NSIS

7 comentarios en “NSIS”

  1. se ve interesante, pero yo trabajo con vb.net, uso sharpdevelop y obviamente .net de microsoft 3.5, se podría usar esto para generar un instalador facilmente?, ignoro si sharpdevelop tenga alguna función parecida, me parece que si, pero nunca lo he probado.

  2. Javier Gutiérrez Chamorro (Guti)

    Sí podrías crear un instalador para .NET, pero no parece la herramienta óptima Manuel.
    Te tendrías que encargar de detectar si el runtime ya está instalado, y qué versión, en caso contrario proceder a su descarga, e instalarlo. Todo esto es algo que te automatiza el instalador de Visual Studio.

  3. Javier Gutiérrez Chamorro (Guti)

    Hola Manuel,

    Sólo por curiosidad, qué instalador usas entones? O es que SharpDevelop ya trae uno?

    Hace años que lo probé, y fue con C#, y no VB.NET, y creo recordar que no traía ninguno.

    Muchas gracias.

  4. Pues, mira, cuando yo usaba vb6 usaba el instalador que traía integrado, pero en sharpdevelop me parece que no trae uno, tiene la función de crear un proyecto de instalación, pero no de instalar un proyecto. De manera que como requisito para mis apps es que halla .net 3.5 en la maquina cliente y por ahora lo instalo mediante un programita propio, pero no puede instalar o detectar si .net está en el equipo.

  5. Javier Gutiérrez Chamorro (Guti)

    En ese sentido no parece que SharpDevelop haya mejorado mucho desde que lo miré hace años. Del instalador de VB6, lo recuerdo perfectamente, yo también lo usé. No era gran cosa, pero si consideramos que estaba enteramente escrito en VB, demostraba muy bien todo lo que se podía hacer con el lenguaje teniendo los conocimientos necesarios. Básicamente llamando a la API de Windows.

Deja un comentario