¿Cómo agregar una acción personalizada de WiX que ocurre solo en la desinstalación (a través de MSI)?

161

Me gustaría modificar un instalador MSI (creado a través de WiX ) para eliminar un directorio completo en la desinstalación.

Entiendo las opciones RemoveFiley RemoveFolderen WiX, pero estas no son lo suficientemente robustas para eliminar recursivamente una carpeta completa que tiene contenido creado después de la instalación.

Noté la pregunta similar de Stack Overflow Eliminar archivos al desinstalar WiX , pero me preguntaba si esto podría hacerse más simplemente usando una llamada a un script por lotes para eliminar la carpeta.

Esta es la primera vez que uso WiX, y todavía me estoy acostumbrando a las acciones personalizadas . ¿Cuál sería un ejemplo básico de una acción personalizada que ejecutará un script por lotes en la desinstalación?

Comunidad
fuente

Respuestas:

188

EDITAR : Quizás mire la respuesta actualmente inmediatamente debajo .


Este tema ha sido un dolor de cabeza por mucho tiempo. Finalmente lo resolví. Hay algunas soluciones en línea, pero ninguna de ellas realmente funciona. Y, por supuesto, no hay documentación. Por lo tanto, en el cuadro a continuación hay varias propiedades que se sugiere usar y los valores que tienen para varios escenarios de instalación:

texto alternativo

Entonces, en mi caso, quería una CA que se ejecute solo en desinstalaciones, no en actualizaciones, no en reparaciones o modificaciones. De acuerdo con la tabla anterior, tuve que usar

<Custom Action='CA_ID' Before='other_CA_ID'>
        (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")</Custom>

¡Y funcionó!

nelsonjchen
fuente
25
¿Son correctos los valores en ese gráfico? ¿Por qué necesitarías agregar REMOVE = "ALL"? NOT UPGRADINGPRODUCTCODE solo es cierto para una desinstalación (de acuerdo con la tabla), por lo que (NOT UPGRADINGPRODUCTCODE) AND (REMOVE = "ALL") también solo sería cierto en una desinstalación. REMOVE = "ALL" parece innecesario.
Todd Ropog
2
Estoy de acuerdo con @ToddRopog: el ejemplo y la tabla de verdad no parecen estar de acuerdo. ¿Es eso realmente correcto?
Tim Long
19
La tabla de verdad está ligeramente equivocada. NO ACTUALIZAR EL CÓDIGO DE PRODUCTO también es válido para una primera instalación
Neil
2
Condiciones comunes: alekdavis.blogspot.ru/2013/05/…
KindDragon
1
Confirme: Instalado e INSTALADO son cosas diferentes, solo Instalado lo establece Windows Installer. No creo que INSTALADO funcione.
Micha Wiedenmann
140

Hay múltiples problemas con la respuesta de yaluna , también los nombres de propiedad distinguen entre mayúsculas y minúsculas, Installedes la ortografía correcta ( INSTALLEDno funcionará). La tabla anterior debería haber sido esta:

ingrese la descripción de la imagen aquí

Suponiendo también que una reparación completa y desinstalar los valores reales de las propiedades podría ser:

ingrese la descripción de la imagen aquí

La documentación de la sintaxis de expresión de WiX dice:

En estas expresiones, puede usar nombres de propiedad (recuerde que distinguen entre mayúsculas y minúsculas).

Las propiedades están documentadas en la Guía del instalador de Windows (por ejemplo, instalado )

EDITAR: pequeña corrección a la primera tabla; evidentemente, "Desinstalar" también puede ocurrir con solo REMOVEser True.

ahmd0
fuente
3
REMOVE también parece estar configurado para Change
szx
2
La columna 'Actualizar', ¿es eso durante la secuencia de desinstalación de la versión anterior o la secuencia de instalación de la nueva versión?
Nick Whaley
1
@NickWhaley: No lo he investigado por un tiempo, pero creo que la opción "Actualizar" es solo cuando instalo una versión mayor que la que ya está instalada.
ahmd0
1
@ ahmd0, bueno, por supuesto. Pero hay una instalación anidada que ocurre dentro de RemoveExistingProducts que tiene un conjunto totalmente diferente de propiedades. Eso es lo que está en su columna 'Actualizar'. El resto de la actualización es idéntica a la columna 'Instalar'.
Nick Whaley
1
@NickWhaley: La opción REMOVE será verdadera para las actualizaciones principales, es decir, 1.0.0 a 2.0.0, no 1.0.0 a 1.1.0, durante la ejecución del desinstalador de la versión anterior. Para ejecutar una acción personalizada durante una actualización importante en la instalación de las nuevas versiones, deberá hacer referencia a la propiedad de acción definida en la tabla MSI de actualización para la actualización de esa versión. symantec.com/connect/articles/msi-upgrade-overview msdn.microsoft.com/en-us/library/aa372379%28v=vs.85%29.aspx
Chaoix
48

Puede hacer esto con una acción personalizada. Puede agregar una referencia a su acción personalizada en <InstallExecuteSequence>:

<InstallExecuteSequence>
...
  <Custom Action="FileCleaner" After='InstallFinalize'>
          Installed AND NOT UPGRADINGPRODUCTCODE</Custom>

Entonces también tendrás que definir tu acción en <Product>:

<Product> 
...
  <CustomAction Id='FileCleaner' BinaryKey='FileCleanerEXE' 
                ExeCommand='' Return='asyncNoWait'  />

Donde FileCleanerEXE es un binario (en mi caso, un pequeño programa c ++ que realiza la acción personalizada) que también se define en <Product>:

<Product> 
...
  <Binary Id="FileCleanerEXE" SourceFile="path\to\fileCleaner.exe" />

El verdadero truco para esto es la Installed AND NOT UPGRADINGPRODUCTCODEcondición de la Acción personalizada, sin que su acción se ejecute en cada actualización (ya que una actualización es realmente una desinstalación y luego reinstalar). Lo que si está eliminando archivos probablemente no sea lo que desea durante la actualización.

En una nota al margen: recomiendo pasar por el problema de usar algo como el programa C ++ para realizar la acción, en lugar de un script por lotes debido a la potencia y el control que proporciona, y puede evitar que la ventana "cmd prompt" parpadee mientras su instalador se ejecuta.

csexton
fuente
3
25 votos a favor pero no una respuesta aceptada. ¡Bienvenido al mundo de los instaladores! :)
Christopher Painter
44
Esto realmente no funciona. Cuando desee ejecutar un fileCleaner.exe, que está instalado en su propia carpeta de instalación, esto será un problema de gallina y huevo: CustomActionse ejecutará "After = 'InstallFinalize'". En este punto, todos los archivos se eliminan de la carpeta Instalación. También el fileCleaner.exe. Por lo tanto, no puede ejecutarlo a través de CustomAction. Esta respuesta es simplemente incorrecta. Me pregunto sobre los 42 votos a favor.
Simon
40

El mayor problema con un script por lotes es manejar la reversión cuando el usuario hace clic en cancelar (o algo sale mal durante la instalación). La forma correcta de manejar este escenario es crear una CustomAction que agregue filas temporales a la tabla RemoveFiles. De esta forma, el Instalador de Windows maneja los casos de reversión por usted. Es increíblemente más simple cuando ves la solución.

De todos modos, para que una acción solo se ejecute durante la desinstalación, agregue un elemento Condición con:

REMOVE ~= "ALL"

el ~ = dice comparar mayúsculas y minúsculas (aunque creo que TODO siempre está en mayúscula). Consulte la documentación del SDK de MSI sobre la sintaxis de condiciones para obtener más información.

PD: Nunca hubo un caso en el que me senté y pensé: "Oh, el archivo por lotes sería una buena solución en un paquete de instalación". En realidad, encontrar un paquete de instalación que tenga un archivo por lotes solo me animaría a devolver el producto para obtener un reembolso.

Rob Mensching
fuente
Estaba a punto de usar un script por lotes y luego vi la sección PS. Gracias por salvarme:) Eliminar ~ = "TODO" funcionó para mí.
ArNumb
12

Aquí hay un conjunto de propiedades que hice que se siente más intuitivo de usar que las cosas integradas. Las condiciones se basan en la tabla de verdad proporcionada anteriormente por ahmd0.

<!-- truth table for installer varables (install vs uninstall vs repair vs upgrade) https://stackoverflow.com/a/17608049/1721136 -->
 <SetProperty Id="_INSTALL"   After="FindRelatedProducts" Value="1"><![CDATA[Installed="" AND PREVIOUSVERSIONSINSTALLED=""]]></SetProperty>
 <SetProperty Id="_UNINSTALL" After="FindRelatedProducts" Value="1"><![CDATA[PREVIOUSVERSIONSINSTALLED="" AND REMOVE="ALL"]]></SetProperty>
 <SetProperty Id="_CHANGE"    After="FindRelatedProducts" Value="1"><![CDATA[Installed<>"" AND REINSTALL="" AND PREVIOUSVERSIONSINSTALLED<>"" AND REMOVE=""]]></SetProperty>
 <SetProperty Id="_REPAIR"    After="FindRelatedProducts" Value="1"><![CDATA[REINSTALL<>""]]></SetProperty>
 <SetProperty Id="_UPGRADE"   After="FindRelatedProducts" Value="1"><![CDATA[PREVIOUSVERSIONSINSTALLED<>"" ]]></SetProperty>

Aquí hay algunos ejemplos de uso:

  <Custom Action="CaptureExistingLocalSettingsValues" After="InstallInitialize">NOT _UNINSTALL</Custom>
  <Custom Action="GetConfigXmlToPersistFromCmdLineArgs" After="InstallInitialize">_INSTALL OR _UPGRADE</Custom>
  <Custom Action="ForgetProperties" Before="InstallFinalize">_UNINSTALL OR _UPGRADE</Custom>
  <Custom Action="SetInstallCustomConfigSettingsArgs" Before="InstallCustomConfigSettings">NOT _UNINSTALL</Custom>
  <Custom Action="InstallCustomConfigSettings" Before="InstallFinalize">NOT _UNINSTALL</Custom>

Cuestiones:

Bill Tarbell
fuente
Esta es una gran solución. Recuerde también considerar las condiciones PATCH y MSIPATCHREMOVE.
Garet Jax
En su tabla de verdad, ¿pretendía usar VERSIONES ANTERIORES INSTALADAS en lugar de ACTUALIZAR EL CÓDIGO DE PRODUCTO como lo usa ahmd0? No veo ninguna referencia a PREVIOUSVERSIONSINSTALLED en la página de referencia de propiedades de MSI ( docs.microsoft.com/en-us/windows/win32/msi/property-reference ).
Patrick
Varios de los predicados para sus propiedades no tienen en cuenta todas las filas en la tabla de ahmd0 (Instalado, REINSTALL, UPGRADINGPRODUCTCODE y REMOVE). ¿Podría explicar por qué?
Patrick
0

Utilicé Acción personalizada codificada por separado en DLL de C ++ y utilicé la DLL para llamar a la función apropiada al desinstalar usando esta sintaxis:

<CustomAction Id="Uninstall" BinaryKey="Dll_Name" 
              DllEntry="Function_Name" Execute="deferred" />

Usando el bloque de código anterior, pude ejecutar cualquier función definida en C ++ DLL en la desinstalación. Para su información, mi función de desinstalación tenía código con respecto a la eliminación de datos de usuarios actuales y entradas de registro.

Sid
fuente