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;