Installshield能够说是最好的作安装程序的商业软件之一,不过由于功能的太过于强大,以致于上手和精通都不是容易的事情,以前都是用Installshield的Project Assistant对付过去的,此次作这个安装程序,为了实现一些功能,必须写代码,国内外现成的资料不多,并且不少都语焉不详,本身反复啃了屡次,对比Installshiel自带的help,才明白资料所表达的意思。这个安装程序虽然比较简陋,在行家眼里多是小菜一碟,可是也花了笔者一个星期的时间,阅读了不少资料,啃了好几天英文help,集成了不少先驱者的经验,也费了本身很多心血作成的,对每一段代码的用处、每个用到的函数都进行了详细的说明,所以转载时请务必保留转载出处和由艾泽拉斯之海洋女神出品的字样;如需刊登,请与做者联系。css
在此要感谢吞硬币的小猪,天下晓明,余满青,海洋C++乐园(此海洋不是彼海洋)等大虾在互联网上的无私奉献,他们的贴子和博客给了我很大启示。java
由于本人是作java出身的,所以对这种类C++语言仍是第一次接触,有理解不当之处,请朋友们指正。欢迎Email至little_fairycat@126.com数据库
需求:公司作了一个软件产品,编程
1. 该软件运行须要JDK环境(不是JRE,由于该软件要向windows注册一个服务,用到了JavaService,JDK才支持这个功能;不过这里侧重于判断是否安装了某软件是否安装,而不是纠缠于该装JDK仍是该装JRE);windows
2. 因为是Server-Client形式的,须要容许用户选择安装组件,好比A机只装Server端,B机只装Client端;网络
3. 文档不打包在安装程序里,直接存放在光盘文件夹下方便用户查看,同时容许用户指定是否安装文档到计算机上(为何这样作,后面说明详细缘由);app
4. 该软件会以受权形式发放给用户,不一样的用户,软件自己可能相同,而不一样的只是受权文件和一些配置,所以但愿受权文件和配置文件不打包在安装程序里,而直接存放在光盘里,以减小可能的重复打包安装程序的劳动;less
5. 在安装完毕后,但愿能自启动程序(由于该软件须要在安装完毕后启动一个程序,该程序实现向Windows注册服务的功能,该程序最好由安装程序启动,而不是由客户手动启动)。分布式
6. 但愿有反安装程序函数
本文提到的“外部”指不打包在安装程序里的,与安装程序一块儿存放在光盘里的一些文件夹,这些文件夹包含了安装中所须要的文件,同时也可能有其余用途,所以不适合直接压缩打包在安装程序里。
该实例实现了以下功能:
1. 显示软件许可协议
2. 判断是否安装了本软件所须要的先决软件JKD1.6.0_04,如无,则启动外部安装程序进行安装(一样原理能够用来判断是否安装了其余软件,只要该软件在注册表中有键值)
3. 安装容许用户选择须要安装的组件
4. 用户的输入信息、所选安装路径、所选安装组件将显示在安装界面上(Installshield虽然自带了此界面,可是默认是显示为空的,须要写脚原本显示信息)
5. 根据用户选择的组件,在开始菜单显示程序的快捷方式(一样适用于桌面快捷方式,后面作详细说明)
6. 根据用户选择的组件,从外部文件夹拷贝相应的文件到安装目标路径的文件夹中
7. 根据从外部拷贝进来的文件,建立快捷方式(这里主要是拷贝文档,并在开始菜单中建立快捷方式)
8. 在安装结束时,显示readme.txt文件
9. 在安装结束后,启动指定的程序
10. 完美卸载
笔者所用的环境为Installshield 12 Premier Edition,Windows XP with SP2, 该环境下创建的工程能够直接使用在Installshield 2008 Premier Edition下,Installshield 2008在打开Installshield 12所建的工程时会提示你是否须要进行Upgrade,确认便可,软件会自动为你进行升级,很方便。
下面咱们一步一步来创建一个基本的工程,而且使用脚原本完善和丰富所需功能
1. 打开Installshield 12 Premier Edition,新建一个Installscript MSI Project,这种被称之为半脚本程序,由于兼具Basic Project基本类型和Installscript Project全脚本类型二者的优势,我比较喜欢用。像我这样需求的,既要用到Wizard的便利,又想写一点脚原本实现一点自定义操做的,就比较适合用这种类型啦。
选择类型为Windows Installer | InstallScript MSI Projcet,输入工程名,指定工程所在的文件夹。
2. 界面会切换到Project Assistant,咱们先从这里开始把工程的基本组件和基本文件创建好。
3. 在Project Assistant界面的底部,会有一个引导动做条,在创建该工程的基本结构和文件时,咱们都将在此界面进行操做,下文都将以“引导条”来指代这个引导动做条。
4. 点击引导条上的Application Information
这里输入:
公司名,公司名将会出如今Setup.exe的注解中
软件名,将会出如今安装过程的左上角标题栏上
版本号,没看到在哪,不过本身比较方便地知道本身在编译哪一个版本的软件
公司网址,没看到在哪,并且若是该公司没有网址呢?并且这里有点bug,好像默认的值老是会报一个String_ID1为空的错误,本身输入一个网址就不会报错。
是否在你建立了更新时自动通知最终用户,没用过,我都选了No。
选择一个图标,这个图标会出如今“添加或删除程序”里,我通常用默认的,固然你能够替换成本身想要的图标。
5. 点击引导条上的Installation Requirement
这里选择对操做系统和一些软件的需求。根据本身须要来选择是否要求操做系统的版本,已是否要求安装了某些软件。
6. 点击引导条上的Installation Architecture
这是个十分有用的设置,对于本文所用的分布式软件来讲很是合适,分布式软件的每一个组件能够设置为一个Feature,用户能够自由选择安装某些功能。
将选项Do you want to customize your Installation选择为Yes。
点击选中根节点Installation Architecture,点击New建立新的Feature,能够为每一个Feature指定新名称。
还能够在Feature下建立子Feature,好比若是文档Feature下包括软件自己文档,和软件所需的运行环境的文档,那么能够建立两个子Feature,分别包含两种文档,用户在安装时就能够选择安装部分或者所有文档了。这里咱们没有用到子Feature,用途和普通Feature同样。
这里,创建好全部Feature后,咱们将切换到Installation Designer作一个设置
找到Installation Designer页面上左边导航树Organization | Features分支,你会看到这里Features都显示为原始的名称,而非咱们改过的名字,由于Feature有Name和Display Name两种名称,咱们刚才改的不过是Display Name,为了便于查看和使用,咱们在这里把Name也改一下
注意Name不能够有空格,可使用下划线
继续切换回Project Assistant
7. 点击引导条上的Application Files
咱们将在这里对安装路径进行微调,而且为每一个Feature指定须要安装的文件
这里我不想使用Program Files | Company Name | Product Name这个路径,我想使用Program Files | Product Name,我直接点击选中My Product Name[INSTALLDIR]拖动到ProgramFileFolder下,还能够直接将My Product Name 改为本身想要的文件夹名字
接下来,为每一个Feature指定要安装的文件。
打开这个下拉列表,全部的Feature都在这里,按顺序来给每个Feature创建文件夹,而且导入所需的文件。
选择第一个Feature, 即Server,点击My Product Name[INSTALLDIR]节点,右键点击,在菜单上选择New Folder来建立一个文件夹。
建立一个Server文件夹,这个文件夹将用来存放该组件须要的一些文件。
再在Server文件夹下建立一个icon文件夹,存放该组件所用的图标。
而后为该Feature添加安装时该Feature要安装的文件。
这里咱们创建的icon文件夹是用来存放这个feature在后面要创建快捷方式时使用的图标的。为这个icon文件夹添加相应的图标文件,而且记住图标文件的来源文件夹,后面设置快捷方式的时候要用。
点击选中要添加文件的文件夹,而后点击右下角的Add Files,而后添加文件
接下来咱们为Feature添加文件夹,若是这个文件夹中的所有文件都为这个Feature所需。添加文件夹的好处在于只要文件夹位置和名称不变,那么文件夹里面的文件都是动态加载的,有多少加载多少,不用考虑文件名的改动带来的影响。
点击选中要添加文件夹的文件夹,而后点击右下角的Add Folders,而后添加文件夹。
选中文件夹,点击肯定。
会询问你是否要使用动态文件连接,我都选择肯定,好处就在于我刚才上面所述。
显示了源文件夹,若是这个文件夹下有子文件夹,而且也须要一并添加进来的话,务必钩选Include subfolders选项。
这里还容许作一些简单设置来包含或者排除一些特定文件,支持通配符。
点击OK肯定加入文件夹。
如法炮制为每一个Feature创建文件夹,而且添加文件,最后效果如图所示
Document这个Feature,除了文件所用的图标外,什么都不要添加,后面咱们将用安装时实时拷贝的方式来拷贝文档进来。
8. 接下来咱们为可执行文件建立快捷方式。
点击引导条上的Application Shortcuts
点击New新建一个快捷方式
选择一个要创建快捷方式的Feature。
若是要创建快捷方式的程序为非.exe形式,请把Files Of选择选为All Files(*.*)格式。
咱们的程序安装目标路径设置在Program Files下,所以双击[ProgramFilesFolder]打开,层层点击进入。
咱们这里要为client.bat创建一个快捷方式,由于这个是启动用的批处理文件。
Installshield能够自动监测到.exe文件的存在,自动生成快捷方式,用户只须要作一些适当修改便可。
新建的快捷方式将出如今这里,名字很差听,样子也很差看,咱们将为它改一个名字,而且换一个图标。
选中快捷方式,点击Rename,而且为这个快捷方式改一个适当的名字。
注意右边的几个选项。
Create shortcut in Start Menu,将在开始菜单里建立一个快捷方式。 Create shortcut on Desktop,将在桌面上建立一个快捷方式。 Use alternate shortcut Icon,替换快捷方式的图标 Associate a file extension with the shortcut’s target,没用过,不知道什么意思。
咱们在这里将只建立开始菜单的快捷方式,所以钩选第一项。
钩选第三项,而且点击Browse来浏览图标。
请回想刚才在为Feature添加文件的时候,每一个feature都添加了对应的icon。这里,请把浏览的文件夹设定为刚才添加icons所用的文件夹,通俗的说,就是你刚才从哪儿添加一个图标进feature的,如今仍是从哪儿添加的这个图标。
其实这一点我是一直很费解的,当初不知道要这么选择图标,随便从外面一个任意文件夹里添加了一个图标,以致于打包后死活找不到图标,后来通过试验才知道这个被选中的图标文件要拷贝进来,打包进安装文件才能够。这一点上不能不提一下visual studio,这个工具作安装程序虽然功能通常,可是思想仍是不错的,当它的组件指定拷贝了图标文件后,在创建快捷方式时,快捷方式使用的图标是指向虚拟的安装目标路径下的图标文件的,而不是指定到这个实实在在的源文件夹。这一点差异就体现出了思想上的差别。
如法炮制为每一个Feature指定快捷方式,Document除外,由于咱们在这个feature里除了图标文件外什么都没有添加。
至此咱们为每一个可执行程序添加了开始菜单下的快捷方式。
咱们再切换去Installation Designer,找到System Configuration | Shortcuts。
看到快捷方式在开始菜单中是以 公司名 | 软件名 | 快捷方式 这种形式存在的。事实上我是不喜欢这种形式了,想一想点开一层还有一层,不如直接了当来得干脆,所以作一些修改。
这里我改为了以下设置
不要告诉我你不会改,直接拖动Test文件夹往Program Menu(即开始菜单下的那个“全部程序”)下一塞便可,而后删除掉多余的Company Name文件夹。
9. 可能刚才在Project Assistant界面有人已经注意到了左边栏上More Options下Create an uninstallation shortcut这个诱人的字样了。
但是我要告诉你,若是你选择了这种方式创建卸载快捷方式的话,你会很沮丧地发现:
a) 彷佛只有在安装某个feature的时候这个卸载快捷方式才会出现(固然,就是那个default feature,这种要命的feature形式决定了每一个文件或者快捷方式都必须明确地归属到某个feature下),所以,当你的客户只选择了其余feature安装时,这个卸载方式不会出现,而他必须去“添加或卸载程序”里面去卸载
b) 若是你写脚本使得安装时会拷贝一些外部文件进来,那么这些文件在这种卸载方式下是删除不掉的。(若是你确实想保存这些文件,你能够在脚本里设置它们属性为permanent,这个属性能够保证什么卸载方式都不能删除你的这些文件)。
因此这里咱们忽视这个卸载快捷方式的存在,而将在后面采用脚本形式实现完美卸载。
10. 点击引导条上的Application Registry
向注册表写键和键值,因为本工程不须要,忽略之。有须要的朋友能够查阅相关资料,不难。
11. 点击引导条上的Installation Location
这个是用来设置安装包的语言的,选择了多个语言后,用户能够在安装界面开始的时候选择安装时所用的语言;不过做为一个公司产品来讲,这么偷懒,客户的印象是要打折扣的,因此仍是选个单语言吧,该什么语言的安装包就什么语言的安装包,各归各。
不过你又会沮丧地发现,若是要选择一种其余语言做为Default Language,好像又报错了。
这个问题当时折腾了我一个星期(固然那时候才接触Installshield,还一窍不通),最后问了技术支持才得以解决。
切换去Installation Designer,找到Installation Information | General Information,看到String Tables下面是什么?对,全部你选的语言都列出来了,选中你要的语言,右键,选择Make Default,OK,再切换回Project Assistant去把全部不要的语言通通去掉钩选便可。
看到此处,已经变成了English为默认语言了。
12. 点击引导条上的Build Installation。
打包安装盘的设置,本人历来不用这个选项,都用工具条上的Release Wizard。
至此,第一部分基本完成。若是是一些没有特别要求的安装包,这部分讲解的内容足够能够作一个基本的安装包了
在开始进行编程前,咱们先明确一下咱们要用编程来弥补前面设置的哪些功能的不足
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);之间的nResult = SdStartCopy( szTitle, szMsg, 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 = "";这两行代码若是赋值为空,则显示如图所示的默认信息,用户能够赋值成本身想要显示的信息。
4. 根据用户选择的组件,从外部文件夹拷贝相应的文件到安装目标路径的文件夹中
这个用途常见于配置文件和受权文件的应用,同一程序,受权给不一样的用户,只须要不一样的配置和受权文件。若是将配置和受权文件每次都打包在安装程序里,那么变动一个用户就须要从新打包一次,这是一个浪费时间和精力的行为。若是将受权和配置文件(固然内容是加密过的)放在外部文件夹中,每次安装的时候从这个文件夹中读取拷贝,那么会是一个比较通用型的安装程序。
另外,本程序的好几个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. 这段代码在After 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. 这段代码在After 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,则说明源字符串中找不到要查找的字符串 **************************************************************************** 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 = "\"" + szmsg1 + "\"" +szmsg2; 拼凑出正确的可执行文件的长文件名,带路径,包含扩展名 **************************************************************************** AddFolderIcon(FOLDER_PROGRAMS^"Test","Uninstall",szfilename,WINDIR,"",0,"",REPLACE); 添加一个快捷方式到开始 | 全部程序 | Test下;照抄便可。 小结:可能读者会比较奇怪这一段代码的写法,由于中间那段if endif;代码看上去简直就是画蛇添足。在Installshield7以前,一直是这样写的: szfilename = UNINSTALL_STRING +" /UNINSTALL"; AddFolderIcon(FOLDER_PROGRAMS^"Test","Uninstall",szfilename,WINDIR,"",0,"",REPLACE);从Installshield8开始,长文件名一直有引号封闭不正确的问题,所以if endif;代码彻底是为了解决这个问题而存在的,而上面提到的两个脱去引号的语句,是笔者在前人基础上修改加上的,由于发现解析出来的字串要是不脱一下括号仍是有问题。 这个快捷方式运行的时候,出现界面和在安装完毕后再次运行安装程序出现的界面相同。选择Remove便可进行卸载。
这个卸载不会把程序运行时产生的文件卸载掉,好比日志文件、配置信息文件等;会把安装目录中全部从安装程序中安装的文件都卸载掉,包括安装时从外部拷贝的文件。利用Project Assistant建立的卸载快捷方式则没法卸载掉安装时从外部拷贝的文件。
在作完这个安装程序后,觉得能够结束了,没想到经理又提出了一个新的要求,由于以前的安装里(参阅第二部分的第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 +" /REMOVEONLY"; 就能够了;可是试验出来是不等我确认删除,这个服务就卸载掉了,缘由是这个界面一出来就是MAINTENANCE状态,而程序捕获了这个状态后,是无论我是否按下了确认按钮就会去作这个操做了。
后来想在Onbegin里添加一个SdWelcomeMaint函数的判断,结果是判断却是成功的,可是多了另外一个重复界面。
看来这个思路多是有问题的,而后满地google之,仍是吞硬币的小猪的一篇文章给了启发,原文地址找不到了,只找到了这篇http://school.ogdev.net/ArticleShow.asp?id=1699&categoryid=7,这里面实际上是谈反安装时候不执行OnMaintUIBefore函数的问题,我想既然这个函数是反安装时候“应该执行的”,那么就看看这个函数吧。
因而 打开Before Move Data | OnMainUIBefore
打开一看,大喜过望,这个函数里明明白白地显示了反安装时候的全部界面。
因而顺着向下看,找到Dlg_SdFeatureTree。
这里红色圈出来的一行代码明确地告诉咱们:若是为反安装状态,那么卸载全部组件!OK,代码只要添在这里就能够了。
这里就运用了一个函数LaunchAppAndWait来达到目的。其实一开始我还在想是否是要写批处理文件来执行呢,结果是不须要,直接写在这个函数里就能够了。
LaunchAppAndWait ( szProgram, szCmdLine, nOptions ); 参数一:szProgram,要运行的程序。在Help里有这样一句解释:想在命令行里指定要运行的程序,那么能够对这个参数传空值 参数二: szCmdLine,命令行参数;很奇妙的参数,这里咱们就能够写入咱们想要的批处理语句了。 参数三:静态变量,操做类型,这里LAAW_OPTION_HIDDEN可使批处理窗口隐藏掉,若是使用了LAAW_OPTION_WAIT,就会看到一个命令行窗口一闪而过,让人十分不爽。
因而,折腾了一下午的问题,就靠这短短的两分钟就解决了…
1. 修改显示界面的风格
Installshield 原始安装界面我始终以为很丑,幸亏Installscript 是能够不用写代码就能够改界面风格的。
在 Installation Designer 的左边导航树上找到User Interface | Dialogs
Skins 选项下面显示了不一样的界面风格,默认是None,选择一个喜爱的风格便可,笔者通常使用Blue 或BlueTC,适用于通常商业软件的稳
重风格;Midnight 比较酷,要是作电脑游戏的安装程序,我必定会选这个风格。
2. 编译打包
一切都准备就绪以后,就差一个编译打包成实际的安装程序的过程了。
编译
编译可使用工具栏上的 ,检查一下有无定义错误,编译错误等。
打包
打包可使用工具栏上的 。
1. 第一步,指定一个打包的配置版本,若是使用同一个安装程序源来打包成不一样的配置,就能够选择新的配置版本。这里的配置指的
是安装程序自己的配置,包括是否压缩文件、打包成网络安装程序或光盘安装程序、安装程序的客户信息、是否加密等等。
2. 同一配置下容许多个版本的存在;通常笔者习惯于当安装源文件升级的时候,就打包一个新版本出来以示区别,同时也能够保留老
版本的安装程序备用不时之需。
3. 过滤设置,笔者历来没用过。貌似是对feature 的一些设置参数,大概是针对某些版本,若是某些feature 不须要的话,就直接过滤掉
了,安装时候没法选择安装了。不过参数具体怎么设置,包括下面的语言过滤,笔者还没有使用过。可是回想起之前安装的一些大型
软件好比Oracle 之类的,会有一些选项是灰色没法选择安装的,大概就是相似这种功能。
4. 安装程序的语言和被安装的应用程序的语言没有必然联系,所以这里还须要设置一下安装时所用的语言。选择列表里显示的语言取
决于在作安装程序时选择的语言,刚才咱们只选择了English,所以这里只显示English 了。若是选择了多项语言,那么在安装时出
现的第一个界面会是让你选择安装时所用语言的界面,很是智能。这种功能针对须要发布到多个语种国家的软件是很是有用的。不
过这样的话,在写脚本的时候,只要是显示在界面上的语言,除了系统能够默认显示的,都要多加一个语言判断,而且显示不一样的
内容了。
5. 选择介质类型,通常笔者都会把安装程序刻录到光盘上,所以选择 CD-ROM
6. 光盘选项,第一个选项 Automatic 会自动为你检测所需光盘的规格数量,以及制做出光盘之间的断点。我通常都是选第一项,免去
很多啰嗦事,第二个选项应该是高人才会选的吧。
7. 这个选项指定了打包时的形态:
Compress all files:全部的文件都压缩打包(这里不包括脚本里所写的安装时候从外部拷贝的文件,只包括在Project Assistant 里指定
到各个feature 下的文件)
Leave files uncompressed and separate from the installation package:全部的文件都不打包,以原始形态存在
Custom:定制,容许你指定一部分压缩打包,一部分散放。
笔者喜欢把全部的文件都打包,看上干净而且专业。
8. 对操做系统的要求,通常都默认便可
9. 这个是 Windows 安装引擎,对此没有研究过,凭着上面的解释选了第二个,会帮你打包进安装程序,这样就高枕无忧了。
10. 签名,目前用不到,有兴趣的话能够导入数字签名文件
11. 密码和版权声明。密码仍是算了吧,一个商业软件是不应这样设的,客户会骂死的。
12. 是否要包括.NET Framework,Installshield 看来和微软很和谐啊。
13. 编译出来的文件放置的位置,以及一些相关设置,建议长文件名这个选项要选上,以避免路径太深形成没必要要的问题。
14. 最后一步,显示以前所配置的设置,点击“完成”便可编译出一个安装程序来。
15. 编译好的安装程序文件应该在 工程路径\My Project Name\Product Configuration X\Release X\DiskImages\DISK1 下
点击 setup.exe,就能够安装了。
这里顺便说一下,这个安装程序虽然制做好了,可是若是这样光秃秃地拿给客户,客户是要对产品的印象打折扣的。可使用 Flash 或者其
他的专业光盘制做软件来制做一个漂亮的应用界面来提供给客户,在插入光盘的时候自动弹出一个漂亮的使用界面,不但印象深入,并且客
户使用起来也方便。
这篇补遗是《一个完整的安装程序实例—艾泽拉斯之海洋女神出品》的追加叙述,是在这个安装程序安装后发现应用程序运行时的一些小问题,经过安装程序中传递参数和设置环境变量来解决掉问题的,与安装程序自己无关。
这个程序作完以后,工程进入最后的调试阶段;里面发现了两个问题,其实和安装程序自己没有关系,可是均可以经过安装程序来解决,记录在此,以备之后碰到此类问题时能够查阅。
1. JAVA_HOME的问题
以前在第二部分的第九小节里提到了安装完毕后,为JDK设置一个环境变量,事实上这个文档写到这里的时候有一点搞错了前后顺序,由于须要这个环境变量的是第二部分第八小节里安装完毕后须要启动的那个程序,因此后来调试时候发现了,就把第八节的程序内容和第九节的程序内容调换了一下顺序。
可是,很快发现了新问题,在注册表里添加环境变量和在桌面上“个人电脑”里直接添加环境变量是不同的;注册表里的操做,都须要经过重启动计算机来使之生效,因此矛盾出现了:当这个程序启动的时候,环境变量尚未生效;而若是设置了让计算机重启动,就必须让客户手动启动这个程序,这是很是不友好的操做。
因此这里修改了一下方法,首先把须要启动的程序,也就是一个批处理文件,里面的
set JAVA_HOME=%JAVA_HOME%
这句话改为了
set JAVA_HOME=%1
在批处理里面,须要从外部接收参数的时候,能够把参数写成%1,%2…%n。
而后,在第八小节的程序基础上修改,把
if (LaunchApp (serviceTarget, "") < 0) then
这句话修改为
if (LaunchApp (serviceTarget, javahome) < 0) then
便可。
2. 代码解释
这里javahome就是第九小节里if(RegDBSetKeyValueEx(szKey, "JAVA_HOME", REGDB_STRING, svValue, -1)<0) then这句话里的svValue,即JDK的安装路径。
LaunchApp (serviceTarget, javahome)不能不佩服IS函数设计者,在这篇文档里,三个地方用到了LaunchApp这个函数,并且每一个用法都不一样。 参数一:这里写咱们要打开的文件,带相对路径的 参数二:cmd_line,这里,咱们写入了JDK的路径,这个值将做为参数传递给咱们要打开的批处理文件,批处理接收到JDK路径后,即可以正确启动了。
3. Path的问题
这个问题其实和JavaSerive以及操做系统相关;由于在笔者的计算机上一直没有发现这个问题。
在第二部分的第八小节中提到,咱们会向Windows安装一个服务,可是笔者在工程用的计算机上始终不能启动这个服务,此次这个调试任务推给了经理,他通过屡次试验,发现是环境变量中Path 的问题,只要在Path里添加上JRE的Bin文件路径,这个服务就能够正确启动了。由于没有深刻了解JavaSerive的运行机制和Windows服务的运行机制,也没有深究为何了,猜测多是这个服务须要找这个路径,而有的操做系统只要指定了JAVA_HOME就能够寻找JRE了,而有的却不行。
闲话休叙,咱们须要写一段程序来实现这个功能。
要注意的地方有如下几点:
l Path每每已存在,而且里面有内容,所以不能够像设置JAVA_HOME同样,而要考虑往已有内容中添加JRE路径,而且要考虑内容之间的分号问题
l 要考虑到卸载状态时,不能把Path卸载。说到这个问题,要提一下前面的第九小节,发现引文做者关于卸载时是否卸载本身添加的注册表键值的理解仍是不正确的,Help里关于RegDBSetKeyValueEx有这样一句话However, the newly created key is not logged for uninstallation unless it is a subkey of a key already logged for uninstallation.也就是新建立的键值不会被日志记录了要反卸载掉,除非它有子键值被日志记录了要卸载
程序内容仍然添加在OnEnd()里,写在最后,以下:
szKey = "SOFTWARE\\JavaSoft\\Java Runtime Environment\\1.6.0_04";//jre的键 RegDBSetDefaultRoot(HKEY_LOCAL_MACHINE);//设置一下根键 if (RegDBKeyExist(szKey)=1) then//若是这个键存在 if(RegDBGetKeyValueEx(szKey,"JavaHome",nvType,svValue,nvSize)=0) then //查找这个键的值 javaPath= svValue; endif; endif; **************************************以上为第一部分,如下为第二部分 //wirte the environment variable PATH szKey = "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"; //环境变量在注册表中所在位置 javaPath=javaPath+"\\bin";//jre\bin的路径 if(RegDBGetKeyValueEx(szKey,"Path",nvType,svValue,nvSize)=0)then//若是Path存在 if(svValue!="") then if(StrFind ( svValue, javaPath )<0) then//若是path尚未jre\bin的路径信息 svValue=svValue+";"+javaPath; //添加路径信息,此时要带上分号 endif; else svValue= javaPath;//若是键值为空,则直接添加便可,事实上键值为空的状况不会出现,这句话是无用的判断 endif; else svValue= javaPath; //若是没有这个键值,把值也直接添加进去,事实上这个状况也不会出现,由于path在操做系统安装完毕后就存在了,//除非你手动删除了,可是那样操做系统也会有问题了 endif; if(!MAINTENANCE)then Disable(LOGGING); if(RegDBSetKeyValueEx(szKey, "Path", REGDB_STRING_EXPAND, svValue, -1)<0) then//添加或者重设键值 MessageBox ("Path create failed, please set it manually!", SEVERE); endif; Enable(LOGGING); endif;
4. 代码解释
第一部分的目的在于找出JRE的安装路径,全部的函数以前都有解释,再也不赘述
第二部分:
szKey = "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"; 环境变量,位于注册表的这个位置 ************************************************************************* javaPath=javaPath+"\\bin"; 须要寻找的是JRE下的bin文件 ************************************************************************* if(RegDBGetKeyValueEx(szKey,"Path",nvType,svValue,nvSize)=0)then 若是Path存在并返回了值。 这里其实若是加上一个判断此键是否存在,代码会更加完善,不过操做系统装完以后这个键是必定存在的,这里笔者偷懒了 ************************************************************************* if(StrFind ( svValue, javaPath )<0) then 判断返回的键值里是否包含jre的bin文件夹的路径 StrFind(szString, strFineMe) 在源字串里查找是否包含指定的字串 参数一: szString,被查找的源字串 参数二:strFineMe,要查找的字串 若是包含要查找的字符串,则返回要查找的字符串在源字符串里的位置;若是查找不到则返回小于0的随机数字 ************************************************************************* if(!MAINTENANCE)then 判断一下安装状态,使之只有在非维护(修改,从新安装,卸载)状态使起做用 ************************************************************************* Disable(LOGGING); Enable(LOGGING); 这两句话做用分别是中止日志记录和使日志记录从新生效,这是从网上的一篇心得里抄录的,当日志中止记录时候,安装程序就无视了中止日志后的全部操做,这样保证键值不会在反安装时候被操做;其实原本上面一句if(!MAINTENANCE)then(非维护状态时操做)在原文是没有加上去,做者说只要不让日志记录到操做注册表,这个操做就不会被反安装掉,不过好像本身试了一下不行,加上一句,比较保险些,至少试验出来是没问题的。 ************************************************************************* if(RegDBSetKeyValueEx(szKey, "Path", REGDB_STRING_ EXPAND, svValue, -1)<0) 写注册表的键值。 Help里RegDBSetKeyValueEx的对应帮助里有这样一句话If the value data already exists, RegDBSetKeyValueExoverwrites it,也就是,若是键值存在,那么覆盖它。不过对注册表操做后,要把计算机从新启动才能生效;这一点和咱们直接在“个人电脑”里操做环境变量是不同的。 这里,第三个参数改为了REGDB_STRING_ EXPAND,由于在Windows 2003server下,path含有一个%SystemRoot%的相对路径,当时一开始使用了REGDB_STRING,结果无心中发现全部的dos命令都用不了了,在dos下输入path一看,该替换成绝对路径的地方都没有替换掉,当时也是一头雾水,上CSDN一问,有人提醒说我应该看一下IS程序里键值设置时候的类型问题,跑回来一看果真设置有问题,REGDB_STRING是不认识相对路径的,换成REGDB_STRING_ EXPAND就能够了。
5. 文件的只读性问题
用光盘装程序的时候发现一个问题,当从光盘上拷贝出文件的时候,文件会默认为只读格式,致使配置文件不能正常存储数据库信息,所以,在安装程序代码里拷贝完文件后,指定一下文件的属性
SetFileInfo ( szPathFile, nType, nAttribute, szValue ); 此处用做SetFileInfo ( szPathFile, FILE_ATTRIBUTE, FILE_ATTR_NORMAL, "" );
Q: 如何替换setup.exe的图标?
A: 这不是一个推荐的操做,由于可能会引发不可预见的错误,并且IS没有开放这个接口。若是你坚持要这么作,可使用第三方软件好比ExeScope来进行图标替换。
Q: 如何去掉安装界面左上方的Installshield Wizard字样?
A: Installation Designer -> Installation Information -> General Information -> String Tables ->你使用的语言,右键-> Export String Tables导出为文本文件,而后把里面相应的“Installshield Wizard”字段替换成空白字符串便可。建议作好备份后再修改。
Q: 如何自定义每一个安装界面的标题或者说明文字?
A: Installation Designer -> Behavior and Logic -> InstallScript ->Setup.rul,没有这个文件的话在file底下new一个,默认就是这个名字的,我是在Installscript msi类型下测试的,其余的可能略有不一样。
在Setup.rul打开OnFirstUIBefore函数,这个函数里面显示出安装过程当中的全部界面,在须要修改的界面中把标题或者说明性文字赋值为本身想要的值便可。
举例:
Dlg_SdWelcome: szTitle = ""; szMsg = ""; nResult = SdWelcome(szTitle, szMsg); if (nResult = BACK) goto Dlg_SdWelcome; szTitle = ""; svName = ""; svCompany = "";
szTitle就是安装时候欢迎界面所显示的标题,好比我改为szTitle=”欢迎使用XX软件”这样的形式便可。
Q: 如何在卸载时不要弹出”Modify, Repair, Remove”界面,而是点击卸载后直接卸载掉?
A: 在Installation Designer->Behavior and Logic->InstallScrip中Setup.rul里打开OnMaintUIBefore这个函数,找到Dlg_Start:,把这个框体包含的代码所有注释掉,也就是从Dlg_Start:到Dlg_SdFeatureTree:上面一行的内容所有去掉,而且添加一句nType = REMOVEALL;在Dlg_SdFeatureTree:前面,这样就能够实现不出现那个“modify,repair, remove"的界面,而是直接进行删除动做了。
Q: 我怎样在目标机上安装.NET,若是目标机上没有安装的话?
A: 在Release Wizard的倒数第三步将”Include or setup .NET framework”的选项勾上。
Q: 我如何调用第三方软件?
A: 在Installation Designer -> Behavior and Logic -> InstallScript ->Setup.rul里使用LaunchAppAndWait或者LaunchApp函数,至于写在哪儿要看具体应用,好比能够写在OnBegin, OnFirstUIBefore之类的函数体里。例子请参阅个人另一个文档《一个完整的Installshield安装程序—艾泽拉斯之海洋女神出品》,csdn有下载。或者你也能够加入installshield中文论坛官方QQ群来获取这份放在群共享里的文件。
在笔者写这篇文档的时候,有网友告诉我Installshield2009已经无需如此繁琐地写脚本了,而是有界面容许用户本身指定一个注册表键值,并指定须要安装的软件所在的路径,当键值不存在的时候IS自动调用指定的软件了,有兴趣的朋友能够去研究下。
Q: 我如何调用bat文件?
A: 在Installation Designer -> Behavior and Logic -> InstallScript ->Setup.rul里使用LaunchAppAndWait或者LaunchApp函数。
Q: 我如何为本身的程序建立一个在开始菜单里的卸载快捷方式?
A: 某些工程类型好比Installscript MSI自带有这个选项,可是在08版本前都不推荐使用,由于容易致使系统崩溃;写脚本是一个不错的解决方法。代码以下,可是具体的详细解释请参考个人另一个文档《一个完整的Installshield安装程序—艾泽拉斯之海洋女神出品》,csdn有下载。或者你也能够加入installshield中文论坛官方QQ群来获取这份放在群共享里的文件。
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);
Q: 我如何修改“添加或删除程序”里个人软件的卸载图标?
A: Project Assisant -> Application Information,Select the icon to display your application in Add or Remove Program这项,点击Browse选择你想要的图标便可。