Como adicionar uma ação personalizada do WiX que ocorre apenas na desinstalação (via MSI)?

160

Gostaria de modificar um instalador MSI (criado pelo WiX ) para excluir um diretório inteiro na desinstalação.

Entendo as opções RemoveFilee RemoveFolderno WiX, mas elas não são robustas o suficiente para excluir recursivamente uma pasta inteira com conteúdo criado após a instalação.

Percebi a pergunta similar Stack Overflow Removendo arquivos ao desinstalar o WiX , mas fiquei pensando se isso poderia ser feito mais simplesmente usando uma chamada para um script em lote para excluir a pasta.

Esta é a primeira vez que uso o WiX, e ainda estou pegando o jeito das ações personalizadas . Qual seria um exemplo básico de uma ação personalizada que executará um script em lote na desinstalação?

Comunidade
fonte

Respostas:

188

EDIT : Talvez veja a resposta atualmente imediatamente abaixo .


Este tópico é uma dor de cabeça há muito tempo. Eu finalmente descobri. Existem algumas soluções online, mas nenhuma delas realmente funciona. E, claro, não há documentação. Portanto, no gráfico abaixo, existem várias propriedades sugeridas para uso e os valores que eles têm para vários cenários de instalação:

texto alternativo

Portanto, no meu caso, eu queria uma CA que funcionasse apenas em desinstalações - não atualizações, reparos ou modificações. De acordo com a tabela acima, eu tive que usar

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

E funcionou!

nelsonjchen
fonte
25
Os valores nesse gráfico estão corretos? Por que você precisaria adicionar REMOVE = "ALL"? NOT UPGRADINGPRODUCTCODE é válido apenas para uma desinstalação (de acordo com o gráfico), portanto (NOT UPGRADINGPRODUCTCODE) AND (REMOVE = "ALL") também seria válido apenas em uma desinstalação. O REMOVE = "ALL" parece desnecessário.
Todd Ropog
2
Concordo com @ToddRopog - O exemplo e a tabela da verdade não parecem concordar. Isso está realmente correto?
Tim Long
19
A tabela da verdade está um pouco errada. NÃO UPGRADINGPRODUCTCODE é verdade para uma primeira instalação bem
Neil
2
Condições comuns: alekdavis.blogspot.ru/2013/05/…
KindDragon
1
Confirme: Instalado e INSTALADO são coisas diferentes, apenas o Instalado é definido pelo Windows Installer. Eu não acho que o INSTALADO funcione.
Micha Wiedenmann
139

Existem vários problemas com a resposta de yaluna , também os nomes de propriedades diferenciam maiúsculas de minúsculas, Installedé a ortografia correta ( INSTALLEDnão funcionará). A tabela acima deveria ter sido esta:

insira a descrição da imagem aqui

Supondo também que um reparo completo e desinstalação dos valores reais das propriedades possam ser:

insira a descrição da imagem aqui

A documentação da WiX Expression Syntax diz:

Nessas expressões, você pode usar nomes de propriedades (lembre-se de que eles diferenciam maiúsculas de minúsculas).

As propriedades estão documentadas no Guia do Windows Installer (por exemplo, Instalado )

EDIT: Pequena correção para a primeira tabela; evidentemente "Desinstalar" também pode acontecer com apenas REMOVEser True.

ahmd0
fonte
3
REMOVE também parece estar definido para Alterar
szx
2
A coluna 'Upgrade' é aquela durante a sequência de desinstalação da versão antiga ou a sequência de instalação da nova versão?
Nick Whaley
1
@NickWhaley: Eu não olho para isso há um tempo, mas acredito que a opção "Atualizar" é apenas ao instalar uma versão maior que a já instalada.
precisa saber é
1
@ ahmd0, bem, é claro. Mas há uma instalação aninhada que ocorre dentro de RemoveExistingProducts que possui um conjunto de propriedades totalmente diferente. É isso que está na coluna "Atualização". O restante da atualização é idêntico à coluna 'Instalar'.
Nick Whaley
1
@NickWhaley: a opção REMOVE será verdadeira para atualizações principais, ou seja, 1.0.0 a 2.0.0, não 1.0.0 a 1.1.0, durante a execução do desinstalador da versão anterior. Para executar uma Ação Personalizada durante uma Atualização Principal nas novas versões instaladas, você precisará fazer referência à ActionProperty definida na sua tabela MSI de Atualização para essa atualização de versão. symantec.com/connect/articles/msi-upgrade-overview msdn.microsoft.com/en-us/library/aa372379%28v=vs.85%29.aspx
Chaoix
48

Você pode fazer isso com uma ação personalizada. Você pode adicionar uma referência à sua ação personalizada em <InstallExecuteSequence>:

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

Então você também terá que definir sua ação em <Product>:

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

Onde FileCleanerEXE é um binário (no meu caso, um pequeno programa c ++ que executa a ação personalizada) que também é definido em <Product>:

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

O verdadeiro truque para isso é o Installed AND NOT UPGRADINGPRODUCTCODE condição da Ação personalizada, sem que sua ação seja executada em todas as atualizações (uma vez que uma atualização é realmente uma desinstalação e reinstalação). O que, se você estiver excluindo arquivos, provavelmente não será o desejado durante a atualização.

Em uma nota lateral: eu recomendo enfrentar o problema de usar algo como o programa C ++ para executar a ação, em vez de um script em lote devido à energia e controle que ele fornece - e você pode impedir que a janela "cmd prompt" pisque enquanto seu instalador é executado.

Csexton
fonte
3
25 votos positivos, mas não uma resposta aceita. Bem-vindo ao mundo dos instaladores! :)
Christopher Painter
4
Isso realmente não funciona. Quando você deseja executar um fileCleaner.exe, instalado em sua própria pasta de instalação, este é um problema comum: CustomActionserá executado "Depois = 'InstallFinalize'". Neste ponto, todos os arquivos são removidos da pasta Instalação. Também o fileCleaner.exe. Portanto, você não pode executá-lo por meio de uma CustomAction. Esta resposta está simplesmente errada. Eu estou querendo saber sobre os 42 upvotes!
Simon
40

O maior problema com um script em lote é lidar com a reversão quando o usuário clica em cancelar (ou algo dá errado durante a instalação). A maneira correta de lidar com esse cenário é criar uma CustomAction que adicione linhas temporárias à tabela RemoveFiles. Dessa forma, o Windows Installer lida com os casos de reversão para você. É incrivelmente mais simples quando você vê a solução.

De qualquer forma, para executar uma ação apenas durante a desinstalação, adicione um elemento Condition com:

REMOVE ~= "ALL"

o ~ = diz que a comparação não diferencia maiúsculas de minúsculas (embora eu ache que ALL é sempre superior). Consulte a documentação do MSI SDK sobre sintaxe de condições para obter mais informações.

PS: Nunca houve um caso em que me sentei e pensei: "Oh, o arquivo em lotes seria uma boa solução em um pacote de instalação". Na verdade, encontrar um pacote de instalação que contenha um arquivo em lotes só me incentivaria a devolver o produto para um reembolso.

Rob Mensching
fonte
Eu estava prestes a usar um script em lote e depois vi a seção PS. Obrigado por me salvar :) O Remove ~ = "ALL" funcionou para mim.
ArNumb 10/05/19
12

Aqui está um conjunto de propriedades que fiz que parecem mais intuitivas de usar do que as coisas incorporadas. As condições são baseadas na tabela verdade fornecida acima 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>

Aqui está um exemplo 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>

Problemas:

Bill Tarbell
fonte
Esta é uma otima soluçao. Lembre-se de considerar também as condições PATCH e MSIPATCHREMOVE.
precisa
Na sua tabela de verdade, você quis usar PREVIOUSVERSIONSINSTALLED em vez de UPGRADINGPRODUCTCODE como é usado por ahmd0? Não estou vendo nenhuma referência a PREVIOUSVERSIONSINSTALLED na página de referência da propriedade MSI ( docs.microsoft.com/en-us/windows/win32/msi/property-reference ).
Patrick
Vários predicados de suas propriedades não levam em consideração todas as linhas da tabela do ahmd0 (Instalado, REINSTALL, UPGRADINGPRODUCTCODE e REMOVE). Você poderia explicar o porquê?
Patrick
0

Usei a Ação personalizada codificada separadamente na DLL C ++ e usei a DLL para chamar a função apropriada em Desinstalando usando esta sintaxe:

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

Usando o bloco de código acima, consegui executar qualquer função definida na DLL do C ++ na desinstalação. Para sua informação, minha função de desinstalação tinha um código referente à limpeza de dados atuais do usuário e entradas do registro.

Sid
fonte