在全部安装完毕后,启动指定的程序,向Windows安装一个服务。或者也可使用于安装结束后的程序的自启动。
1. 这部分很明显是要在安装全部结束后进行的,因此放在After Move Data | OnEnd里
2. 把OnEnd()的代码替换如下 function OnEnd() STRING szFeatureName; STRING serviceTarget; STRING szDocFile; begin
/*
//这个服务所需的文件只有在钩选了某feature时候才会被拷贝,并且也只有在用户钩选安装了此feature时候才会在安装结束时安装此服务,因此首要判断是否选择了此feature,然后寻找到该执行文件,并且进行安装 */
szFeatureName=\
serviceTarget=TARGETDIR^\
if (FeatureIsItemSelected(MEDIA, szFeatureName)=1) then if(FindFile(TARGETDIR, \ if (LaunchApp (serviceTarget, \
MessageBox (\ endif; endif; endif; end;
3. 代码解释
*************************************************************************************** if (FeatureIsItemSelected(MEDIA, szFeatureName)=1) then endif;
首先判断这个feature是否被用户选择安装。因为在这个应用程序里这个服务只与此feature相关,因此要做一下判断,如果用户没有安装这个feature,就不需要启动这个服务了。 当用户选择了这个feature时,返回值为0
*************************************************************************************** if(FindFile(TARGETDIR, \ endif;
这个是判断一下文件是否被正确地拷贝过去了,这个文件应该位于安装目录下,名为watch.exe。当该文件存在时,返回值为0
*************************************************************************************** if (LaunchApp (serviceTarget, \ endif;
启动该服务;如果启动失败,则返回小于0的值。
这里LaunchApp的用法和上面第6段的用法略有不同。这个函数的本意是启动第一个参数指定的运行程序来打开第二个参数指定的文件。这里第二个参数指定为空,因为没有要打开的文件;第一个参数指向我们需要启动的可执行程序即可。
*************************************************************************************** MessageBox (\
如果上一步中判断到程序未能正确启动,则弹出一个错误提示框体现用户。
小结:这段代码的用法非常简单,但是如果用在适当的安装程序里会非常重要;笔者的安装程序,在一开始的时候需要用户安装完毕后手动地去安装目录里找到这个服务并且启动,使人感觉非常不
友好;现在在安装完毕后做到了静默启动,用户无需做任何事情。而且这个服务需要JDK的支持,配合上述第2段中判断是否安装了JDK这个应用,就不会出现安装了此服务但是无法运行的局面。
9. 安装结束后,为JDK设置一个环境变量
之前提到了,要在安装本系统时判断是否安装了JDK,在最初笔者所做的安装盘中,还要让用户手动地去为JDK设置环境变量JAVA_HOME,设置环境变量对于外行来说简直就是天方夜谭,在JAVA论坛新手区最常见就是求助设置环境变量的问题了,因此,这个功能最好还是由安装程序代劳为妙。
1. 这段代码在After Move Data | OnFirstUIAfter()里 //write the environment variable
szKey = \
RegDBSetDefaultRoot(HKEY_LOCAL_MACHINE); if (RegDBKeyExist(szKey)=1) then//如果该注册表值存在
if(RegDBGetKeyValueEx(szKey,\获取注册表值成功
szKey = \
if(RegDBSetKeyValueEx(szKey, \ MessageBox (\ endif; endif; endif;
2. 代码解释
**************************************************************************** RegDBKeyExist(szKey)
判断JDK1.6.0_04的注册表值是否存在;要判断JDK1.6.0_04是否被安装,只有通过注册表来判断啦,同理可得,要是自己开发的一套系统中有多个安装程序,而且相互关联,就得朝注册表里写入值了。
如果返回值为1,则说明存在该键值; 如果返回值小于0,则说明该键值不存在。
**************************************************************************** RegDBGetKeyValueEx(szKey,\ 因为设置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 = \ RegDBSetKeyValueEx(szKey, \
如果搜索注册表发现JDK已经安装了,就去读一下注册表的键值,并且设置我们所需要的环境变量,这两句话就是用来设置环境变量的。
环境变量也是利用注册表键值设置函数RegDBSetKeyValueEx来实现的,这个键是一个特殊的位置,一定是\,我们对该函数进行进行详细说明。
RegDBSetKeyValueEx ( szKey, szName, nType, szValue, nSize ); 函数作用:设置注册表键值
参数一:szKey注册表里的键,这里,我们需要设置环境变量的值,因此这里固定传值为\
参数二: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来解决,而自己一旦解决了问题,就分享出来,以便于大家少走弯路。
10. 完美卸载
在第一部分的第9点我们提到过InstallScript工程里自带的Uninstall快捷方式的缺陷,这里我们将会创建一个可以实现全部卸载的卸载方式,这个卸载方式会以快捷方式出现在开始菜单下,利用安装程序本身的反安装功能来实现
3. 这段代码在After Move Data | OnFirstUIAfter()里,和其他创建快捷方式的代码放一起 function OnFirstUIAfter()
STRING szfilename,szFolder ,szmsg1,szmsg2; NUMBER nresult;
begin
//创建删除快捷方式
szfilename = UNINSTALL_STRING +\ nresult = StrFind(szfilename,\ if nresult >=0 then
StrSub(szmsg1,szfilename,0,nresult + 4); StrSub(szmsg2,szfilename,nresult + 4,200); LongPathToQuote(szmsg1, FALSE ); LongPathToQuote(szmsg2, FALSE );
szfilename = \ endif;
AddFolderIcon(FOLDER_PROGRAMS^\;
End;
4. 代码解释
**************************************************************************** szfilename = UNINSTALL_STRING +\
参数一: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时为非安装状态,即修复、重新安装和卸载状态。
因此,这个字符串的值应该是这种形式: \
Information\\{0D9DF66A-44E5-4754-A522-2AD6C9D5CDBE}\\setup.exe\/UNINSTALL
**************************************************************************** nresult = StrFind(szfilename,\
寻找到“.exe”这个字符串在szfilename这个字符串中的位置。 Help里对这个函数的描述如下:
StrFind (szString, szFindMe);
参数一:szString,被查找的源字符串 参数二:szFindMe,要查找的字符串
返回值为要查找的字符串在源字符串中的位置,如果返回值小于0,则说明源字符串中找不到要查找的字符串
**************************************************************************** StrSub(szmsg1,szfilename,0,nresult + 4); StrSub(szmsg2,szfilename,nresult + 4,200);
如果要查找的字符串存在,那么源字符串就是正确的;这两句语句就对源字符串进行截断,得到想要的子串。
szmsg1应该为C:\\Program Files\\InstallShield Installation
Information\\{0D9DF66A-44E5-4754-A522-2AD6C9D5CDBE}\\setup.exe 而szmsg2应该为 -runfromtemp -l0x0409 /UNINSTALL
Helpl里的解释如下:
StrSub ( svSubStr, szString, nStart, nLength ); 参数一:svSubStr返回的结果字符串 参数二:szfilename源字符串
参数三:开始截断的位置。如果指定的位置大于整个被解析的字符串长度,则返回一个空字串。
参数四:结束截断的位置。如果指定的位置大于整个被解析的字符串长度,则默认为结束截断的位置是字符串的结尾处。
**************************************************************************** LongPathToQuote(szmsg1, FALSE ); LongPathToQuote(szmsg2, FALSE );
这两句的作用是对上面解析出的两个子串脱去括号。原本笔者参考的例子里没有这两句,在自己计算机上运行正常,但是换了一台计算机后,创建出的卸载快捷方式无效,查看快捷方式的指向发现和原来计算机的指向略有差别,查阅了一些资料得知Windows下的长文件名就有这个缺陷,每个操作系统解析出来的可能会有所不同,主要是引号的麻烦。在笔者自己的计算机上获取的长文件名是不带引号的,因此,解析正确;而测试的那台计算机上获取的文件名却是带引号的,这就造成了解析后拼凑的字符串的差别。这里就要显式地为解析出来的子串脱一下引号。
**************************************************************************** szfilename = \
拼凑出正确的可执行文件的长文件名,带路径,包含扩展名
****************************************************************************
AddFolderIcon(FOLDER_PROGRAMS^\;
添加一个快捷方式到开始 | 所有程序 | Test下;照抄即可。
小结:可能读者会比较奇怪这一段代码的写法,因为中间那段if endif;代码看上去简直就是多此一举。在Installshield7之前,一直是这样写的:
szfilename = UNINSTALL_STRING +\
AddFolderIcon(FOLDER_PROGRAMS^\;
从Installshield8开始,长文件名一直有引号封闭不正确的问题,因此if endif;代码完全是为了解决这个问题而存在的,而上面提到的两个脱去引号的语句,是笔者在前人基础上修改加上的,因为发现解析出来的字串要是不脱一下括号还是有问题。
这个快捷方式运行的时候,出现界面和在安装完毕后再次运行安装程序出现的界面相同。选择Remove即可进行卸载。
这个卸载不会把程序运行时产生的文件卸载掉,比如日志文件、配置信息文件等;会把安装目录中所有从安装程序中安装的文件都卸载掉,包括安装时从外部拷贝的文件。利用Project Assistant创建的卸载快捷方式则无法卸载掉安装时从外部拷贝的文件。
11. 完美卸载之卸载时触发命令(卸载Windows服务)
在做完这个安装程序后,以为可以结束了,没想到经理又提出了一个新的要求,因为之前的安装里(参阅第二部分的第8小节),在安装完毕后,启动了一个指定程序,这个指定程序干的事情就是向Windows写了一个服务进去(有兴趣的同学可以去看看Java Service相关资料,是一个把Java程序注册为Windows服务的一个工具或者说是组件更合适些);所以,这里希望能够在卸载的时候能够把这个服务给卸载掉。
首先我们介绍一下两条Windows cmd命令: 1) SC stop XXX
这条命令用于停止某个名叫XXX的正在运行的Windows服务 2) SC delete XXX
这条命令用于删除某个名叫XXX的Windows服务
一开始我的思路是这样的,获取安装程序的卸载状态,然后调用这两条命令来删除服务;没想到这个“获取安装程序的卸载状态”让我浪费了整整一个下午的时间,只知道MAINTENANCE是程序的反安装状态,而这个反安装状态是有可能包括“重装”、“修复”和“卸载状态”的,当然我可以让反安装界面只能处于卸载状态,只要把前面创建卸载快捷方式中的szfilename = UNINSTALL_STRING +\/UNINSTALL\这句话改成szfilename = UNINSTALL_STRING +\就可以了;但是试验出来是不等我确认删除,这个服务就卸载掉了,原因是这个界面一出来就是
MAINTENANCE状态,而程序捕获了这个状态后,是不管我是否按下了确认按钮就会去做这个操作了。
后来想在Onbegin里添加一个SdWelcomeMaint函数的判断,结果是判断倒是成功的,但是多了另一个重复界面。
看来这个思路可能是有问题的,然后满地google之,还是吞硬币的小猪的一篇文章给了启发,原文地址找不到了,只找到了这篇http://school.ogdev.net/ArticleShow.asp?id=1699&categoryid=7,这里面其实是谈反安装时候不执行OnMaintUIBefore函数的问题,我想既然这个函数是反安装时候“应该执行的”,那么就看看这个函数吧。
于是打开Before Move Data | OnMainUIBefore
打开一看,大喜过望,这个函数里明明白白地显示了反安装时候的所有界面。 于是顺着向下看,找到Dlg_SdFeatureTree。