【问题标题】:Uninstall another MSI on install安装时卸载另一个 MSI
【发布时间】:2023-03-24 11:28:01
【问题描述】:

我有一个基本的 MSI 项目。我需要在安装时删除另一个已集成到我们主应用程序中的 MSI 产品。我尝试使用升级场景并将其视为重大升级。但是,这不起作用,因为我认为升级代码不匹配。

接下来,我还做了一个自定义操作,在 CostFinalize 之后运行 msiexec.exe(我认为这在 Installshield 帮助中有所说明。)直到我安装在没有我正在寻找的安装程序的系统上之前,它一直运行良好去除。如果未安装其他过时产品,我的安装程序将失败。我试图对系统搜索设置的自定义操作设置条件,但系统搜索似乎在功能上受到限制。我不能只检查一个 reg 键并设置一个布尔属性。

有什么想法吗?

【问题讨论】:

    标签: installation windows-installer installshield installshield-2010


    【解决方案1】:

    我在 InstallShield 2013 中使用自定义 InstallScript 实现了这一点。该脚本是通过 UI 序列中的自定义操作执行的,但我将其放在“SetupProgress”对话框之后,即在“执行操作”之前而不是在 CostFinalize 之后(如文档所述)。我在操作中添加了条件“未安装”。如果您按照建议的顺序放置它,它将在安装程序初始化后立即开始卸载。如果您将其移至我所做的位置,则在用户单击最终的立即安装按钮之前它不会启动。

    将其置于 UI 序列中的原因是为了解决一次只有一个 msi 安装程序(或卸载程序)的问题。由于该限制,这在执行顺序中根本不起作用。

    这种方法的主要弱点是,正如 Christopher 所说,这在静默安装中不起作用(也可以在 IS 文档中找到)。这是实现这一目标的官方手段。 (查看:http://helpnet.installshield.com/installshield16helplib/IHelpCustomActionMSIExec.htm)如果您可以忍受(因为静默安装通常是特殊情况),那么这很好用。

    正如 Chris 所说,您无法在主 ui 运行时启动卸载程序 ui,但这不是我的脚本的问题,因为它添加了一个命令行开关来在没有 ui 的情况下运行卸载程序(即静默)。

    我的脚本还避免了必须知道要卸载的应用程序的 guid。这是绑定到自定义操作的脚本(UninstallPriorVersions 是入口点函数):

    ////////////////////////////////////////////////////////////////////////////////
        //                                                                            
        //  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;
    

    【讨论】:

      【解决方案2】:

      需要考虑的一些事项

      1) UpgradeTable ( FindRelatedProducts / RemoveExisting Products ) 可用于删除与另一个产品的 UpgradeCode 关联的 ProductCodes。

      2) 如果记忆犹新,MSI 不会在每台机器安装期间删除每用户产品(或相反)。上下文必须相同。

      3) UI 序列在静默安装期间不运行。

      4) 您不能从执行序列中运行 msiexec,因为系统范围的互斥体中每台机器只有一个执行序列。

      5) 如果您在 UI 中安排(我已经告诉过您不应该这样做,因为它不会在静默安装期间运行)还有另一个互斥锁,它说每个进程只有 1 个 UI。

      如果您要从每用户到每用户或每台机器到每台机器,我认为您应该能够使用升级元素/表格行来做您想做的事情,而无需编写自定义操作。否则,在进入 msiexec 世界之前,您需要一个 setup.exe 风格的引导程序来处理卸载。

      【讨论】:

      • 感谢您的信息。按照您的建议,我能够使用升级元素完成我需要的工作。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多