一个完整的安装程序实例 --高级设置(转)
来源:http://www.cnblogs.com/Cindy_weiwei/archive/2009/05/19/1460232.html
在开始进行编程前,我们先明确一下我们要用编程来弥补前面设置的哪些功能的不足
1. 显示软件许可协议
2. 判断是否安装了本软件所需要的先决软件JKD1.6.0_04,如无,则启动外部安装程序进行安装(同样原理可以用来判断是否安装了其他软件,只要该软件在注册表中有键值)
3. 用户的输入信息、所选安装路径、所选安装组件将显示在安装界面上(Installshield虽然自带了此界面,但是默认是显示为空的,需要写脚本来显示信息)
4. 根据用户选择的组件,从外部文件夹拷贝相应的文件到安装目标路径的文件夹中
5. 根据从外部拷贝进来的文件,创建快捷方式(这里主要是拷贝文档,并在开始菜单中创建快捷方式)
6. 在安装结束时,显示readme.txt文件
7. 在安装结束后,启动指定的程序
8. 完美卸载
脚本编程这部分都将在Installer Designer这个界面进行。后面不再赘述。
Installshield大小写敏感,因此请严格按照示例上所写的大小写规则来书写。例:字符串变量STRING和string都支持,但是String不支持。
1. 添加许可协议文本
在左边导航树上找到Behavior and Logic | Support Files/Billboards选项。这个选项允许用户添加一些在安装过程中需要用到的文件。
中间的导航栏会显示对应的选项
在Support Files分支下,会显示一个Language Independent和所有你所选择的语言类型。 Language Independent意为,如果你在这里分支下做了设置,那么无论选择用何种语言安装,这个设置都会生效;而各个语言类型意为,如果你在某语言下做了设置,那么这个设置只有在选择了用这种语言安装的时候才会生效。
点击Language Independent,这次我们将在这个分支下进行试验。
在右边的Files栏中右键点击,在弹出菜单上选择Insert Files选项。
选择事先撰写好的许可协议的文本文件,插入到Files栏中。
许可协议允许两种文本格式:txt和rtf格式,此处我们采用 txt格式。
2. 然后切换到Behavior and Logic | InstallScript选项,
3. 中间的导航栏Files下有一个默认的Rul文件Setup.Rul,我们这个工程的全部installscript代码都将写在这个默认文件里
4. 点击选中Setup.Rul节点,右边会显示该文件的可编程面板。
5. 许可协议应该在一开始运行安装程序的时候就显示,也就是在拷贝数据前。请在第一个下拉框中选择Before Move Data选项,然后在第二个下拉框中选择OnBegin选项(不要因为默认显示的是这两个选项,而不做这个打开下拉列表进行选择的动作,否则软件检测不到你选择了选项,无法自动添加代码),则编程界面上会自动添加一些代码如下图所示。当然,如果你手动敲代码上去也是可以的。
6. 我们将在function OnBegin()的函数体里面写代码来显示刚才添加的许可协议文本的内容,直接把下面的代码拷贝到OnBegin()函数的begin和end;之间就可以了
Disable (BACKBUTTON);
if(!MAINTENANCE)then
SdLicense2 ("License ", "", "", SUPPORTDIR ^ "2.txt", FALSE);
endif;.
7. 代码解释
************************************************************************
Disable (BACKBUTTON);
将“上一步”按键设置为不可用。安装程序在一开始的时候会有一个默认的开始界面,第二步才显示许可协议,一般来说没必要回退回去看这个什么都没有的开始界面,因此将回退按键设置为不可用
************************************************************************
if(!MAINTENANCE)then
endif;
这一个条件用来判断安装程序处于何种状态,安装、修复、重新安装或卸载状态,后三者都属于MAINTENANCE状态,因此判断只有在正常安装的状态才显示许可协议
************************************************************************
SdLicense2 ("License ", "", "", SUPPORTDIR ^ "2.txt", FALSE);
这个函数用于在界面上显示所用的许可协议。Help里对该函数的构造函数如下
SdLicense2 ( szTitle, szOpt1, szOpt2, szLicenseFile, bLicenseAccepted );
参数一:szTitle,显示在界面左上角的标题,如果填写空字符串””,则显示为默认值”License Agreement”。
参数二:szOpt1,我们常见许可协议界面上会有两个选项,一个是“同意”,一个是“不同意”,szOpt1和szOpt2就是这两个选项,如果填写空字符串,则会显示为默认值"I accept the terms of the license agreement"和"I do not accept the terms of the license agreement"。
参数三:szOpt2,见参数二的说明
参数四:szLicenseFile,指定需要显示的文档,包含路径和带扩展名的文档名。我们刚才把许可协议文本放在supportfile选项下了,这个路径在Installshield里有专门的静态变量来指明,即SUPPORTDIR,然后再添加上带扩展名的文档名,这里是2.txt。静态变量路径和引号引起来的路径之间用^符号来连接。
参数四:bLicenseAccepted,布尔型变量,TRUE状态,则在许可协议界面上默认选中的是那个“同意”的选项;不过好像一般更常见的是默认选中为“不同意”的选项,因此这里可以填入FALSE。
这是许可协议的界面。当用户选择了I accept the terms of the license agreement这个选项后,Next按键可用,安装程序可以继续。(请忽略这里显示的许可协议内容…网上有很多软件许可协议的范本供下载...)
小结:至此,许可协议就添加完毕,在安装执行的时候,用户就可以看到许可协议显示在界面上,并且只有选择了“同意”选项后,安装程序才会往下执行。
显示许可协议的函数一共有三个SdLicense,SdLicenseRtf和SdLicense2,参数略有不同,显示的界面也略有不同,用户可以根据喜好来选择。目前我常用的就是SdLicense2这个函数,显示的界面符合大多数目前流行的安装界面的习惯。
1. 代码还是在OnBegin()函数体内实现,直接把下面的代码拷贝到OnBegin()函数的begin和end;之间就可以了
RegDBSetDefaultRoot(HKEY_LOCAL_MACHINE);
if (RegDBKeyExist ("SOFTWARE\\JavaSoft\\Java Development Kit\\1.6.0_04") < 0) then
LaunchAppAndWait (SRCDISK^"jdk\\jdk-6u4-windows-i586-p.exe","", LAAW_OPTION_WAIT);
endif;
2. 代码解释
************************************************************************
RegDBSetDefaultRoot(HKEY_LOCAL_MACHINE);
设置一下默认的注册表键值根节点为HKEY_LOCAL_MACHINE。
打开注册表可以看到“我的电脑”下的根节点有HKEY_CLASSES_ROOT, HKEY_CURRENT_USER,HKEY_LOCAL_MACHINE等。我们这次要寻找的JDK软件的注册表键值在HKEY_LOCAL_MACHINE下,因此要把根键设置为HKEY_LOCAL_MACHINE。
表告诉我你不知道怎么看注册表,开始-〉运行-〉输入命令regedit
***********************************************************************
RegDBKeyExist ("SOFTWARE\\JavaSoft\\Java Development Kit\\1.6.0_04") < 0)
判断是否存在键值SOFTWARE\\JavaSoft\\Java Development Kit\\1.6.0_04,这个是JDK1.6.0_04安装时向注册表写入的值;
RegDBKeyExist( szSubKey );如果存在键值则返回1,否则返回小于0的随机数字。
***********************************************************************
LaunchAppAndWait (SRCDISK^"jdk\\jdk-6u4-windows-i586-p.exe","", LAAW_OPTION_WAIT);
当上面判断了没有安装JDK1.6.0_04这个软件时,则启动光盘里jdk文件夹下的jdk-6u4-windows-i586-p.exe安装程序来安装。
这个函数在help里是这样叙述的:
LaunchAppAndWait ( szProgram, szCmdLine, nOptions );
参数一:szProgram,即要启动的程序。这里我们写入的参数是SRCDISK^"jdk\\jdk-6u4-windows-i586-p.exe", SRCDISK指源盘,安装程序所在的盘,光盘和硬盘都可以。"jdk\\jdk-6u4-windows-i586-p.exe"源盘下jdk文件夹下的jdk-6u4-windows-i586-p.exe安装程序。
参数二:szCmdLine,如果要启动的程序需要从命令行读入参数来启动,那么在这里写入对应的参数值;我们这里不需要,因此输入空字符串””。
参数三:nOptions,静态变量,不同的静态变量会得到不同的执行结果,比如无等待安装,静默安装,鼠标外形改变等等。详情请参阅Installshield自带的Help。这里我们用LAAW_OPTION_WAIT,即当JDK安装结束后(无论是正常安装了,还是用户点击取消了安装),安装程序才往下继续。
这里可以看到,当点击了同意许可协议的时候,安装程序会自动检测是否安装了JDK,如果没有安装,则弹出安装界面。
这里在函数体里面,没有对找不到JDK安装程序,以及安装出错等情况做判断。如果用户有需要,可以添加一个消息框,提示在找不到安装程序或者安装出错的情况下,用户可以手动地安装需要的软件。代码可以改写为
RegDBSetDefaultRoot(HKEY_LOCAL_MACHINE);
if (RegDBKeyExist ("SOFTWARE\\JavaSoft\\Java Development Kit\\1.6.0_04") < 0) then
if(LaunchAppAndWait (SRCDISK^"jdk\\jdk-6u4-windows-i586-p.exe","", LAAW_OPTION_WAIT)<0)then
MessageBox ("You haven't installed JDK 1.6.0_04 yet! ", INFORMATION);
endif;
endif;
小结:至此,判断运行所需软件的功能结束,用户可以自己试验一下判断多个软件。用法就是重复上述代码功能,仍在OnBegin()函数体内执行。
Installshield是自带这个界面的,在安装过程中用户可以看到这个界面,但是这个界面上的信息是空的,这一点让人很是疑惑,怀疑是Installshield的bug。因此我们不得不手动地实现这个功能。
1. 这个功能需要在OnFirstUIBefore()函数体中实现,选择Before Move Data | OnFirstUIBefore选项
2. 选择了这个选项后,软件会自动在编程界面生成大量代码,如图所示,这里的每一个Dlg_SdXXXX都对应着一个界面,例如Dlg_SdWelcome就是对应着最初开始的欢迎界面。如果开发人员对这些很熟悉,可以在这里对每一个界面编程设置。
3. 找到Dlg_SdStartCopy这个界面选项,我们将在这里对已有的代码进行改动,使之显示用户输入的用户信息、所选安装路径和组件等信息
4. 首先定义所需变量。
在begin前定义6个feature的名字和两个NUMBER类型的变量,即蓝色字串。之前在第一部分我们定义了6个可用的feature,这里就要对这6个feature进行一些判断。
在begin字样后对这6个feature赋值,所赋的值就是我们在第一部分定义的feature的名字(Name, not Display Name)。
//---------------------------------------------------------------------------
function OnFirstUIBefore()
NUMBER nResult, nSetupType, nvSize, nUser;
STRING szTitle, szMsg, szQuestion, svName, svCompany, szFile;
STRING szLicenseFile;
LIST list, listStartCopy;
BOOL bCustom;
STRING szFeatureName1;
STRING szFeatureName2;
STRING szFeatureName3;
STRING szFeatureName4;
STRING szFeatureName5;
STRING szFeatureName6;
NUMBER bvOpt1,bvOpt2;
begin
// TO DO: if you want to enable background, window title, and caption bar title
// SetTitle( @PRODUCT_NAME, 24, WHITE );
// SetTitle( @PRODUCT_NAME, 0, BACKGROUNDCAPTION );
// Enable( FULLWINDOWMODE );
// Enable( BACKGROUND );
// SetColor(BACKGROUND,RGB (0, 128, 128));
szFeatureName1 ="Server";
szFeatureName2 ="Client";
szFeatureName3 ="Watch_Portion";
szFeatureName4 ="Log_Portion";
szFeatureName5 ="Report_Portion";
szFeatureName6 ="Document";
5. 在Dlg_SdStartCopy的listStartCopy = ListCreate( STRINGLIST ); 和ListDestroy(listStartCopy);之间加入如下代码。
ListAddString(listStartCopy,"Customer Information:",AFTER);
ListAddString(listStartCopy,"User Name: " + svName,AFTER);
ListAddString(listStartCopy,"Company Name: " + svCompany,AFTER);
ListAddString(listStartCopy,"Destination Location: " + INSTALLDIR,AFTER);
switch (nSetupType)
case TYPICAL : ListAddString(listStartCopy,"Setup Type: Typical",AFTER);
case COMPACT: ListAddString(listStartCopy,"Setup Type: Compact",AFTER);
case CUSTOM: ListAddString(listStartCopy,"Setup Type: Custom",AFTER);
endswitch;
ListAddString(listStartCopy," ",AFTER);
ListAddString(listStartCopy,"The Selected Feature:",AFTER);
if (FeatureIsItemSelected(MEDIA, szFeatureName1)=1) then
ListAddString(listStartCopy," "+szFeatureName1,AFTER);
endif;
if (FeatureIsItemSelected(MEDIA, szFeatureName2)=1) then
ListAddString(listStartCopy," "+szFeatureName2,AFTER);
endif;
if (FeatureIsItemSelected(MEDIA, szFeatureName3)=1) then
ListAddString(listStartCopy," "+szFeatureName3,AFTER);
endif;
if (FeatureIsItemSelected(MEDIA, szFeatureName4)=1) then
ListAddString(listStartCopy," "+szFeatureName4,AFTER);
endif;
if (FeatureIsItemSelected(MEDIA, szFeatureName5)=1) then
ListAddString(listStartCopy," "+szFeatureName5,AFTER);
endif;
if (FeatureIsItemSelected(MEDIA, szFeatureName6)=1) then
ListAddString(listStartCopy," "+szFeatureName6,AFTER);
endif;
6. 代码解释
*************************************************************
ListAddString(listStartCopy,"XXXXXX",AFTER);
把要显示的信息添加到list里去,这个list的内容稍后会添加到界面上进行显示。
Help里对这个函数是这样描述的:ListAddString ( listID, szString, nPlacementFlag );
参数一:listID,需要用户事先创建一个list,这里我们看到listStartCopy = ListCreate( STRINGLIST );这句话,即创建了一个叫listStartCopy的list
参数二:szString,要添加的字符串
参数三:nPlacementFlag,如果设置为AFTER,则顺序添加;如果为BEFORE,则逆序添加,即新添加的内容会放在前面显示。
*************************************************************
switch (nSetupType)
case TYPICAL : ListAddString(listStartCopy,"Setup Type: Typical",AFTER);
case COMPACT: ListAddString(listStartCopy,"Setup Type: Compact",AFTER);
case CUSTOM: ListAddString(listStartCopy,"Setup Type: Custom",AFTER);
endswitch;
这是根据用户选择的安装类型来显示安装类型信息。安装类型分三种:TYPICAL,COMPACT和CUSTOM。
*************************************************************
if (FeatureIsItemSelected(MEDIA, szFeatureName1)=1) then
ListAddString(listStartCopy," "+szFeatureName1,AFTER);
endif;
这里的FeatureIsItemSelected(MEDIA, szFeatureName1)=1是一个很重要的函数,将会在本安装程序内反复出现多次。这个函数用于判断用户是否选择了某feature。Help里对这个函数是这样描述的:FeatureIsItemSelected ( szFeatureSource, szFeature );
参数一:szFeatureSource,大意好像是feature的来源,具体不是很明白到底指什么,反正help自带的例子里写的MEDIA照抄没有错。
参数二:szFeatureName1,就是 feature的名字了
如果用户选择了这个feature,返回值就为1,往list里添加一个关于该feature的相关信息即可。
如此反复,判断所有的feature是否被选择,如被选择则添加一个相关信息即可。
这个就是显示了用户信息,安装路径和安装组件的信息。如果没有添加上述代码,这个界面默认是显示的,但是信息栏里是空白的。
顺便说一句,以前在制作这个安装程序的时候,因为这块显示是空白的,那时候对编程也是一窍不通的,情急之下,笔者把这个显示设置的框框设置了不可见。设置方法如下:
找到User Interface | Dialogs
在中间的导航树上找到SdStartCopy这个选项
这里我们使用的是英文界面,因此点击选中English选项
选中这个界面上的将会显示用户信息的框,把右边的Visible选项设置为False即可
小结:在Dlg_SdStartCopy界面里,用户还可以设置左上角显示的标题和消息,szTitle = ""; szMsg = "";这两行代码如果赋值为空,则显示如图所示的默认信息,用户可以赋值成自己想要显示的信息。
这个用途常见于配置文件和授权文件的应用,同一程序,授权给不同的用户,只需要不同的配置和授权文件。如果将配置和授权文件每次都打包在安装程序里,那么变更一个用户就需要重新打包一次,这是一个浪费时间和精力的行为。如果将授权和配置文件(当然内容是加密过的)放在外部文件夹中,每次安装的时候从这个文件夹中读取拷贝,那么会是一个比较通用型的安装程序。
另外,本程序的好几个feature用到了相同的库,如果直接在feature下加库文件也可以,但是每一个feature都加一次这个库文件夹,整个安装程序就会变得很庞大,因此比较理想的情况是选到了这个feature的时候从外部拷贝这些库文件。
这里我们先不包括文档这个feature的说明,文档feature另有详细说明。
1. 这个功能需要在OnFirstUIAfter()函数体中实现,选择After Move Data | OnFirstUIAfter选项,即在选择了移动哪些数据后这个操作生效。
2. 之前我们已经接触过了如何判断是否选择了某个Feature,这里也需要判断是否选择了某个Feature,并且根据这个Feature来拷贝对应的外部文件
首先定义一些需要的变量并且进行赋值,蓝色字体即为所定义变量和赋值语句
function OnFirstUIAfter()
//feature name
STRING szFeatureName1;
STRING szFeatureName2;
STRING szFeatureName3;
STRING szFeatureName4;
STRING szFeatureName5;
STRING szSrcFile1;
STRING szSrcFile2;
STRING szTarFolder1;
STRING szTarFolder2;
NUMBER nResult;
STRING szTitle, szMsg1, szMsg2, szOption1, szOption2;
NUMBER bOpt1, bOpt2;
begin
//feature 定义
szFeatureName1 ="Server";
szFeatureName2 ="Client";
szFeatureName3 ="Watch_Portion";
szFeatureName4 ="Log_Portion";
szFeatureName5 ="Report_Portion";
//需要拷贝的源文件
szSrcFile1 = "Test\\lib\\*.*";
szSrcFile2 = "Test\\databaselib\\*.*";
//拷贝的目的地,目标文件夹
szTarFolder1 = "lib\\*.*";
szTarFolder2 = "databaselib\\*.*";
3. 对每一个feature进行判断,进行相应的文件拷贝
在OnFirstUIAfter()的begin和end之间添加如下代码:
//copy the lib to the target ,copy the necessary file to the target
if (FeatureIsItemSelected(MEDIA, szFeatureName1)=1) then
CopyFile(SRCDISK^szSrcFile1, TARGETDIR^szTarFolder1);
CopyFile(SRCDISK^"Test\\configure\\title.gif", TARGETDIR^"Server\\ title.gif");
CopyFile(SRCDISK^"Test\\configure\\background.gif", TARGETDIR^" Server \\ background.gif");
CopyFile(SRCDISK^"Test\\configure\\configure.dat", TARGETDIR^" Server \\configure.dat ");
endif;
if (FeatureIsItemSelected(MEDIA, szFeatureName2)=1) then
CopyFile(SRCDISK^szSrcFile1, TARGETDIR^szTarFolder1);
CopyFile(SRCDISK^"Test\\configure\\configure.dat", TARGETDIR^"Client\\configure.dat ");
CopyFile(SRCDISK^"Test\\configure\\license.dat", TARGETDIR^" Client \\license.dat");
endif;
if (FeatureIsItemSelected(MEDIA, szFeatureName3)=1) then
CopyFile(SRCDISK^szSrcFile1, TARGETDIR^szTarFolder1);
CopyFile(SRCDISK^"Test\\configure\\configure", TARGETDIR^" Watch Portion \\configure");
CopyFile(SRCDISK^"Test\\configure\\license.dat", TARGETDIR^" Watch Portion \\license.dat");
endif;
if (FeatureIsItemSelected(MEDIA, szFeatureName4)=1) then
CopyFile(SRCDISK^szSrcFile1, TARGETDIR^szTarFolder1);
endif;
if (FeatureIsItemSelected(MEDIA, szFeatureName5)=1) then
CopyFile(SRCDISK^szSrcFile1, TARGETDIR^szTarFolder1);
endif;
4. 代码解释
if (FeatureIsItemSelected(MEDIA, szFeatureName1)=1) then
CopyFile(SRCDISK^szSrcFile1, TARGETDIR^szTarFolder1);
CopyFile(SRCDISK^"Test\\configure\\title.gif", TARGETDIR^"Server\\ title.gif");
CopyFile(SRCDISK^"Test\\configure\\background.gif", TARGETDIR^" Server \\ background.gif");
CopyFile(SRCDISK^"Test\\configure\\configure.dat", TARGETDIR^" Server \\configure.dat ");
endif;
**************************************************************************************
FeatureIsItemSelected(MEDIA, szFeatureName1) 这个函数用于判断用户是否选择了某feature。Help里对这个函数是这样描述的:FeatureIsItemSelected ( szFeatureSource, szFeature );
参数一:szFeatureSource,大意好像是feature的来源,具体不是很明白到底指什么,反正help自带的例子里写的MEDIA照抄没有错。
参数二:szFeatureName1,就是 feature的名字了
如果返回值为1,则说明用户选择了这个feature
**************************************************************************************
CopyFile(SRCDISK^szSrcFile1, TARGETDIR^szTarFolder1);
拷贝文件的函数。Help里是这样描述的:CopyFile ( szSrcFile, szTargetFile );
参数一:szSrcFile,源文件,可带路径,要带有扩展名的文件名。当这个文件带路径时,则从这个指定路径下拷贝指定的文件;如果是不带路径的,则直接从安装文件所在盘的盘符下寻找指定的文件来进行拷贝。如果要拷贝某个文件夹下的一系列文件,可以使用通配符。
参数二:目标文件,可带路径,要带有扩展名的文件名。当这个文件带路径时,则将文件拷贝到这个指定路径下;如果是不带路径的,则将文件拷贝到安装路径下。支持通配符。
小结:上面这段代码的意思是:如果用户选择了某个feature,则从安装程序所在的盘下面的一些文件夹下拷贝文件到目标路径下的一些对应文件夹下。这里记住拷贝文件一定要带上文件的全名,包括扩展名。
1. 这个功能仍然在After Move Data | OnFirstUIAfter()的函数里实现
先定义一些变量并赋值,蓝色字体
function OnFirstUIAfter()
//feature name
STRING szFeatureName6;//feature名
STRING szSrcFile3; //需要拷贝的源文件
STRING szTarFolder3; //拷贝的目的地,带文件名
STRING szTarFolder4; //拷贝的目标文件夹,后面有一个函数要用到不带文件名的目标路径
STRING szDocFile, szDocFileName;// szDocFile,查找函数返回的查询得到文件名;szDocFileName,要查找的文件名
NUMBER nResult; //数字型变量,存放函数的返回结果
begin
//feature 定义
szFeatureName6 ="Document";
//需要拷贝的源文件
szSrcFile3 = "Docs\\*.*";
//拷贝的目的地,目标文件夹
szTarFolder3 = TARGETDIR^"Docs\\*.*";
szTarFolder4 = TARGETDIR^"Docs";//文档的存放路径,不带文件名
2. 仍然在begin和end之间的函数体内把下面的代码拷贝进去即可
if (FeatureIsItemSelected(MEDIA, szFeatureName6)=1) then //如果选择了此feature
if(CopyFile(SRCDISK^szSrcFile3, szTarFolder3)=0) then //那么把要拷贝的文件拷贝过去
nResult = FindAllFiles(TARGETDIR^"Docs", "*.pdf", szDocFile, RESET); //对拷贝过去的文件进行查找,该函数会在第一个符合条件//的文件处停止
while (nResult = 0)
LongPathToQuote(szDocFile, TRUE );
ParsePath (szDocFileName, szDocFile, FILENAME_ONLY);//对查找到的文件获取文件名
AddFolderIcon(FOLDER_PROGRAMS^"Test\\Docs",szDocFileName, szDocFile, "", TARGETDIR^"Docs\\icons\\help.ico" , 0 ,"" , REPLACE ); //为该文件创建快捷方式,快捷方式的显示名就是刚才获取的文件名
nResult = FindAllFiles(TARGETDIR^"Docs", "*.pdf", szDocFile, CONTINUE);//从上一个查找的位置继续向下查找,进行循环
endwhile;
endif;
endif;
3. 代码解释
***************************************************************************************
if (FeatureIsItemSelected(MEDIA, szFeatureName6)=1) then
endif;
如果用户选择了文档feature,则进行一些相应操作
***************************************************************************************
if(CopyFile(SRCDISK^szSrcFile3, szTarFolder3)=0) then
endif;
这里执行了两步操作:
第一步,从源盘的Docs文件夹下把所有文件都拷贝安装路径的Docs文件夹下,注意在定义变量的时候使用了通配符
第二步,如果拷贝成功,则返回值为0,那么进行下一步相应操作
**************************************************************************************
nResult = FindAllFiles(TARGETDIR^"Docs", "*.pdf", szDocFile, RESET);
查找目标文件夹下所有后缀名为pdf的文件,从文件夹的开始位置进行查找,查找成功则返回0。
这个函数在这里有一个巧妙的应用,因为这个函数会在查找到第一个符合条件的文件时就会停止继续向下查找,因此利用静态变量的传值不同,来实现对文件夹的全部查找。
Help里的解释如下:
FindAllFiles ( szDir, szFileName, svResult, nOp );
参数一:szDir,被查找的文件夹
参数二:szFileName,需要查找的文件的名字,支持通配符,例如*.*,*.pdf,*.doc
参数三:svResult,函数会在查找到第一个符合条件的文件时停止,返回这个符合条件的文件的文件名,带全路径和含扩展名的文件名
参数四:nOp, 静态变量。CONTINUE,从上一次查找的位置开始查找,这个特性我们呆会儿会用到;RESET,从文件夹的开始位置进行查找;CANCEL,释放被上一次的FindAllFiles查找的函数。在Windows NT系统下,需要在安装过程中使用带CANCEL的FindAllFiles来释放之前的查找,确保安装的正确性(因此我怀疑查找有bug,这个函数用来弥补这个bug…)。
**************************************************************************************
LongPathToQuote(szDocFile, TRUE );
szDocFile为上一个函数查找到的第一个符合条件的文件名,带完整路径,这个LongPathToQuote函数加上这个文件名上的括号;否则下面一个函数无法解析不带括号的长文件名。
Help里的解释如下:
LongPathToQuote ( svPath, nParameter );
参数一:svPath,长文件名
参数二:nParameter,静态变量。 TRUE,为长文件名加上括号;FALSE,为长文件名脱去括号。
**************************************************************************************
ParsePath (szDocFileName, szDocFile, FILENAME_ONLY);
解析带路径的长文件名,返回文件本身的文件名
Help里的解释如下:
ParsePath ( svReturnString, szPath, nOperation );。
参数一:svReturnString为返回的解析过的文件名,
参数二:szPath,即被解析的长文件名
参数三:nOperation,静态变量,指定用何种方式来解析。这里使用FILENAME_ONLY,也就说返回值为不带路径、不包含扩展名的文件名。这个文件名被下面一步用作显示的快捷方式的名称。
**************************************************************************************
AddFolderIcon(FOLDER_PROGRAMS^"Test\\Docs",szDocFileName, szDocFile, "", TARGETDIR^"Docs\\icons\\help.ico" , 0 ,"" , REPLACE );
创建一个快捷方式,使用指定的图标。
Help里的解释如下:
AddFolderIcon ( szProgramFolder, szItemName, szCommandLine, szWorkingDir, szIconPath, nIcon, szShortCutKey, nFlag );
参数一:szProgramFolder, 要创建的快捷方式所在的文件夹。这里FOLDER_PROGRAMS指开始 | 所有程序,因此我们的快捷方式将会出现在开始 | 所有程序 | Test的Docs下;如果要添加到桌面上,可以设置为FOLDER_DESKTOP;FOLDER_STARTUP 指添加为启动项;FOLDER_STARTMENU添加到开始菜单下。
参数二:szItemName,help里解释很晦涩,解释为要添加到文件夹下的图标的名称,即出现的图标旁边的那个字符串。其实就是我们常说的快捷方式的名称。这里填写被解析出来的那个不带路径也不带扩展名的文件名。
参数三:szCommandLine,全限定路径的文件名或文件夹名,可包含命令行参数。这里传入刚才查找到的文件名,包含路径、文件名和扩展名。读者可能注意到这个参数被做了一些预处理,这个处理也是折腾了几次才搞出来的,不同的操作系统默认路径也是有是否带引号的差别的,这里需要显式地指定一下,以免在不同操作系统上运行时引起不同的结果。
参数四:szWorkingDir,工作目录。Help里的解释如下:设置这个目录为你的应用程序文件所在的地方;要设置包含了应用程序的目录为工作目录,则可传一个空字符串给这个参数。这个参数一开始我并未理解其含义,不过传空字符串也没有出错;在后来经理提出新要求:允许用户自行选择是否在桌面上创建快捷方式时无意中明白这个参数的含义;请读者随便寻找一个自己计算机上的任意位置的快捷方式,右键点击选择“属性”,这个szWorkingDir就是属性面板上的“起始位置”,值为这个快捷方式所指的应用程序所在的文件夹的路径。至少在我试验的程序里,创建开始菜单的快捷方式和桌面快捷方式,这个参数要求的值还是略有不同的,开始菜单里创建,可以直接传空字符串;而桌面快捷方式,传控字符串总是会出错,查看属性面板里的“起始位置”值为空,因此手动地传了快捷方式所指向的应用程序的所在文件夹的路径,后面在“安装结束时允许用户选择创建桌面快捷方式”话题里有详细说明。
参数五:szIconPath,带全限定名的图标的路径,即包含路径、文件名和扩展名
参数六:nIcon。如果不是使用Windows图标的话,统统指定为0;Windows图标我没有研究过,Help里说可以指定为0,1,2,3…n我猜测是不是图标文件本身包含了多个图标,而我可以指定使用哪个图标?
参数七:szShortCutKey,热键,一般用不到。如果有需要可以设置为比如"Ctrl + Alt + 1"这种形式。
参数八:nFlag,静态变量,多个用途。这个程序里我们使用了REPLACE,即永远使用当前这个快捷方式的属性;RUN_MAXIMIZED ,当从这个快捷方式登录程序时,程序界面最大化;RUN_MINIMIZED,当从这个快捷方式登录程序时,程序界面最小化; NULL,无任何操作(不知道这个无任何操作适用于何种情况?)。
小结:这段代码的重点在于
1) 实现对文件夹下的文件的盲读。因为之前笔者的文档都打包在程序里,苦于文档的名称和数量常常变更,每做一次都要耗费人力物力,而且在光盘里仍然需要单独放置一个文档文件夹供用户在没有安装程序前的随时查看,重复打包安装使得安装内容容量巨大,以至于从刻录小光盘改成刻录大光盘,从VCD盘改成DVD盘。这段代码在用户选择了安装文档的条件下,对外部文件夹进行了拷贝,并且读取文件夹下所有的pdf文件(依次类推,只要设置了正确的过滤条件,可以读取文件夹下想要的文件)。难点就在于将文件夹下的文件一个个读取出来并且获取该文件的信息。
2) 对读取的文件创建快捷方式,这个难点在于8个参数的理解。我在互联网上搜索了一阵子,并且啃了一阵子help,但是可能自己外语水平不是很过关,以至于第四个参数没有完全理解到底是什么意思,所见的例子也很单调并且偷懒,能赋””的地方都给赋了””,无语~~~~
整个安装程序做下来这一段代码是最难的,FindAllFiles在Help里解释是当碰到第一个符合条件的文件就会停下来,因此如何读取全部文件,并且获取文件信息,代码的撰写也是费了很大的功夫,并且参考了别人的程序修改出来的。
这是个很有用的设置,但是在InstallScript工程里不是默认自带的,因此也需要脚本编程实现。
这段代码的位置是在After Move Data | OnFirstUIAfter()的函数里实现的
1. 首先,在安装的时候把readme.txt文件从源盘拷贝到安装目录下。把这段代码拷贝到After Move Data | OnFirstUIAfter()的begin和end;之间即可。README.TXT文件放置在源盘的根目录下,并且在安装时拷贝到安装目录下。
CopyFile(SRCDISK^"README.TXT", TARGETDIR^"README.TXT");
这段代码意味着当安装执行的时候,这个文件总会被拷贝过去。
2. 创建一个Finish界面,并在界面上设置询问是否显示readme.txt文件的选项。
之前我们看到当我们第一次选取了After Move Data | OnFirstUIAfter()选项时,系统会为我们创建如下代码(当然不创建也不要紧,自己敲就是了)
这个就是结束界面。Installscript工程默认安装完毕后,界面直接消失,而不会出现一个带有Finish按钮的界面让用户点击了以后才结束整个安装过程。
这段代码就是创建了一个Finish界面了,我们要对这段代码进行改造,使之出现一个是否显示readme的选项。
把上图中从Disable(STATUSEX);起到SdFinishEx这行的代码,全部替换成如下代码:
Disable(STATUSEX);
ShowObjWizardPages(NEXT);
bOpt1 = TRUE;
bOpt2 = TRUE;
szMsg1 = SdLoadString(IFX_SDFINISH_MSG1);
szTitle="";
szMsg1="";
szMsg2="";
szOption1="Show Readme";
szOption2="";
SdFinishEx(szTitle, szMsg1, szMsg2, szOption1, szOption2, bOpt1, bOpt2);
if (bOpt1=TRUE) then
if(FindFile(TARGETDIR, "README.TXT", szDocFile)=0) then
LaunchApp ( WINDIR^"Notepad.exe" , TARGETDIR^"README.TXT" );
endif;
endif;
3. 代码解释
*******************************************************************************************
Disable(STATUSEX);
使默认的安装设置对话框无效。
*******************************************************************************************
ShowObjWizardPages(NEXT);
顺序执行这个OnFirstUIAfter()的代码,如果参数为BACK,则逆序执行
*******************************************************************************************
SdLoadString(IFX_SDFINISH_MSG1);
返回参数所关联的字符串值,这个参数应当是一个资源ID。
*******************************************************************************************
SdFinishEx(szTitle, szMsg1, szMsg2, szOption1, szOption2, bOpt1, bOpt2);
参数一:szTitle,即显示在界面上的左上角的标题,如果传空值,则显示默认值
参数二:szMsg1,安装结束的界面上允许最多有两个可选项,这个参数可以显示第一个选项的一些相关说明,如果赋空则不显示任何说明
参数三:szMsg2,解释同上
参数四:szOption1,选项名。这个是一个Checkbox,如果设置为空则不显示,如果赋值则显示一个Checkbox并且在这个Checkbox旁边显示这个所赋的简短值。
参数五:szOption2,解释同上。
参数六:第一个选项的状态,如果设置为TRUE,则第一个选项Checkbox默认为选中状态,FALSE则为未选中状态。
参数七:第二个选项的状态,解释同上。
*******************************************************************************************
if (bOpt1=TRUE) then
判断是否选择了checkbox。如果用户选择了这个选项,则进行下一步操作
*******************************************************************************************
if(FindFile(TARGETDIR, "README.TXT", szDocFile)=0) then
为了保险起见,需要进一步判断一下这个readme.txt是否被拷贝进来了
Help里解释如下:
FindFile ( szPath, szFileName, svResult );
参数一:szPath,文件所在的路径,不包含文件名
参数二:szFileName,文件名,包含扩展名
参数三:szDocFile,返回的文件名
如果查找成功,则返回值为1
*******************************************************************************************
LaunchApp ( WINDIR^"Notepad.exe" , TARGETDIR^"README.TXT" );
打开readme文件
Help里没有对这个函数的专门的解释,但是有个例子,以至于我看了好几遍才看懂要表达的意思
参数一:应用程序,也就是你用什么工具来打开第二个参数指定的文件。我们这里用记事本打开,因此要引用一下Windows下自带的程序Notepad.exe,路径为WINDIR^"Notepad.exe" 。如果是一些不是Windows自带的程序,比如PDF,DOC,还需要从注册表里得到所安装的目标位置,从这个目标位置得到要用的工具。有兴趣的朋友可以试验一下。
参数二:要打开的文件,带路径,包含扩展名
小结:这个界面我曾经试图写在OnFirstUIBefore()里的结尾部分,用Dlg_SdFinish来实现,但是总是发现虽然结束界面能出来,但是上一个界面不能消失掉的情况。因为这个资料也不好找,仓促之间试验出上述所说的办法,估计是等安装界面结束后补上一个界面来达到这个效果的;其实我本人是比较讨厌结束的时候有这么一个要看readme的选项的,一般自己装到这种软件,都是去掉钩选框,不看readme的;但是如果直接结束掉,不出这个结束界面又觉得提示不足,有时候不能确定安装程序有没有结束,所以私下里还是比较想去掉readme选项,而直接显示一个只有一个finish按钮的界面的。
有时候我们会看到别的安装程序在安装过程中允许用户选择是否要在桌面上显示快捷方式,一开始因为我们公司的分布式系统的组件太多了,不想显示在桌面上,而且觉得和在开始菜单中显示快捷方式的原理是一样的,因此也就轻轻带过;后来经理抱怨说没有桌面快捷方式,总是要去开始菜单找,觉得麻烦,而且客户是使用专用计算机运行我们的程序,也就是桌面上会很干净,希望我能够做这个功能出来。我试了一下,发现和在开始菜单中显示快捷方式还是有一点不同的,也是值得写出来的,至少可以让读者少走一些弯路。
1. 首先要显示一个允许用户选择是否显示桌面快捷方式的界面,这个界面上要有一个checkbox(钩选框),当钩选了以后,安装程序就要在安装时为用户显示桌面快捷方式。
这段代码的位置是在After Move Data | OnFirstUIAfter()的函数里实现的,也就是和“显示readme文件”的功能放在一起。
把从Disable(STATUSEX);起到SdFinishEx这行的代码,全部替换成如下代码:
Disable(STATUSEX);
ShowObjWizardPages(NEXT);
bOpt1 = TRUE;
bOpt2 = TRUE;
szMsg1 = SdLoadString(IFX_SDFINISH_MSG1);
szTitle="";
szMsg1="";
szMsg2="";
szOption1="Show Readme";
szOption2="Create Shortcut on Desktop?";
SdFinishEx(szTitle, szMsg1, szMsg2, szOption1, szOption2, bOpt1, bOpt2);
2. 代码解释
与上面的“显示readme文件”中的代码相比,只动了一个地方,即szOption2="Create Shortcut on Desktop?";
这个是一个Checkbox,如果值设置为空则不显示,如果赋值则显示一个Checkbox并且在这个Checkbox旁边显示这个所赋的简短值。
这里我们需要它显示出来,这样在界面上用户就会看到一个钩选框询问是否要显示桌面快捷方式。
3. 接下来我们要对用户所做的选择做一些判断,并且显示桌面快捷方式,在这段代码后面加上
if(bOpt2=TRUE) then
if (FeatureIsItemSelected(MEDIA, szFeatureName1)=1) then
szDocFile = TARGETDIR^"Server\\server.bat";
LongPathToQuote(szDocFile, TRUE );
AddFolderIcon(FOLDER_DESKTOP, "Server" , szDocFile, TARGETDIR^"Server" , TARGETDIR^"Server\\icons\\appClient.ico" , 0 ,"" , REPLACE );
endif;
4. 代码解释
因为上面对这些函数的每个参数都有详细解释了,所以这里就不做一一解释了,只对要注意的地方做说明。
这里,一开始,笔者对第四个参数仍然传的是空字符串,但是创建的快捷方式总是不能运行,对比属性面板才发现,桌面快捷方式的“起始位置”的值居然是空的,看来Help解释的“当传空值的时候,默认为快捷方式所指的应用程序所在的目录”并未生效,只好老老实实地把运行目录的值手动地传进去。
读者可能注意到在AddFolderIcon函数里的第三个参数被做了一些预处理,这个处理也是折腾了几次才搞出来的,不同的操作系统默认路径也是有是否带引号的差别的,这里需要显式地指定一下,以免在不同操作系统上运行时引起不同的结果。
在全部安装完毕后,启动指定的程序,向Windows安装一个服务。或者也可使用于安装结束后的程序的自启动。
1. 这部分很明显是要在安装全部结束后进行的,因此放在After Move Data | OnEnd里
2. 把OnEnd()的代码替换如下
function OnEnd()
STRING szFeatureName;
STRING serviceTarget;
STRING szDocFile;
begin
/*
//这个服务所需的文件只有在钩选了某feature时候才会被拷贝,并且也只有在用户钩选安装了此feature时候才会在安装结束时安装此服务,因此首要判断是否选择了此feature,然后寻找到该执行文件,并且进行安装
*/
szFeatureName="Watch_Portion";
serviceTarget=TARGETDIR^"watch.exe";
if (FeatureIsItemSelected(MEDIA, szFeatureName)=1) then
if(FindFile(TARGETDIR, " watch.exe ", szDocFile)=0) then
if (LaunchApp (serviceTarget, "") < 0) then
MessageBox ("Unable to launch "+serviceTarget+".", SEVERE);
endif;
endif;
endif;
end;
3. 代码解释
***************************************************************************************
if (FeatureIsItemSelected(MEDIA, szFeatureName)=1) then
endif;
首先判断这个feature是否被用户选择安装。因为在这个应用程序里这个服务只与此feature相关,因此要做一下判断,如果用户没有安装这个feature,就不需要启动这个服务了。
当用户选择了这个feature时,返回值为0
***************************************************************************************
if(FindFile(TARGETDIR, " watch.exe ", szDocFile)=0) then
endif;
这个是判断一下文件是否被正确地拷贝过去了,这个文件应该位于安装目录下,名为watch.exe。当该文件存在时,返回值为0
***************************************************************************************
if (LaunchApp (serviceTarget, "") < 0) then
endif;
启动该服务;如果启动失败,则返回小于0的值。
这里LaunchApp的用法和上面第6段的用法略有不同。这个函数的本意是启动第一个参数指定的运行程序来打开第二个参数指定的文件。这里第二个参数指定为空,因为没有要打开的文件;第一个参数指向我们需要启动的可执行程序即可。
***************************************************************************************
MessageBox ("Unable to launch "+serviceTarget+".", SEVERE);
如果上一步中判断到程序未能正确启动,则弹出一个错误提示框体现用户。
小结:这段代码的用法非常简单,但是如果用在适当的安装程序里会非常重要;笔者的安装程序,在一开始的时候需要用户安装完毕后手动地去安装目录里找到这个服务并且启动,使人感觉非常不友好;现在在安装完毕后做到了静默启动,用户无需做任何事情。而且这个服务需要JDK的支持,配合上述第2段中判断是否安装了JDK这个应用,就不会出现安装了此服务但是无法运行的局面。
之前提到了,要在安装本系统时判断是否安装了JDK,在最初笔者所做的安装盘中,还要让用户手动地去为JDK设置环境变量JAVA_HOME,设置环境变量对于外行来说简直就是天方夜谭,在JAVA论坛新手区最常见就是求助设置环境变量的问题了,因此,这个功能最好还是由安装程序代劳为妙。
1. 这段代码在Before Move Data | OnFirstUIAfter()里
//write the environment variable
szKey = "SOFTWARE\\JavaSoft\\Java Development Kit\\1.6.0_04";
RegDBSetDefaultRoot(HKEY_LOCAL_MACHINE);
if (RegDBKeyExist(szKey)=1) then//如果该注册表值存在
if(RegDBGetKeyValueEx(szKey,"JavaHome",nvType,svValue,nvSize)=0) then//获取注册表值成功
szKey = "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment";
if(RegDBSetKeyValueEx(szKey, "JAVA_HOME", REGDB_STRING, svValue, -1)<0) then
MessageBox ("Javahome create failed, please set it manually!", SEVERE);
endif;
endif;
endif;
2. 代码解释
****************************************************************************
RegDBKeyExist(szKey)
判断JDK1.6.0_04的注册表值是否存在;要判断JDK1.6.0_04是否被安装,只有通过注册表来判断啦,同理可得,要是自己开发的一套系统中有多个安装程序,而且相互关联,就得朝注册表里写入值了。
如果返回值为1,则说明存在该键值;
如果返回值小于0,则说明该键值不存在。
****************************************************************************
RegDBGetKeyValueEx(szKey,"JavaHome",nvType,svValue,nvSize)
因为设置JAVA_HOME环境变量需要JDK的安装位置,所以要根据注册表来寻找到这个安装位置,而幸运的是,该键值下的JavaHome键名所对应的值就是JDK的安装位置。
Help里对该函数的解释如下:
RegDBGetKeyValueEx ( szKey, szName, nvType, svValue, nvSize );
参数一:szKey, 要查找的注册表的键,这里我们查找SOFTWARE\\JavaSoft\\Java Development Kit\\1.6.0_04
参数二:szName,一些注册表键下面会有一些键名,如果你去看一下我们查找的键,会发现该键下存在多个键名,这里我们只要查找JavaHome键名对应的值,因此,指定szName为JavaHome
参数三:nvType,返回该键名对应的值的类型,比如字符型,数字型;当时笔者还犯了一个错误,以为这个参数是需要笔者指定类型的,因此写了一个REGDB_STRING,结果编译出错,搞了半天发现这个参数是个返回值,汗一个。
参数四:svValue,返回该键名对应的值
参数五:nvSize,返回该键名对应的值的字节数
****************************************************************************
szKey = "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment";
RegDBSetKeyValueEx(szKey, "JAVA_HOME", REGDB_STRING, svValue, -1)
如果搜索注册表发现JDK已经安装了,就去读一下注册表的键值,并且设置我们所需要的环境变量,这两句话就是用来设置环境变量的。
环境变量也是利用注册表键值设置函数RegDBSetKeyValueEx来实现的,这个键是一个特殊的位置,一定是"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment",我们对该函数进行进行详细说明。
RegDBSetKeyValueEx ( szKey, szName, nType, szValue, nSize );
函数作用:设置注册表键值
参数一:szKey注册表里的键,这里,我们需要设置环境变量的值,因此这里固定传值为"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"
参数二:szName,键名,这里我们需要设置的是名为JAVA_HOME的环境变量
参数三:nType,被设置的键的类型,这里是字符串型,并且不带%PATH%之类的符号,也不转行
参数四:szValue,就是键值了,这里我们已经从上面得到了JDK的安装路径,就把安装路径传进去
参数五:nSize,help里说明如果键类型为REGDB_STRING, REGDB_STRING_EXPAND, 或者 REGDB_NUMBER时,都可以设置该值为-1,installshield会自动为我们计算正确的长度,而当键类型为REGDB_BINARY 和REGDB_STRING_MULTI时,就必须传该键值的实际大小进去。
小结:Installshield默认键值位置是在HKEY_CLASSES_ROOT下的,因此在这里,我们需要在进行搜索键值和设置键值的操作之前使用RegDBSetDefaultRoot(HKEY_LOCAL_MACHINE);这句话来设置一下默认的根键值为HKEY_LOCAL_MACHINE;另,在网上看了一个帖子,当时匆匆看了一下,说是设置的键值会在反安装时候卸载掉,我倒是没有在自己的安装程序里发现这个问题,不过可以研究一下;作者说当时为了解决这个问题,是在代码头加上DISABLE(LOGGING);代码尾加上ENABLE(LOGGING)来实现的,虽然我没有碰到这个问题,但是还是很感谢这位作者,因为当时他也说了,根本找不到资料,自己啃了天书般的HELP来解决,而自己一旦解决了问题,就分享出来,以便于大家少走弯路。
在第一部分的第9点我们提到过InstallScript工程里自带的Uninstall快捷方式的缺陷,这里我们将会创建一个可以实现全部卸载的卸载方式,这个卸载方式会以快捷方式出现在开始菜单下,利用安装程序本身的反安装功能来实现
3. 这段代码在Before Move Data | OnFirstUIAfter()里,和其他创建快捷方式的代码放一起
function OnFirstUIAfter()
STRING szfilename,szFolder ,szmsg1,szmsg2;
NUMBER nresult;
begin
//创建删除快捷方式
szfilename = UNINSTALL_STRING +" /UNINSTALL";
nresult = StrFind(szfilename,".exe");
if nresult >=0 then
StrSub(szmsg1,szfilename,0,nresult + 4);
StrSub(szmsg2,szfilename,nresult + 4,200);
LongPathToQuote(szmsg1, FALSE );
LongPathToQuote(szmsg2, FALSE );
szfilename = "\"" + szmsg1 + "\"" +szmsg2;
endif;
AddFolderIcon(FOLDER_PROGRAMS^"Test","Uninstall",szfilename,WINDIR,"",0,"",REPLACE);
End;
4. 代码解释
****************************************************************************
szfilename = UNINSTALL_STRING +" /UNINSTALL";
参数一:UNINSTALL_STRING这个静态变量指向的就是我们的安装程序,也就是setup.exe,不过指向的位置不是我们的源盘里的setup.exe,而是C:\Program Files\InstallShield Installation Information\{0D9DF66A-44E5-4754-A522-2AD6C9D5CDBE}\setup.exe;Installshield创建的安装文件在安装时总会在这个文件夹里创建对应信息,一长串数字型序列码就是安装程序的Product ID。利用这个setup.exe就可以进行反安装
参数二:/UNINSTALL,告诉程序启动这个setup.exe时为非安装状态,即修复、重新安装和卸载状态。
因此,这个字符串的值应该是这种形式:
"C:\Program Files\InstallShield Installation Information\{0D9DF66A-44E5-4754-A522-2AD6C9D5CDBE}\setup.exe" -runfromtemp -l0x0409 /UNINSTALL
****************************************************************************
nresult = StrFind(szfilename,".exe");
寻找到“.exe”这个字符串在szfilename这个字符串中的位置。
Help里对这个函数的描述如下:
StrFind (szString, szFindMe);
参数一:szString,被查找的源字符串
参数二:szFindMe,要查找的字符串
返回值为要查找的字符串在源字符串中的位置,如果返回值小于0,则说明源字符串中找不到要查找的字符
评论