安装时卸载另一个 MSI

发布于 2024-08-24 07:03:05 字数 316 浏览 6 评论 0原文

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

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

有什么想法吗?

I have a Basic MSI project. I need to remove another MSI product on installation that is now integrated into our main application. I tried to use the upgrade scenarios and treat it as a major upgrade. However, this didn't work because of the upgrade codes not matching I believe.

Next, I also made a custom action that ran msiexec.exe after the CostFinalize (I think this was stated in the Installshield help.) This worked perfectly until I installed on a system that didn't have the installer I was looking to remove. My installer would fail if the other obsolete product was not installed. I tried to put a condition on the custom action set by the system search, but it seems the system search is limited in functionality. I can't just check a reg key and set a boolean property.

Any ideas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

那伤。 2024-08-31 07:03:05

需要考虑的一些事项

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

2) 如果内存可用,MSI 将不会在每台机器安装期间删除每用户产品(或反之亦然)。上下文必须相同。

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

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

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

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

A few things to consider

1) The UpgradeTable ( FindRelatedProducts / RemoveExisting Products ) can be used to remove ProductCodes associated with another product's UpgradeCode.

2) If memory serves, MSI won't remove a Per-User product during a Per-Machine install ( or the other way around ). The context has to be the same.

3) The UI Sequence doesn't run during silent installs.

4) You can't run msiexec from the execute sequence because there is a system wide mutex of only one execute sequence per machine.

5) If you schedule in UI ( I already told you that you shouldn't since it doesn't run during silent installs ) there is another mutex that says only 1 UI per process.

If you are going from per-user to per-user or per-machine to per-machine, I would think it's reasonaable you should be able to do what you want using Upgrade elements / table rows without writing custom actions. Otherwise you'll need a setup.exe style bootstrapper to handle the uninstall prior to entering the msiexec world.

梦里泪两行 2024-08-31 07:03:05

我在 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;

I achieved this in InstallShield 2013 using custom InstallScript. The script is executed via a custom action in the UI sequence, but I placed it after the "SetupProgress" dialog, i.e. before "Execute Action" instead of after the CostFinalize (as the documentation does say). I added the condition "NOT Installed" to the action. If you place this in the suggested order, it will kick off the uninstallation as soon as the installer has initialized. If you move it to where I did, it doesn't kick off until the user has clicked the final install now button.

The reason to put this in the UI sequence is to get around the one msi installer (or uninstaller) at a time problem. This simply doesn't work in the execution sequence because of that restriction.

The primary weakness in this method is that as Christopher stated, this won't work in a silent install (that is also found in the IS documentation). That is the official means for achieving this though. (check out: http://helpnet.installshield.com/installshield16helplib/IHelpCustomActionMSIExec.htm) If you can live with that (since silent install is generally as special case scenario), then this works just fine.

As Chris also said, you can't launch the uninstaller ui while the primary ui is running, but that's not a problem with my script because it adds a command line switch to run the uninstaller without the ui (i.e. silently).

My script also avoids having to know the guid of the application you want to uninstall. Here's the script to bind to the custom action (UninstallPriorVersions is the entry point function):

////////////////////////////////////////////////////////////////////////////////
    //                                                                            
    //  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;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文