windows

Crear instalador para Windows con NSIS

En este post te enseñaré cómo hacer un Asistente de instalación en Windows para distribuir tus programas de manera simple y sencilla. Es decir, vamos a crear un Instalador de paquete.

Con lo que te mostraré vas a poder empacar tus programas y sus dependencias (imágenes, texturas, assets) en un ejecutable donde el usuario únicamente hará clic al botón de “Siguiente” y en donde tú podrás controlar cada paso de la instalación incluyendo ubicación, accesos directos, proceso de instalación, desinstalación y otras cosas.

Para ello vamos a usar NSIS y HM NIS Edit, programas muy buenos, simples y que funcionan perfectamente incluso en Windows 10 y 11.

Descargar programas

El software que usaremos para crear instaladores para nuestras aplicaciones se puede descargar desde los siguientes enlaces. Es totalmente gratuito y open source:

Si los enlaces no funcionan o cambian en el futuro simplemente busca los programas por su nombre en Google o tu buscador favorito.

Creando script de instalación

Debemos programar un script que NSIS va a “compilar”. Para ello es que vamos a usar HM NIS Edit, que permite crear scripts con un asistente o a través de plantillas.

Yo recomiendo abrirlo y crear uno con el asistente (Archivo > Nuevo script desde el asistente) para empezar a probar, ya que nos deja con una plantilla que podemos modificar.

Crear instalador de programa para Windows – Empaquetar software

No te preocupes si llenas los parámetros con valores que no existen o con valores de prueba, más adelante podrás personalizar y entender todo.

En mi caso me generó un script así:

; Script generated by the HM NIS Edit Script Wizard.

; HM NIS Edit Wizard helper defines
!define PRODUCT_NAME "Aplicación de Parzibyte"
!define PRODUCT_VERSION "1.0"
!define PRODUCT_PUBLISHER "Parzibyte"
!define PRODUCT_WEB_SITE "https://parzibyte.me/blog"
!define PRODUCT_DIR_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\EjecutablePrincipal.exe"
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
!define PRODUCT_UNINST_ROOT_KEY "HKLM"

; MUI 1.67 compatible ------
!include "MUI.nsh"

; MUI Settings
!define MUI_ABORTWARNING
!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico"
!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico"

; Welcome page
!insertmacro MUI_PAGE_WELCOME
; License page
!insertmacro MUI_PAGE_LICENSE "..\..\..\ruta\ala\licencia\TuLicenciaDeSoftware.txt"
; Instfiles page
!insertmacro MUI_PAGE_INSTFILES
; Finish page
!define MUI_FINISHPAGE_RUN "$INSTDIR\EjecutablePrincipal.exe"
!insertmacro MUI_PAGE_FINISH

; Uninstaller pages
!insertmacro MUI_UNPAGE_INSTFILES

; Language files
!insertmacro MUI_LANGUAGE "Spanish"

; MUI end ------

Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
OutFile "Instalar.exe"
InstallDir "$PROGRAMFILES\Aplicación de Parzibyte"
InstallDirRegKey HKLM "${PRODUCT_DIR_REGKEY}" ""
ShowInstDetails show
ShowUnInstDetails show

Section "Principal" SEC01
  SetOutPath "$INSTDIR"
  SetOverwrite ifnewer
  File "..\..\..\ruta\al\archivo\EjecutablePrincipal.exe"
  CreateDirectory "$SMPROGRAMS\Aplicación de Parzibyte"
  CreateShortCut "$SMPROGRAMS\Aplicación de Parzibyte\Aplicación de Parzibyte.lnk" "$INSTDIR\EjecutablePrincipal.exe"
  CreateShortCut "$DESKTOP\Aplicación de Parzibyte.lnk" "$INSTDIR\EjecutablePrincipal.exe"
  File "..\..\..\ruta\al\archivo\Archivo.ejemplo"
SectionEnd

Section -AdditionalIcons
  WriteIniStr "$INSTDIR\${PRODUCT_NAME}.url" "InternetShortcut" "URL" "${PRODUCT_WEB_SITE}"
  CreateShortCut "$SMPROGRAMS\Aplicación de Parzibyte\Website.lnk" "$INSTDIR\${PRODUCT_NAME}.url"
  CreateShortCut "$SMPROGRAMS\Aplicación de Parzibyte\Uninstall.lnk" "$INSTDIR\uninst.exe"
SectionEnd

Section -Post
  WriteUninstaller "$INSTDIR\uninst.exe"
  WriteRegStr HKLM "${PRODUCT_DIR_REGKEY}" "" "$INSTDIR\EjecutablePrincipal.exe"
  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe"
  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\EjecutablePrincipal.exe"
  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
SectionEnd


Function un.onUninstSuccess
  HideWindow
  MessageBox MB_ICONINFORMATION|MB_OK "La desinstalación de $(^Name) finalizó satisfactoriamente."
FunctionEnd

Function un.onInit
  MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "¿Está completamente seguro que desea desinstalar $(^Name) junto con todos sus componentes?" IDYES +2
  Abort
FunctionEnd

Section Uninstall
  Delete "$INSTDIR\${PRODUCT_NAME}.url"
  Delete "$INSTDIR\uninst.exe"
  Delete "$INSTDIR\Archivo.ejemplo"
  Delete "$INSTDIR\EjecutablePrincipal.exe"

  Delete "$SMPROGRAMS\Aplicación de Parzibyte\Uninstall.lnk"
  Delete "$SMPROGRAMS\Aplicación de Parzibyte\Website.lnk"
  Delete "$DESKTOP\Aplicación de Parzibyte.lnk"
  Delete "$SMPROGRAMS\Aplicación de Parzibyte\Aplicación de Parzibyte.lnk"

  RMDir "$SMPROGRAMS\Aplicación de Parzibyte"
  RMDir "$INSTDIR"

  DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
  DeleteRegKey HKLM "${PRODUCT_DIR_REGKEY}"
  SetAutoClose true
SectionEnd

Obviamente para mi instalador he modificado varias cosas y ahora te voy a mostrar cuáles fueron, sección por sección.

Definiendo variables

Me gusta tener las cosas ordenadas así que he definido variables para el nombre de mi ejecutable principal y cosas por el estilo. También he deshabilitado la licencia pues no necesito que los usuarios acepten algo (por ahora). El inicio quedó así:

!define PRODUCT_NAME "Sistema gratuito para restaurantes By Parzibyte"
!define URL_PROGRAMA "http://localhost:5000/public/#/usuarios/login"
; mío
!define NOMBRE_EJECUTABLE_PROGRAMA "Sistema gratuito para restaurantes by parzibyte.exe"
!define NOMBRE_EJECUTABLE_DETENER "detener.exe"
!define PRODUCT_VERSION "1.0"
!define PRODUCT_PUBLISHER "Parzibyte"
!define PRODUCT_WEB_SITE "https://parzibyte.me/blog"
!define PRODUCT_DIR_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\${NOMBRE_EJECUTABLE_PROGRAMA}"
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
!define PRODUCT_UNINST_ROOT_KEY "HKLM"

Tú puedes definir tus propios valores para no estar escribiéndolos en cada línea donde necesites referenciarlos, y si necesitas cambiarlos puedes hacerlo en un solo lugar.

Función al terminar instalación

Como te dije anteriormente, con este creador de instaladores de paquetes podemos controlar varios pasos del proceso de la instalación. Uno de ellos se ejecuta cuando la instalación ha terminado; podemos ejecutar un comando o una función; yo me he decidido por una función y queda así:

; Finish page
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_FUNCTION funcionAlTerminar
!insertmacro MUI_PAGE_FINISH

Function funcionAlTerminar
  Exec "$INSTDIR\${NOMBRE_EJECUTABLE_PROGRAMA}"
  ExecShell "open" "${URL_PROGRAMA}"
FunctionEnd

Lo que estoy haciendo es ejecutar mi programa principal (mismo que inicia un servidor web) y luego abrir la URL de ese servidor en el navegador web por defecto. Obviamente puedes hacer más cosas aquí, pero yo solo estoy haciendo 2 cosas con Exec y ExecShell.

Ubicación de instalación

El asistente te va a preguntar la ruta de instalación de tu programa. Normalmente será en Program Files pero en mi caso específico tuve problemas al hacerlo, porque mi aplicación no se ejecutaba si no lo hacía con permisos de administrador.

La solución fue cambiar la ruta de instalación a AppData así:

InstallDir "$APPDATA\${PRODUCT_NAME}"

Para referirnos a esa carpeta simplemente indicamos $APPDATA. Recuerda que PRODUCT_NAME ya está definido anteriormente.

Nota: no hay diferencia entre $VARIABLE y ${VARIABLE} pero yo recomiendo usar la segunda forma.

Sección principal: archivos a instalar

Llegamos a la parte que yo considero más importante. Cuando queremos distribuir un programa para Windows y empaquetar todo en un instalador necesitamos indicar cuáles archivos van a instalarse o copiarse en la computadora del usuario.

Usamos File para indicar que debe tomar un archivo de nuestra computadora (puede ser relativo o absoluto) e instalarlo en la ruta de instalación previamente definida.

También podemos copiar directorios completos, para ello primero debemos movernos al directorio donde queremos copiar el contenido del directorio y luego hacer un File /r.

Igualmente en este paso es donde podemos crear accesos directos e incluso hacer que el programa instalado inicie con Windows automáticamente. En mi caso esta sección queda así:

Section "Principal" SEC01
  SetOutPath "$INSTDIR"
  SetOverwrite ifnewer
  File ".\api\${NOMBRE_EJECUTABLE_PROGRAMA}"
  File ".\detener\${NOMBRE_EJECUTABLE_DETENER}"
  SetOutPath "$INSTDIR\dist"
  File /r ".\dist\"
  SetOutPath "$INSTDIR"
  CreateShortCut "$APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\${PRODUCT_NAME}.lnk" "$INSTDIR\${NOMBRE_EJECUTABLE_PROGRAMA}"
  CreateShortCut "$DESKTOP\Desinstalar ${PRODUCT_NAME}.lnk" "$INSTDIR\uninst.exe"
  WriteIniStr "$DESKTOP\${PRODUCT_NAME}.url" "InternetShortcut" "URL" "${URL_PROGRAMA}"
SectionEnd

Fíjate que en la línea 6 me muevo a la carpeta dist que será creada si no existe y luego de eso copio recursivamente la carpeta dist. Ese comando copiará todo el contenido de esa carpeta pero no a la propia carpeta, por eso es que necesito “moverme” de directorio antes de ejecutar ese comando, y para ello uso SetOutPath.

También estoy haciendo que mi programa inicie con Windows en la línea 9, y finalmente estoy creando un acceso directo al sitio con WriteIniStr ya que (lo descubrí al hacerlo) esos accesos son simples archivos ini.

Nota: $DESKTOP es el escritorio del usuario.

Desinstalación

Con NSIS podemos indicar el proceso de desinstalación del programa. Recuerda que esto es importante para que el usuario tenga control sobre nuestro software.

La desinstalación en mi caso necesita más cosas ya que tengo que borrar otras carpetas y archivos. En mi caso queda así:

Section Uninstall
  DetailPrint "Deteniendo servidor..."
  DetailPrint "$INSTDIR\${NOMBRE_EJECUTABLE_DETENER}"
  Exec "$INSTDIR\${NOMBRE_EJECUTABLE_DETENER}"
  Sleep 5000
  DetailPrint "Detenido"
  Delete "$INSTDIR\uninst.exe"
  Delete "$INSTDIR\${NOMBRE_EJECUTABLE_PROGRAMA
  Delete "$INSTDIR\${NOMBRE_EJECUTABLE_DETENER}"
  Delete "$INSTDIR\*.db"
  Delete "$INSTDIR\*.log"
  RMDir /r "$INSTDIR\dist"
  RMDir /r "$INSTDIR\fotos_platillos"
  Delete "$SMPROGRAMS\Sistema para restaurantes By Parzibyte\Uninstall.lnk"
  Delete "$DESKTOP\${PRODUCT_NAME}.url"
  DELETE "$DESKTOP\Desinstalar ${PRODUCT_NAME}.lnk"
  Delete "$SMPROGRAMS\Sistema para restaurantes By Parzibyte\Sistema para restaurantes By Parzibyte.lnk"
  Delete "$APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\${PRODUCT_NAME}.lnk"
  RMDir "$SMPROGRAMS\Sistema para restaurantes By Parzibyte"
  RMDir "$INSTDIR"
  DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
  DeleteRegKey HKLM "${PRODUCT_DIR_REGKEY}"
  SetAutoClose true
SectionEnd

Por ejemplo, necesito detener mi servidor con otro programa que incluyo en la instalación, así que lo ejecuto, espero 5 segundos y después ya puedo borrar todo lo demás.

No olvides que $INSTDIR es la ruta de instalación de nuestro programa.

Como puedes ver también estoy eliminando la carpeta dist y fotos_platillos de manera recursiva así como todos los archivos con extensión .db y .log ya que esos archivos son creados por mi programa.

Por cierto, RMDir no va a funcionar si el directorio tiene contenido (por ello es que cuando quiero que lo haga recursivamente especifico /r), así que asegúrate de vaciar la carpeta antes de intentar borrarla en la línea 20.

Creando instalador

Cuando ya tenemos nuestro script (modificado con HM NIS Edit) podemos “compilarlo” creando el instalador yendo a NSIS > Compilar. El instalador estará en la misma ubicación que el script.

NSIS – Empaquetar nuestro programa (Crear paquete de instalación)

Si hay algún problema (como errores de sintaxis o archivos no referenciados correctamente) el editor te lo va a decir. Solo es cuestión de ajustar las cosas así como cuando programamos en cualquier otro lenguaje.

Bonus: automatizar creación de instalador

Una vez que hayas probado tu script, puedes automatizar la creación del paquete de instalación.

Al final lo que hace el editor es invocar a NSIS, concretamente al binario makensis; así que basta con agregar la carpeta que lo contiene (en mi caso es C:\Program Files (x86)\NSIS\) a la PATH y luego invocarlo así:

makensis script.nsi

Yo he llevado el proceso más allá y he creado un script de Python que compila mi frontend, backend, calcula sumas de verificación y crea el instalador. Aquí te lo dejo:

import argparse
import datetime
import os
import shutil
from subprocess import check_output
import hashlib

NOMBRE_DIRECTORIO_DIST = "dist"
NOMBRE_DIRECTORIO_API = "api"
NOMBRE_DIRECTORIO_DETENER = "detener"
NOMBRE_DE_EJECUTABLE_SIN_EXTENSION = "Sistema gratuito para restaurantes by parzibyte"
NOMBRE_DE_EJECUTABLE_DETENER = "detener.exe"
NOMBRE_ARCHIVO_CHECKSUM = "checksum_archivos.go"

fecha_y_hora = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")

directorio_inicial = os.getcwd()

parser = argparse.ArgumentParser()
parser.add_argument(
    "arquitectura", help="Arquitectura para la que se compila. Puede ser 32 o 64 bits, pero si compilas para 32 bits recuerda modificar la PATH")
argumentos = parser.parse_args()
arquitecturas = ["32", "64"]
if not argumentos.arquitectura in arquitecturas:
    print(
        f"La arquitectura debe ser una de: {arquitecturas} pero {argumentos.arquitectura} fue proporcionado")
    exit()

arquitectura = argumentos.arquitectura
print("""
Si vas a compilar para 32 bits, probablemente quieras:
SET PATH=C:\Go32\go\\bin;C:\MinGW\\bin;%PATH% && SET GOROOT=C:\Go32\go\
""")


def hash_file(filename):

    # make a hash object
    h = hashlib.sha512()

    # open file for reading in binary mode
    with open(filename, 'rb') as file:

        # loop till the end of the file
        chunk = 0
        while chunk != b'':
            # read only 1024 bytes at a time
            chunk = file.read(1024)
            h.update(chunk)

    # return the hex representation of digest
    return h.hexdigest()


def obtener_codigo_go_para_checksum(archivos_checksum):
    codigo = """
package main

/*
    Este código es autogenerado por el script de Python. No se recomienda tocarlo,
    pero si quieres puedes hacerlo. Solo es un mapa en donde la clave es la ubicación relativa
    del archivo, y el valor es el SHA512 de ese archivo

    Generado el: """+fecha_y_hora + """
*/
var ubicacionesConHash = map[string]string{
"""

    for archivo in archivos_checksum:
        codigo += f"\t\"./{NOMBRE_DIRECTORIO_DIST}/{archivo['ubicacion']}\" : \"{archivo['hash']}\",\n"

    codigo += """
}"""
    return codigo


def obtener_lista_archivos_para_checksum(directorio_dist):
    archivos = []
    for directorio in os.listdir(directorio_dist):
        ruta_completa = os.path.join(directorio_dist, directorio)
        if os.path.isfile(ruta_completa):
            archivos.append({
                "hash": hash_file(ruta_completa),
                "ubicacion": directorio,
            })
        if os.path.isdir(ruta_completa):
            for subdirectorio in os.listdir(ruta_completa):
                if os.path.isfile(os.path.join(ruta_completa, subdirectorio)):
                    archivos.append({
                        "hash": hash_file(os.path.join(ruta_completa, subdirectorio)),
                        "ubicacion": directorio+"/" + subdirectorio,
                    })
    return archivos


# Compilar cliente
comando_compilar_cliente = "npm run build"
print(f"Compilando cliente con {comando_compilar_cliente}...")
check_output(comando_compilar_cliente, shell=True)


directorio_dist = os.path.join(directorio_inicial, NOMBRE_DIRECTORIO_DIST)
print("Obteniendo hash de archivos dist")
archivos_checksum = obtener_lista_archivos_para_checksum( directorio_dist)
ruta_api = os.path.join(directorio_inicial, NOMBRE_DIRECTORIO_API)
ruta_detener = os.path.join(directorio_inicial, NOMBRE_DIRECTORIO_DETENER)
ruta_archivo_checksum = os.path.join(ruta_api, NOMBRE_ARCHIVO_CHECKSUM)
print(f"Escribiendo código Golang en {ruta_archivo_checksum}")
with open(ruta_archivo_checksum, "w+", encoding="utf-8") as archivo:
    archivo.write(obtener_codigo_go_para_checksum(archivos_checksum))

print(f"Cambiando directorio a {ruta_api}")
os.chdir(ruta_api)
nombre_ejecutable = f"{NOMBRE_DE_EJECUTABLE_SIN_EXTENSION}.exe"
comando = f"go build -tags produccion -ldflags \"-H windowsgui\" -o \"{nombre_ejecutable}\""
print(f"Compilando API con {comando}...")
check_output(comando, shell=True)
print(f"Cambiando directorio a {directorio_inicial}")
os.chdir(directorio_inicial)


print(f"Cambiando directorio a {directorio_inicial}")
os.chdir(directorio_inicial)
print(f"Cambiando directorio a {ruta_detener}")
os.chdir(ruta_detener)
comando = f"go build -ldflags \"-H windowsgui\" -o \"{NOMBRE_DE_EJECUTABLE_DETENER}\""
print(f"Compilando binario para detener servidor con {comando}...")
check_output(comando, shell=True)
print(f"Cambiando directorio a {directorio_inicial}")
os.chdir(directorio_inicial)
comando_crear_instalador = "makensis instalador.nsi"
print(f"Creando instalador con {comando_crear_instalador}...")
check_output(comando_crear_instalador, shell=True)

Fíjate en que estoy invocando a makensis en la línea 132.

Obviamente este instalador sirve para todo tipo de programas. Yo lo he usado para distribuir mi webapp y funciona como un encanto.

Poniendo todo junto

Hasta este punto te he mostrado los aspectos más importantes y cómo es que he modificado el script para ajustarlo a mis necesidades.

Obviamente puedes hacer muchas cosas más, solo es cuestión de leer la documentación para poder hacer lo que necesitas.

Para terminar te dejo mi script completo aunque ya te mostré lo más importante.

; Script generated by the HM NIS Edit Script Wizard.

; HM NIS Edit Wizard helper defines
!define PRODUCT_NAME "Sistema gratuito para restaurantes By Parzibyte"
!define URL_PROGRAMA "http://localhost:5000/public/#/usuarios/login"
!define NOMBRE_EJECUTABLE_PROGRAMA "Sistema gratuito para restaurantes by parzibyte.exe"
!define NOMBRE_EJECUTABLE_DETENER "detener.exe"
!define PRODUCT_VERSION "1.0"
!define PRODUCT_PUBLISHER "Parzibyte"
!define PRODUCT_WEB_SITE "https://parzibyte.me/blog"
!define PRODUCT_DIR_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\${NOMBRE_EJECUTABLE_PROGRAMA}"
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
!define PRODUCT_UNINST_ROOT_KEY "HKLM"

; MUI 1.67 compatible ------
!include "MUI.nsh"

; MUI Settings
!define MUI_ABORTWARNING
!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico"
!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico"

; Welcome page
!insertmacro MUI_PAGE_WELCOME
; Directory page
!insertmacro MUI_PAGE_DIRECTORY
; Instfiles page
!insertmacro MUI_PAGE_INSTFILES
; Finish page
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_FUNCTION funcionAlTerminar
!insertmacro MUI_PAGE_FINISH

Function funcionAlTerminar
  Exec "$INSTDIR\${NOMBRE_EJECUTABLE_PROGRAMA}"
  ExecShell "open" "${URL_PROGRAMA}"
FunctionEnd

; Uninstaller pages
!insertmacro MUI_UNPAGE_INSTFILES

; Language files
!insertmacro MUI_LANGUAGE "Spanish"

; MUI end ------

Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
OutFile "Sistema para restaurantes By Parzibyte (Instalador).exe"
InstallDir "$APPDATA\${PRODUCT_NAME}"
InstallDirRegKey HKLM "${PRODUCT_DIR_REGKEY}" ""
ShowInstDetails show
ShowUnInstDetails show

Section "Principal" SEC01
  SetOutPath "$INSTDIR"
  SetOverwrite ifnewer
  File ".\api\${NOMBRE_EJECUTABLE_PROGRAMA}"
  File ".\detener\${NOMBRE_EJECUTABLE_DETENER}"
  SetOutPath "$INSTDIR\dist"
  File /r ".\dist\"
  SetOutPath "$INSTDIR"
  CreateShortCut "$APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\${PRODUCT_NAME}.lnk" "$INSTDIR\${NOMBRE_EJECUTABLE_PROGRAMA}"
  CreateShortCut "$DESKTOP\Desinstalar ${PRODUCT_NAME}.lnk" "$INSTDIR\uninst.exe"
  WriteIniStr "$DESKTOP\${PRODUCT_NAME}.url" "InternetShortcut" "URL" "${URL_PROGRAMA}"
SectionEnd

Section -AdditionalIcons
  CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk" "$INSTDIR\uninst.exe"
SectionEnd

Section -Post
  WriteUninstaller "$INSTDIR\uninst.exe"
  WriteRegStr HKLM "${PRODUCT_DIR_REGKEY}" "" "$INSTDIR\EjecutablePrincipal.exe"
  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe"
  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\EjecutablePrincipal.exe"
  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
  WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
SectionEnd


Function un.onUninstSuccess
  HideWindow
  MessageBox MB_ICONINFORMATION|MB_OK "La desinstalaci�n de $(^Name) finaliz� satisfactoriamente."
FunctionEnd

Function un.onInit
  MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "�Est� completamente seguro que desea desinstalar $(^Name) junto con todos sus componentes?" IDYES +2
  Abort
FunctionEnd

Section Uninstall
  DetailPrint "Deteniendo servidor..."
  DetailPrint "$INSTDIR\${NOMBRE_EJECUTABLE_DETENER}"
  Exec "$INSTDIR\${NOMBRE_EJECUTABLE_DETENER}"
  Sleep 5000
  DetailPrint "Detenido"
  Delete "$INSTDIR\uninst.exe"
  Delete "$INSTDIR\${NOMBRE_EJECUTABLE_PROGRAMA}"
  Delete "$INSTDIR\${NOMBRE_EJECUTABLE_DETENER}"
  Delete "$INSTDIR\*.db"
  Delete "$INSTDIR\*.log"
  RMDir /r "$INSTDIR\dist"
  RMDir /r "$INSTDIR\fotos_platillos"
  Delete "$SMPROGRAMS\Sistema para restaurantes By Parzibyte\Uninstall.lnk"
  Delete "$DESKTOP\${PRODUCT_NAME}.url"
  DELETE "$DESKTOP\Desinstalar ${PRODUCT_NAME}.lnk"
  Delete "$SMPROGRAMS\Sistema para restaurantes By Parzibyte\Sistema para restaurantes By Parzibyte.lnk"
  Delete "$APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\${PRODUCT_NAME}.lnk"
  RMDir "$SMPROGRAMS\Sistema para restaurantes By Parzibyte"
  RMDir "$INSTDIR"
  DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
  DeleteRegKey HKLM "${PRODUCT_DIR_REGKEY}"
  SetAutoClose true
SectionEnd

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Entradas recientes

Servidor HTTP en Android con Flutter

El día de hoy te mostraré cómo crear un servidor HTTP (servidor web) en Android…

4 días hace

Imprimir automáticamente todos los PDF de una carpeta

En este post te voy a enseñar a designar una carpeta para imprimir todos los…

4 días hace

Guía para imprimir en plugin versión 1 desde Android

En este artículo te voy a enseñar la guía para imprimir en una impresora térmica…

1 semana hace

Añadir tasa de cambio en sistema de información

Hoy te voy a mostrar un ejemplo de programación para agregar un módulo de tasa…

2 semanas hace

Comprobar validez de licencia de plugin ESC POS

Los usuarios del plugin para impresoras térmicas pueden contratar licencias, y en ocasiones me han…

2 semanas hace

Imprimir euro € en impresora térmica

Hoy voy a enseñarte cómo imprimir el € en una impresora térmica. Vamos a ver…

4 semanas hace

Esta web usa cookies.