卸载另一个MSI安装
-
18-09-2019 - |
题
我有一个基本MSI项目。我需要删除另一个MSI产品上安装的是现在集成到我们的主要应用。我试图使用升级方案,并把它作为一个重大升级。然而,这并没有因为升级代码不匹配,我相信的工作。
接下来,我也做了跑msiexec.exe的的CostFinalize后(我认为这是在InstallShield帮助说明。)此,直到我说没有我一直在寻找安装程序安装在系统上工作完全自定义操作去除。如果没有安装其他过时的产品我的安装会失败。我试图把一个条件由系统搜索设置的自定义操作,但似乎该系统搜索功能有限。我不能只检查一个reg键,并设置一个布尔属性。
任何想法?
解决方案
一个几件事考虑
1)UpgradeTable(FindRelatedProducts / RemoveExisting产品)可以使用,以删除与另一个产品的相关联的UpgradeCode ProductCodes。
2)如果记错,MSI不会在除去每用户产品每个机器安装(或反过来)。上下文必须是相同的。
3)的UI序列不期间静默安装运行。
4),因为每台机器只有一个执行序列的系统宽互斥无法从执行顺序运行MSIEXEC。
5)如果在UI计划(我已经告诉过你,你不应该因为它没有在静默安装运行),还有就是每个进程只有1 UI说,另一个互斥。
如果您是从每个用户将每个用户或每台机器以每台机器,我会认为这是reasonaable你应该能够做到你想要使用升级元素/表行,而无需编写自定义操作内容。否则,你就需要一个Setup.exe风格引导程序来处理卸载进入MSIEXEC世界之前。
其他提示
我使用自定义的InstallScript InstallShield中2013实现这一点。脚本通过在UI序列的自定义操作执行,但我把它放在“SetupProgress”对话框,即后CostFinalize前后“执行行动”,而不是(如文档不说了)。我添加条件“未安装”的行动。如果你把这个在该建议订单,但会尽快安装已初始化揭开序幕卸载。如果你把它移动到我在哪里,直到用户点击了最后的安装按钮,现在它不揭开序幕。
把此在UI顺序的原因是在一个时间问题,以绕过一个MSI安装(或卸载)。这只是不因为限制的执行顺序工作。
在该方法的主要缺点是,由于克里斯托弗指出,这不会在无声安装工作(即也IS文档中找到)。这是实现这虽然官方的手段。 (看看: http://helpnet.installshield.com/installshield16helplib/IHelpCustomActionMSIExec.htm )如果你可以忍受的(因为静默安装通常是作为特殊情况),那么这工作得很好。
由于克里斯还表示,主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;