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.
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.
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