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選擇你想要的圖標便可。