3

J'ai un projet MSI de base. Je dois retirer un autre produit MSI de l'installation qui est maintenant intégrée dans notre application principale. J'ai essayé d'utiliser les scénarios de mise à niveau et de le traiter comme une mise à jour majeure. Cependant, cela n'a pas fonctionné en raison des codes de mise à niveau ne correspondant pas je crois.Désinstaller un autre MSI sur l'installation

Ensuite, j'ai également fait une action personnalisée qui a exécuté msiexec.exe après le CostFinalize (je pense que cela a été indiqué dans l'aide de Installshield.) Cela a fonctionné parfaitement jusqu'à ce que je l'installe sur un système cherche à supprimer. Mon programme d'installation échouerait si l'autre produit obsolète n'était pas installé. J'ai essayé de mettre une condition sur l'action personnalisée définie par la recherche de système, mais il semble que la recherche de système est limitée dans la fonctionnalité. Je ne peux pas simplement vérifier une clé reg et définir une propriété booléenne.

Des idées?

Répondre

5

Quelques choses à considérer

1) Le UpgradeTable (FindRelatedProducts/RemoveExisting produits) peut être utilisé pour enlever ProductCodes associés à UpgradeCode d'un autre produit.

2) Si la mémoire est bonne, MSI ne supprimera pas un produit par utilisateur pendant une installation par machine (ou l'inverse). Le contexte doit être le même.

3) La séquence d'interface utilisateur ne s'exécute pas pendant les installations silencieuses.

4) Vous ne pouvez pas exécuter msiexec à partir de la séquence d'exécution car il existe un mutex d'une seule séquence d'exécution par machine sur l'ensemble du système.

5) Si vous planifiez dans l'interface utilisateur (je vous ai déjà dit que vous ne devriez pas le faire car il ne s'exécute pas pendant les installations silencieuses), un autre mutex indique seulement 1 UI par processus. Si vous passez d'un utilisateur par utilisateur ou par machine à une machine, je pense qu'il est raisonnable que vous soyez capable de faire ce que vous voulez en utilisant des éléments de mise à niveau/lignes de tableau sans écrire d'actions personnalisées. Sinon, vous aurez besoin d'un bootstrappeur de style setup.exe pour gérer la désinstallation avant d'entrer dans le monde msiexec.

+0

Merci pour l'info. J'ai été en mesure d'accomplir ce dont j'avais besoin en utilisant les éléments de mise à jour comme vous l'avez suggéré. – Web

-1

J'ai atteint cet objectif dans InstallShield 2013 à l'aide d'InstallScript personnalisé. Le script est exécuté via une action personnalisée dans la séquence d'interface utilisateur, mais je l'ai placé après le dialogue "SetupProgress", c'est-à-dire avant "Exécuter l'action" au lieu de après le CostFinalize (comme le dit la documentation). J'ai ajouté la condition "NON installé" à l'action. Si vous le placez dans l'ordre suggéré, la désinstallation sera lancée dès que le programme d'installation aura été initialisé. Si vous le déplacez à l'endroit où je l'ai fait, il ne démarre pas tant que l'utilisateur n'a pas cliqué sur le bouton d'installation finale.

La raison de mettre cela dans la séquence de l'interface utilisateur est de contourner le programme d'installation (ou de désinstallation) d'un msi à un moment donné. Cela ne fonctionne tout simplement pas dans la séquence d'exécution à cause de cette restriction.

La principale faiblesse de cette méthode est que, comme l'a déclaré Christopher, cela ne fonctionnera pas dans une installation silencieuse (qui se trouve également dans la documentation de l'IS). C'est le moyen officiel pour y parvenir. (check out: http://helpnet.installshield.com/installshield16helplib/IHelpCustomActionMSIExec.htm) Si vous pouvez vivre avec cela (puisque l'installation silencieuse est généralement comme un cas particulier), alors cela fonctionne très bien. Comme Chris l'a dit aussi, vous ne pouvez pas lancer le programme de désinstallation lorsque l'interface principale est en cours d'exécution, mais ce n'est pas un problème avec mon script car il ajoute un commutateur de ligne de commande pour exécuter le programme de désinstallation sans l'interface utilisateur. .

Mon script évite également de connaître le guid de l'application que vous souhaitez désinstaller. Voici le script à lier à l'action personnalisée (UninstallPriorVersions est la fonction de point d'entrée):

//////////////////////////////////////////////////////////////////////////////// 
    //                    
    // This template script provides the code necessary to build an entry-point 
    // function to be called in an InstallScript custom action. 
    //                    
    //                    
    // File Name: Setup.rul             
    //                    
    // Description: InstallShield script           
    // 
    //////////////////////////////////////////////////////////////////////////////// 

    // Include Ifx.h for built-in InstallScript function prototypes, for Windows 
    // Installer API function prototypes and constants, and to declare code for 
    // the OnBegin and OnEnd events. 
    #include "ifx.h" 

    // The keyword export identifies MyFunction() as an entry-point function. 
    // The argument it accepts must be a handle to the Installer database. 

    export prototype UninstallPriorVersions(HWND); 

    // To Do: Declare global variables, define constants, and prototype user- 
    //   defined and DLL functions here. 

    prototype NUMBER UninstallApplicationByName(STRING); 
    prototype NUMBER GetUninstallCmdLine(STRING, BOOL, BYREF STRING); 
    prototype STRING GetUninstallKey(STRING); 
    prototype NUMBER RegDBGetSubKeyNameContainingValue(NUMBER, STRING, STRING, STRING, BYREF STRING); 

    // To Do: Create a custom action for this entry-point function: 
    // 1. Right-click on "Custom Actions" in the Sequences/Actions view. 
    // 2. Select "Custom Action Wizard" from the context menu. 
    // 3. Proceed through the wizard and give the custom action a unique name. 
    // 4. Select "Run InstallScript code" for the custom action type, and in 
    //  the next panel select "MyFunction" (or the new name of the entry- 
    //  point function) for the source. 
    // 5. Click Next, accepting the default selections until the wizard 
    //  creates the custom action. 
    // 
    // Once you have made a custom action, you must execute it in your setup by 
    // inserting it into a sequence or making it the result of a dialog's 
    // control event. 


    /////////////////////////////////////////////////////////////////////////////// 
    //                   
    // Function: UninstallPriorVersions 
    //                   
    // Purpose: Uninstall prior versions of this application 
    //                   
    /////////////////////////////////////////////////////////////////////////////// 
    function UninstallPriorVersions(hMSI) 
    begin 

     UninstallApplicationByName("The Name Of Some App");   

    end; 


    /////////////////////////////////////////////////////////////////////////////// 
    //                   
    // Function: UninstallApplicationByName 
    //                   
    // Purpose: Uninstall an application (without knowing the guid) 
    //       
    // Returns: (UninstCmdLine is assigned a value by referrence) 
    //  >= ISERR_SUCCESS The function successfully got the command line. 
    //  < ISERR_SUCCESS  The function failed to get the command line. 
    // 
    /////////////////////////////////////////////////////////////////////////////// 
    function NUMBER UninstallApplicationByName(AppName) 
     NUMBER nReturn; 
     STRING UninstCmdLine; 
    begin   

     nReturn = GetUninstallCmdLine(AppName, TRUE, UninstCmdLine); 
     if(nReturn < ISERR_SUCCESS) then 
      return nReturn; 
     endif; 

     if(LaunchAppAndWait("", UninstCmdLine, LAAW_OPTION_WAIT) = 0) then 
      return ISERR_SUCCESS; 
     else 
      return ISERR_SUCCESS-1; 
     endif; 

    end; 


    /////////////////////////////////////////////////////////////////////////////// 
    //                   
    // Function: GetUninstallCmdLine 
    //                   
    // Purpose: Get the command line statement to uninstall an application 
    //       
    // Returns: (UninstCmdLine is assigned a value by referrence) 
    //  >= ISERR_SUCCESS The function successfully got the command line. 
    //  < ISERR_SUCCESS  The function failed to get the command line. 
    // 
    /////////////////////////////////////////////////////////////////////////////// 
    function NUMBER GetUninstallCmdLine(AppName, Silent, UninstCmdLine) 
     NUMBER nReturn; 
    begin   

     nReturn = RegDBGetUninstCmdLine (GetUninstallKey(AppName), UninstCmdLine); 
     if(nReturn < ISERR_SUCCESS) then 
      return nReturn; 
     endif; 

     if(Silent && StrFind(UninstCmdLine, "MsiExec.exe") >= 0)then 
      UninstCmdLine = UninstCmdLine + " /qn"; 
     endif; 

     return nReturn; 
    end; 


    /////////////////////////////////////////////////////////////////////////////// 
    //                   
    // Function: GetUninstallKey 
    //                   
    // Purpose: Find the uninstall key in the registry for an application looked up by name 
    //  
    // Returns: The uninstall key (i.e. the guid or a fall back value) 
    //                  
    /////////////////////////////////////////////////////////////////////////////// 
    function STRING GetUninstallKey(AppName) 
     STRING guid; 
     STRING Key64, Key32, ValueName; 
    begin 

     Key64 = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"; 
     Key32 = "SOFTWARE\\Wow6432Node\\\Microsoft\\Windows\\CurrentVersion\\Uninstall"; 
     ValueName = "DisplayName"; 

     if(RegDBGetSubKeyNameContainingValue(HKEY_LOCAL_MACHINE, Key64, ValueName, AppName, guid) = 0) then 
      return guid; // return 64 bit GUID 
     endif; 

     if(RegDBGetSubKeyNameContainingValue(HKEY_LOCAL_MACHINE, Key32, ValueName, AppName, guid) = 0) then 
      return guid; // return 32 bit GUID 
     endif; 

     return AppName; // return old style uninstall key (fall back value) 

    end; 


    /////////////////////////////////////////////////////////////////////////////// 
    //                   
    // Function: RegDBGetSubKeyNameContainingValue 
    //                   
    // Purpose: Find a registry sub key containing a given value. 
    //   Return the NAME of the subkey (NOT the entire key path) 
    // 
    // Returns: (SubKeyName is assigned a value by referrence) 
    //  = 0  A sub key name was found with a matching value 
    //  != 0 Failed to find a sub key with a matching value 
    //                   
    /////////////////////////////////////////////////////////////////////////////// 
    function NUMBER RegDBGetSubKeyNameContainingValue(nRootKey, Key, ValueName, Value, SubKeyName) 
     STRING SearchSubKey, SubKey, svValue; 
     NUMBER nResult, nType, nvSize; 
     LIST listSubKeys; 
    begin 

     SubKeyName = ""; 

     listSubKeys = ListCreate(STRINGLIST); 
     if (listSubKeys = LIST_NULL) then 
      MessageBox ("Unable to create necessary list.", SEVERE); 
      abort; 
     endif; 

     RegDBSetDefaultRoot(nRootKey); 

     if (RegDBQueryKey(Key, REGDB_KEYS, listSubKeys) = 0) then  
      nResult = ListGetFirstString (listSubKeys, SubKey); 
      while (nResult != END_OF_LIST) 
       SearchSubKey = Key + "\\" + SubKey; 
       nType = REGDB_STRING; 
       if (RegDBGetKeyValueEx (SearchSubKey, ValueName, nType, svValue, nvSize) = 0) then 
        if(svValue = Value) then    
         SubKeyName = SubKey;  
         nResult = END_OF_LIST; 
        endif; 
       endif;  
       if(nResult != END_OF_LIST) then      
        nResult = ListGetNextString (listSubKeys, SubKey); 
       endif; 
      endwhile; 
     endif; 

     ListDestroy (listSubKeys); 

     if (SubKeyName = "") then 
      return 1; 
     else 
      return 0; 
     endif; 

    end;