InstallShield : 实现二次安装时的覆盖安装

实验的原因

偶遇csdnerA君, 需要解决领导给他的打包需求.
需求要求:
* 二次安装时,需要检查是否有旧版安装.
* 如果有旧版安装,提示用户是否继续安装.
* 如果不继续安装,退出安装程序.
* 如果继续安装,实现覆盖安装,不让用户在UI上选择( 修改,修复,卸载).

我在2010年时,经常会用到InstallShield. 现在的打包程序是自己写的, 界面效果好, 安装程序控制灵活.

我开始建议A君自己去写安装程序. 但是A君说领导指定必须用InstallShield来打包, 晕倒啊~

A君在用InstallShield的过程中, 不知道是哪里的细节搞的不对, 不能实现覆盖安装, 也不能完全实现提示用户后的安装控制.

我今天下午和晚上就贡献给他, 为他来做这个实验. 对自己来说, 也是对技术点的复习和提高. 和他讨论的过程中, 也学到了一些他的心得.

实验做完了, 解决了他的问题, 实验结果很完美~

实现点

  1. 覆盖安装(note : InstallShield本来就提供, 需要注意的是 : InstallShield工程的新版本要增加版本号,不能是原来的版本号. 打包用的PE文件版本号需要比已经安装的PE文件版本号高, 而且必须要是改前三位版本号,不能改第四位版本号. 否则不能覆盖安装. e.g. 旧PE版本号 1.0.1.0, 新版本号码不能为1.0.1.9, 可以为 9.0.1.0, 1.9.1.0, 1.0.9.0)
  2. 对已经安装的旧版本进行检测, 通过查InstallShield自带帮助, 翻了好久,发现可以用 MAINTENANCE 来判断
  3. 当用户选择安装时,直接安装. 我采用了跳过 OnMaintUIBefore() 中 SdWelcomeMaint, 直接对 nType 赋值成 REPAIR, 来实现
  4. 当用户选择不安装时, 在 OnFirstUIBefore() 和 OnMaintUIBefore() 处 abort 实现安装程序退出.

总结

  • 感觉自己查资料的能力和解决问题的能力比2010年好很多, 很欣慰~
  • InstallShield的资料, 在网上很少. 自己翻InstallShield的自带Help, 需要翻很久. 权衡后, 还是翻InstallShield自带帮助节省时间, 而且能找到具体的线索和答案.
  • InstallShield能为我们生成每个安装事件的脚本实现, 确实让用户感到贴心.

备注

实验环境 : InstallShield2010

测试工程下载

test-installShield-2015-0516-2232.rar

安装脚本实现

#include "ifx.h"

prototype IsProductWasInstalled(); 
prototype GetUnInstallExePathName(BYREF STRING); 
prototype RunUnInstallExe();

BOOL g_bSetupNow; ///< 如果旧版程序已经安装, 记录用户选择(是否覆盖安装)

function OnBegin()
begin
    // TODO: Perform custom initialization steps, check requirements, etc.
    g_bSetupNow = TRUE;
    if IsProductWasInstalled() then
        if (AskYesNo("已经发现本软件已经被安装!\r\n" + "是否继续安装?", YES) = NO) then 
            g_bSetupNow = FALSE; ///< 用户选择不安装
            // RunUnInstallExe(); 
        endif;
    endif; 
end;                              

function IsProductWasInstalled()
    BOOL bRc;
begin
    if (MAINTENANCE != 0) then
        // 产品已经被安装过
        bRc = TRUE;
    else
        // 产品首次被安装(还没有被安装,本次安装是首次)
        bRc = FALSE;
    endif;                    

    return bRc;
end;      

function GetUnInstallExePathName(strPathName)
    BOOL bRc;
begin                               
    if (RegDBGetItem(REGDB_UNINSTALL_MODIFYPATH, strPathName) < 0) then
        bRc = FALSE;
    else  
        bRc = TRUE;
    endif;

    return bRc;
end;        

function RunUnInstallExe()
    STRING strUnInstallPathName;
begin
/**
        if (GetUnInstallExePathName(strUnInstallPathName)) then
            LaunchAppAndWait(strUnInstallPathName, "", WAIT);
        endif;
        */
        ComponentRemoveAll();
end;
//---------------------------------------------------------------------------                                                                        
// OnFirstUIBefore
//
// First Install UI Sequence - Before Move Data
//
// The OnFirstUIBefore event is called by OnShowUI when the setup is
// running in first install mode. By default this event displays UI allowing
// the end user to specify installation parameters.
//
// Note: This event will not be called automatically in a
// program...endprogram style setup.
//---------------------------------------------------------------------------
function OnFirstUIBefore()
    number  nResult, nLevel, nSize, nSetupType;
    string  szTitle, szMsg, szOpt1, szOpt2, szLicenseFile;
    string  szName, szCompany, szTargetPath, szDir, szFeatures;
    BOOL    bLicenseAccepted;
begin   

    /// 不安装时的处理 : 退出安装程序                                    
    if (g_bSetupNow == FALSE) then
        abort;
    endif;

    // Added in InstallShield 15 - Show an appropriate error message if
    // -removeonly is specified and the product is not installed.
    if( REMOVEONLY ) then
        Disable( DIALOGCACHE );
        szMsg = SdLoadString( IDS_IFX_ERROR_PRODUCT_NOT_INSTALLED_UNINST );
        SdSubstituteProductInfo( szMsg );
        MessageBox( szMsg, SEVERE );
        abort;
    endif;

    nSetupType = COMPLETE;  
    szDir = TARGETDIR;
    szName = "";
    szCompany = "";
    bLicenseAccepted = FALSE;

// Beginning of UI Sequence
Dlg_Start:
    nResult = 0;

Dlg_SdWelcome:
    szTitle = "";
    szMsg = "";
    //{{IS_SCRIPT_TAG(Dlg_SdWelcome)
    nResult = SdWelcome( szTitle, szMsg );
    //}}IS_SCRIPT_TAG(Dlg_SdWelcome)
    if (nResult = BACK) goto Dlg_Start;

Dlg_SdLicense2:
    szTitle = "";
    szOpt1 = "";
    szOpt2 = "";
    //{{IS_SCRIPT_TAG(License_File_Path)
    szLicenseFile = SUPPORTDIR ^ "License.rtf";
    //}}IS_SCRIPT_TAG(License_File_Path)
    //{{IS_SCRIPT_TAG(Dlg_SdLicense2)
    nResult = SdLicense2Ex( szTitle, szOpt1, szOpt2, szLicenseFile, bLicenseAccepted, TRUE );
    //}}IS_SCRIPT_TAG(Dlg_SdLicense2)
    if (nResult = BACK) then
        goto Dlg_SdWelcome;
    else
        bLicenseAccepted = TRUE;
    endif;

Dlg_SdRegisterUser:
    szMsg = "";
    szTitle = "";
    //{{IS_SCRIPT_TAG(Dlg_SdRegisterUser)   
    nResult = SdRegisterUser( szTitle, szMsg, szName, szCompany );
    //}}IS_SCRIPT_TAG(Dlg_SdRegisterUser)
    if (nResult = BACK) goto Dlg_SdLicense2;

Dlg_SetupType2:   
    szTitle = "";
    szMsg = "";
    nResult = CUSTOM;
    //{{IS_SCRIPT_TAG(Dlg_SetupType2)   
    nResult = SetupType2( szTitle, szMsg, "", nSetupType, 0 );
    //}}IS_SCRIPT_TAG(Dlg_SetupType2)
    if (nResult = BACK) then
        goto Dlg_SdRegisterUser;
    else
        nSetupType = nResult;
        if (nSetupType != CUSTOM) then
            szTargetPath = TARGETDIR;
            nSize = 0;
            FeatureCompareSizeRequired( MEDIA, szTargetPath, nSize );
            if (nSize != 0) then      
                MessageBox( szSdStr_NotEnoughSpace, WARNING );
                goto Dlg_SetupType2;
            endif;
        endif;   
    endif;

Dlg_SdAskDestPath2:
    if ((nResult = BACK) && (nSetupType != CUSTOM)) goto Dlg_SetupType2;
    szTitle = "";
    szMsg = "";
    if (nSetupType = CUSTOM) then
                //{{IS_SCRIPT_TAG(Dlg_SdAskDestPath2)   
        nResult = SdAskDestPath2( szTitle, szMsg, szDir );
                //}}IS_SCRIPT_TAG(Dlg_SdAskDestPath2)
        TARGETDIR = szDir;
    endif;
    if (nResult = BACK) goto Dlg_SetupType2;

Dlg_SdFeatureTree: 
    if ((nResult = BACK) && (nSetupType != CUSTOM)) goto Dlg_SdAskDestPath2;
    szTitle = "";
    szMsg = "";
    szFeatures = "";
    nLevel = 2;
    if (nSetupType = CUSTOM) then
        //{{IS_SCRIPT_TAG(Dlg_SdFeatureTree)    
        nResult = SdFeatureTree( szTitle, szMsg, TARGETDIR, szFeatures, nLevel );
        //}}IS_SCRIPT_TAG(Dlg_SdFeatureTree)
        if (nResult = BACK) goto Dlg_SdAskDestPath2;  
    endif;

Dlg_SQLServer:
    nResult = OnSQLServerInitialize( nResult );
    if( nResult = BACK ) goto Dlg_SdFeatureTree;

Dlg_ObjDialogs:
    nResult = ShowObjWizardPages( nResult );
    if (nResult = BACK) goto Dlg_SQLServer;

Dlg_SdStartCopy2:
    szTitle = "";
    szMsg = "";
    //{{IS_SCRIPT_TAG(Dlg_SdStartCopy2) 
    nResult = SdStartCopy2( szTitle, szMsg );   
    //}}IS_SCRIPT_TAG(Dlg_SdStartCopy2)
    if (nResult = BACK) goto Dlg_ObjDialogs;

    // Added in 11.0 - Set appropriate StatusEx static text.
    SetStatusExStaticText( SdLoadString( IDS_IFX_STATUSEX_STATICTEXT_FIRSTUI ) );

    return 0;
end;
//---------------------------------------------------------------------------
// OnMaintUIBefore
//
// Maintenance UI Sequence - Before Move Data
//
// The OnMaintUIBefore event is called by OnShowUI when the setup is
// running in maintenance mode. By default this event displays UI that
// allows the end user to add or remove features, repair currently
// installed features or uninstall the application.
//
// Note: This event will not be called automatically in a
// program...endprogram style setup.
//---------------------------------------------------------------------------
function OnMaintUIBefore()
    number  nResult, nType;
    string  szTitle, szMsg;
begin

    /// 不安装时的处理 : 退出安装程序                          
    if (g_bSetupNow == FALSE) then
        abort;
    endif;

    // nType defaults to MODIFY.
    nType = REPAIR; ///< 只有选修复, 才能实现覆盖安装

    //Initialize SQL
    OnSQLServerInitializeMaint();

// Beginning of UI Sequence
Dlg_Start:

    // Added in Version 9.5 - Support for REMOVEONLY option.
    if( !REMOVEONLY ) then
        // In standard mode show maintenance dialog
        Disable( BACKBUTTON );   
        /// 不让用户选择, 直接进行"修复", 实现覆盖安装
        // nType = SdWelcomeMaint( szTitle, szMsg, nType );
        Enable( BACKBUTTON );
        nResult = NEXT;
    else
        // Hide the initial progress dialog as otherwise the user can
        // click on it, and hide the MessageBox.
        Disable( DIALOGCACHE );

        // In RemoveOnly mode, set to remove.
        nType = REMOVEALL;
    endif;

    // Show Uninstall Confirmation Dialog
    if ( nType = REMOVEALL ) then
        nResult = MessageBox( SdLoadString( IFX_MAINTUI_MSG ), MB_YESNO );
        if (nResult != IDYES ) then

            if( REMOVEONLY ) then
                // In REMOVEONLY mode, abort the setup.
                abort;
            else
                // In non-REMOVEONLY mode, redisplay the previous dialog.
                goto Dlg_Start;
            endif;

        endif;
    endif;

Dlg_SdFeatureTree:
    if ( nType = MODIFY ) then
        szTitle = "";
        szMsg = SdLoadString( SD_STR_COMPONENT_MAINT_MSG );
        nResult = SdFeatureTree( szTitle, szMsg, TARGETDIR, "", -1 );
        if ( nResult = BACK ) goto Dlg_Start;
    endif;

Dlg_ObjDialogs:
    nResult = ShowObjWizardPages( nResult );
    if ( ( nResult = BACK ) && ( nType != MODIFY ) ) goto Dlg_Start;
    if ( ( nResult = BACK ) && ( nType = MODIFY ) ) goto Dlg_SdFeatureTree;

    switch(nType)

        case REMOVEALL:

            // Ensure that all previously installed features are removed.
            FeatureRemoveAllInMediaAndLog();

            // Added in 11.0 - Set appropriate StatusEx static text.
            SetStatusExStaticText( SdLoadString( IDS_IFX_STATUSEX_STATICTEXT_MAINTUI_REMOVEALL ) );

        case REPAIR:

            // Changed for DevStudio 9, Disk1 files are now always updated when installed
            // so when running from ADDREMOVE we need to prevent these files from being
            // updated since this will result in files being updated that are locked by the setup.
            // Updating these files when running from ADDREMOVE should not be needed since updates
            // are not run directly from Add/Remove.
            if( ADDREMOVE ) then
                // Reinstall all previously installed features, except
                // disk1 features.
                FeatureUpdate( "" );
            else
                // Reinstall all previously installed features.
                FeatureReinstall();
            endif;

            // Added in 11.0 - Set appropriate StatusEx static text.
            SetStatusExStaticText( SdLoadString( IDS_IFX_STATUSEX_STATICTEXT_MAINTUI_REPAIR ) );

        case MODIFY:

            // Added in 11.0 - Set appropriate StatusEx static text.
            SetStatusExStaticText( SdLoadString( IDS_IFX_STATUSEX_STATICTEXT_MAINTUI_MODIFY ) );

    endswitch;

end;
//---------------------------------------------------------------------------
// OnMoveData
//
// The OnMoveData event is called by OnShowUI to initiate the file
// transfer of the setup.
//
// Note: This event will not be called automatically in a
// program...endprogram style setup.
//---------------------------------------------------------------------------
function OnMoveData()
number  nResult, nMediaFlags;
begin

    // Don't install the DISK1COMPONENT if MAINT_OPTION_NONE was specified.
    if( MAINT_OPTION = MAINT_OPTION_NONE ) then
        FeatureSelectItem( MEDIA, DISK1COMPONENT, FALSE );
    endif;

    // Updated in 11.5, disable the cancel button during file transfer unless
    // this is non-maintenance mode or repair mode.
    if( MAINTENANCE && ( !REINSTALLMODE || UPDATEMODE ) ) then
        Disable( CANCELBUTTON );
    endif;

    // Show Status
    // Note: Start status window at 1 in case CreateInstallationInfo call
    // is lengthy.
    SetStatusWindow( 1, "" );
    Enable( STATUSEX );
    StatusUpdate( ON, 100 );

    // Create the uninstall infomation (after displaying the progress dialog)
    // Don't create uninstall information if MAINT_OPTION_NONE was specified.
    if( MAINT_OPTION != MAINT_OPTION_NONE ) then
        CreateInstallationInfo();
    endif;

    // Move Data
    nResult = FeatureTransferData( MEDIA );

    // Moved in 11.0, Check for failure before creating uninstall key.
    // Handle move data error and abort if error occured.
    if( nResult < ISERR_SUCCESS ) then
        OnComponentError();
        abort;
    endif;      

    // Create uninstall key, if DISK1COMPONENT was installed.
    if( IFX_DISK1INSTALLED ) then

        // Store text-subs for maintenance mode later, only do this when
        // disk 1 is installed. Note that any text-subs that are updated after
        // this call will not be remembered during maintenance mode.
        FeatureSaveTarget("");

        // Write uninstall information.
        MaintenanceStart();

        // Customize Uninstall Information
        OnCustomizeUninstInfo();

    endif;

    // Disable Status
    Disable( STATUSEX );

end;

你可能感兴趣的:(InstallShield : 实现二次安装时的覆盖安装)