قم بإلغاء تثبيت MSI آخر على التثبيت
-
18-09-2019 - |
سؤال
لدي مشروع أساسي MSI. أحتاج إلى إزالة منتج MSI آخر على التثبيت المدمج الآن في تطبيقنا الرئيسي. حاولت استخدام سيناريوهات الترقية وتعاملها كترقية كبيرة. ومع ذلك، لم ينجح هذا بسبب رموز الترقية لا تتطابق معني.
بعد ذلك، قمت أيضا باتخاذ إجراءات مخصصة قمت بتشغيل MSIEXEC.EXE بعد costfinalize (أعتقد أن هذا قد ذكر في مساعدة InstallShield.) هذا يعمل تماما حتى مثبت على نظام لم يكن لديك المثبت الذي كنت أتطلع إليه. سوف يفشل المثبت الخاص بي إذا لم يتم تثبيت المنتج القديم الآخر. حاولت وضع شرط على الإجراء المخصص الذي حدده البحث عن النظام، ولكن يبدو أن البحث في النظام محدود في الوظيفة. لا يمكنني فقط التحقق من مفتاح REG وقم بتعيين خاصية Boolean.
أيه أفكار؟
المحلول
اشياء قليلة لتأخذ في الاعتبار
1) يمكن استخدام الترقية (المنتجات المستندات / إزالة المنتجات) لإزالة مجموعات المنتجات المرتبطة بترقية منتج آخر.
2) إذا كانت الذاكرة تخدم، فلن تقوم MSI بإزالة منتج لكل مستخدم أثناء تثبيت الجهاز لكل جهاز (أو الطريقة الأخرى). السياق يجب أن يكون هو نفسه.
3) لا يعمل تسلسل UI أثناء التثبيت الصامت.
4) لا يمكنك تشغيل MSIEXEC من تسلسل التنفيذ لأن هناك نظام كبير للنظام من تسلسل تنفيذ واحد فقط لكل جهاز.
5) إذا قمت بالجدول الزمني في واجهة المستخدم (قلت لك بالفعل أنه يجب ألا تدار لأنك لا تدير أثناء التثبيت الصامت)، فهناك موبيوكس آخر يقول فقط 1 واجهة المستخدم لكل عملية.
إذا كنت تذهب من كل مستخدم لكل مستخدم أو لكل جهاز مقابل الجهاز لكل جهاز، فسأعتقد أنه من المقبول، يجب أن تكون قادرا على القيام بما تريد استخدام صفقات الترقية عناصر / طاولة دون كتابة إجراءات مخصصة. وإلا فسوف تحتاج إلى bootstrapper نمط Setup.exe للتعامل مع إلغاء التثبيت قبل إدخال عالم MSIEXEC.
نصائح أخرى
حققت هذا في InstallShield 2013 باستخدام تثبيت مخصص. يتم تنفيذ البرنامج النصي عبر إجراء مخصص في تسلسل UI، لكنني وضعت ذلك بعد مربع الحوار "SetupProgress"، أي قبل "تنفيذ الإجراء" بدلا من بعد coveFinalize (كما يقول الوثائق). أضفت الحالة "غير مثبتة" إلى الإجراء. إذا وضعت هذا في الأمر المقترح، فسوف ينطلق إلغاء التثبيت بمجرد تهيئة المثبت. إذا قمت بنقل الأمر إلى المكان الذي فعلته، فلا يجب أن ينطلق حتى يتم النقر على المستخدم الآن على زر التثبيت النهائي الآن.
السبب في وضع هذا في تسلسل UI هو الحصول على One MSI Installer (أو إلغاء التثبيت) في مشكلة في الوقت المناسب. هذا ببساطة لا يعمل في تسلسل التنفيذ بسبب هذا القيد.
الضعف الأساسي في هذه الطريقة هو أنه كما ذكر كريستوفر، فلن يعمل هذا في تثبيت صامت (موجود أيضا في الوثائق هو أيضا). هذه هي الوسائل الرسمية لتحقيق ذلك. (الدفع: http://helpnet.installshield.com/installshield16helplplib/ihelpcustomacymsiexec.htm.) إذا كنت تستطيع العيش مع ذلك (نظرا لأن التثبيت الصامت هو عموما كسيناريو خاصا خاصا)، فهذا يعمل بشكل جيد فقط.
كما قال كريس أيضا، لا يمكنك تشغيل UNSTALLER UI أثناء تشغيل UI الأساسي، ولكن هذه ليست مشكلة في البرنامج النصي لأنه يضيف مفتاح سطر الأوامر لتشغيل إلغاء التثبيت بدون UI (أي بصمت).
يتجنب النصي أيضا الاضطرار إلى معرفة GUID للتطبيق الذي تريد إلغاء تثبيته. إليك البرنامج النصي لربط الإجراء المخصص (إلغاء التثبيت هو وظيفة نقطة الدخول):
////////////////////////////////////////////////////////////////////////////////
//
// 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;