Electron App 安裝包客製化 -- Inno Setup 指令碼 Pascal Scripting 初探

2023-06-29 06:01:05

在做 Electron 專案時,有個需求是安裝包安裝時要給客戶機上裝上某個軟體

在檢視 Inno Setup 官網後發現是通過 .iss 指令碼編寫實現自定義安裝過程

可在 .iss 內可以新增指令碼為安裝過程新增邏輯

為了測試方便我用 vite 新建一個全新的 electron 專案

用的是這個腳手架 https://github.com/electron-vite/electron-vite-vue

用其它腳手架也行,反正我們對 app 內容本身並不關注,只關心製作安裝包

新建 electron 專案

安裝注意事項(都是我踩過的坑..)

yarn config get registry

我用的是這個 https://registry.npm.taobao.org/

yarn create electron-vite

專案名 first-electron

模板我選了 vue ,其實無所謂我們只是跑個electron demo 目的是打包而不是開發

Project name: first-electron

Project template: Vue

成功後

 cd first-electron
 yarn
 yarn dev

專案應該跑起來了可以看到 electron 視窗

關掉專案, 下一步測一下打包功能

編譯打包 app

專案已經幫你整合了 electron-builder

直接執行打包命令

yarn build

大概率會失敗

從 github 拉 electron 失敗

需要從映象伺服器,設定 electron_mirror 到專門映象 後再打包

yarn config set electron_mirror https://npm.taobao.org/mirrors/electron/
yarn build

成功後在專案 first-electron\release 目錄下就是編譯完成的 electron app 了

我的是在 first-electron\release\0.0.0 目錄下,0.0.0 一看就是軟體版本號了

專案根目錄的 electron-builder.json5 就是 electron-builder 組態檔

組態檔內的 "output": "release/${version}" 就是定義編譯後的檔案目錄,一般不會去改

first-electron_0.0.0.exe 這個檔案就是單獨檔案的安裝版,雙擊安裝時會有安裝過程

win-unpacked 就是綠色版, 安裝包的製作就是把這個資料夾內的檔案進行打包

安裝包自定義安裝過程

如果想對安裝包安裝過程進行自定義

那麼首先電腦上先安裝 Inno Setup Compiler, 再建立 setup.iss 檔案

通過 ide 新建 .iss 檔案

過程如下:

  1. 開啟 Inno Setup Compiler

  2. 新建一個 Inno Setup Script Wizard 開始建立 .iss 指令碼檔案

  3. 按提示一步步往下走,可以選擇填寫 app 名字,版本,公司之類的資訊這些都不重要

  4. 直到 Application Files

    Application main executable file 原預設的 Myprog.exe 主執行檔案變更到我們自己 first-electron 的主執行檔案,如下:

      first-electron\release\0.0.0\win-unpacked\first-electron.exe
    

    另外 Other application files: 這一項點選 Add folder... 按鈕

    把 win-unpacked 整個資料夾也新增進來

    繼續按提示往下走,直到 Compiler Settings 這一頁

  5. Custom compiler output folder: 選擇打包檔案輸出目錄

    我選擇輸出到 first-electron\dist-setup 目錄

  6. 繼續按提示往下走,最後會讓你儲存成 .iss 檔案,這個 .iss 檔案就是打包指令碼了

我在這個例子中輸出檔案儲存為了 setup.iss

先不動這個指令碼。直接編譯,完成後可以在 first-electron\dist-setup 看到 windows 安裝包了

安裝程式完成後呼叫其它程式

比如主程式安裝完後想自動執行安裝東方財富的的安裝檔案 「dfcft8.exe」

(別問為什麼是 「東方財富」 這個安裝包,我的電腦的下載資料夾內剛好看到有這個.exe 執行檔案就用這個來測試吧)

為什麼要做這麼流氓的事? 這只是舉個例子而已!

其實,真實專案中有可能是客戶機器上缺少某種環境或檔案之類的東西,那麼你可以利用此方法幫使用者安裝上

讓我們開始吧。 非常的簡單!

修改之前儲存的 setup.iss 檔案

在 setup.iss 檔案中找到 [Run] 節點新增如下程式碼:

[Run]
Filename: "{app}\resources\bin\dfcft8.exe"; Description: ""; Flags: nowait postinstall skipifsilent

使用 Inno setup compiler 編譯該檔案

安裝程式最後會顯示這樣的畫面:

點選 「完成」 按鈕就會出現以下畫面:

非常簡單!

能不能在安裝開始前就呼叫某個程式?

上面說的是安裝程式安裝完成後執行另外的一個程式,那麼如果想在安裝程式安裝前執行呢?

[code]節點指令碼控制安裝過程

需要 Pascal 指令碼了, 開始吧!

說實話作為長期使用 Javascript 的我來說第一次接觸 Pascal Scripting 我心裡一萬頭xx馬奔騰而過

而且我看 Inno Setup Compiler 官網 <jrsoftware.org> 的大概意思是 Pascal Scripting 不太好弄

官方建議使用者從 Inno Setup 6\Examples 安裝目錄下的 Examples 目錄下參考官方提供的指令碼例子

在 setup.iss 檔案內新增 [code] 節點

這個節點允許你為安裝過程新增邏輯,可細化到安裝的每一步

它通過在 [code] 節點下暴露 Event Functions 實現,比如:

  • function InitializeSetup(): Boolean;
  • procedure DeinitializeSetup();
  • function InitializeUninstall(): Boolean;
  • ...

我姑且把它理解為生命週期吧!

那麼如果想在安裝開始時就呼叫

使用 InitializeSetup 並在其內用 Exec 方法呼叫 'dfcft8.exe'

下面實現安裝過程開始之前前呼叫

[code]
function InitializeSetup(): Boolean;
var
  ResultCode: Integer;
begin
  ExtractTemporaryFile('dfcft8.exe');
  Exec(ExpandConstant('{tmp}\dfcft8.exe'), '', '', SW_SHOW, ewWaitUntilTerminated, ResultCode)
  Log('InitializeSetup called');
  Result := true
end;

為 setup.iss 新增上面程式碼後用 Inno Setup Compiler Ide 按 F9 或 Run 選單 Run選項 "編譯並啟動"

安裝包啟動後點選"確定"按鈕,Ide 會輸出紀錄檔,也可以看到東方財富的安裝程式也啟動了

注意紀錄檔輸出畫面中的 這兩句:

[15:19:26.074]   Created temporary directory: C:\Users\ADMINI~1\AppData\Local\Temp\is-8P52P.tmp
[15:19:26.082]   Extracting temporary file: C:\Users\ADMINI~1\AppData\Local\Temp\is-8P52P.tmp\dfcft8.exe

意思是先建個臨時資料夾,再從安裝包中先解壓出 "dfctt8.exe" 這個檔案放到臨時資料夾內

此時安裝目錄內是取不到檔案的, 因為我們的 first-electron 程式還沒有安裝成功,也不會在客戶電腦上有安裝目錄

只能從安裝包這個壓縮檔案內解壓獲取臨時檔案

這是 setup.iss 現在的程式碼


; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!

#define MyAppName "first-electron"
#define MyAppVersion "1.5"
#define MyAppPublisher "My Company, Inc."
#define MyAppURL "https://www.example.com/"
#define MyAppExeName "first-electron.exe"
#define MyAppAssocName MyAppName + " File"
#define MyAppAssocExt ".myp"
#define MyAppAssocKey StringChange(MyAppAssocName, " ", "") + MyAppAssocExt

[Setup]
; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{72C0FB3C-108B-4530-8D27-5D31B5349C3C}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={autopf}\{#MyAppName}
ChangesAssociations=yes
DisableProgramGroupPage=yes
; Uncomment the following line to run in non administrative install mode (install for current user only.)
;PrivilegesRequired=lowest
OutputDir=C:\Users\Administrator\Desktop\first-electron\dist-setup
OutputBaseFilename=first-electron-setup
Compression=lzma
SolidCompression=yes
WizardStyle=modern

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"

[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked

[Files]
Source: "C:\Users\Administrator\Desktop\first-electron\release\0.0.0\win-unpacked\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\Users\Administrator\Desktop\first-electron\release\0.0.0\win-unpacked\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files

[Registry]
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocExt}\OpenWithProgids"; ValueType: string; ValueName: "{#MyAppAssocKey}"; ValueData: ""; Flags: uninsdeletevalue
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}"; ValueType: string; ValueName: ""; ValueData: "{#MyAppAssocName}"; Flags: uninsdeletekey
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},0"
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1"""
Root: HKA; Subkey: "Software\Classes\Applications\{#MyAppExeName}\SupportedTypes"; ValueType: string; ValueName: ".myp"; ValueData: ""

[Icons]
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon

[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
; Filename: "{app}\resources\bin\dfcft8.exe"; Description: ""; Flags: nowait postinstall skipifsilent



[code]
function InitializeSetup(): Boolean;
var
  ResultCode: Integer;
begin
  ExtractTemporaryFile('dfcft8.exe');
  Exec(ExpandConstant('{tmp}\dfcft8.exe'), '', '', SW_SHOW, ewWaitUntilTerminated, ResultCode);
  Log('InitializeSetup called');
  Result := true
end;

這裡有個疑問,ExtractTemporaryFile('dfcft8.exe') 路徑

這裡可沒指定過 'dfcft8.exe' 檔案位置,而且我明明是把它放在了 resources\bin\dfcft8.exe 下,難道會自動搜尋?

測試一下猜想

為了測試我把用另一個 "electron-fiddle.exe" 安裝包放在 resources 資料夾並且名稱改為同樣的 'dfcft8.exe' ,這樣就有兩個同名檔案在不同資料夾下

resources\bin\'dfcft8.exe' 
resources\'dfcft8.exe' 

編譯測試結果:

果然被改名為 'dfcft8.exe' 的 'electron-fiddle.exe' 被啟動代替了東方財富的安裝程式

官網我找不到資料,但測試後得到的結論是 ExtractTemporaryFile 會返回搜尋到的第一個匹配檔案

如果非要解壓指定資料夾下的指定檔案呢?

那麼需要換種方法,像下面這樣改:

[Files]
Source: "C:\Users\Administrator\Desktop\first-electron\release\0.0.0\win-unpacked\resources\bin\dfcft8.exe"; DestDir: "{tmp}\resources\bin"

[code]
function InitializeSetup(): Boolean;
var
  ResultCode: Integer;
begin
  ExtractTemporaryFiles('{tmp}\resources\bin\dfcft8.exe')
  Exec(ExpandConstant('{tmp}\resources\bin\dfcft8.exe'), '', '', SW_SHOW, ewWaitUntilTerminated, ResultCode);
  Log('InitializeSetup called');
  Result := true
end;

[Files] 節點指定 DestDir: "{tmp}\resources\bin"

[code] 節點內使用 ExtractTemporaryFiles('{tmp}\resources\bin\dfcft8.exe') 解壓

並且 Exec 方法呼叫時也要傳路徑 ExpandConstant('{tmp}\resources\bin\dfcft8.exe')

編譯測試一下

可以看到已經執行呼叫東方財富的安裝程式

先別急著關掉,我們可以通過 log 輸出的臨時路徑資訊,找到對應的資料夾開啟檢視 C:\Users\ADMINI~1\AppData\Local\Temp\is-0467N.tmp

[17:15:15.953]   Created temporary directory: C:\Users\ADMINI~1\AppData\Local\Temp\is-0467N.tmp

果然臨時資料夾內資料夾結構如我們所指定的那樣

該路徑會在安裝程式結束時自動刪除

用 [code] 實現安裝完成後執行 "dfcft8.exe" 可執行檔案

之前是在 [Run] 節點下實現,其實也可以用 procedure DeinitializeSetup() 實現類似的功能

改動如下:

[code]
function InstallDfcf: Boolean;
var
  ResultCode: Integer;
begin
  if not Exec(ExpandConstant('{app}\resources\bin\dfcft8.exe'), '', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then
  begin
    Log('安裝東方財富失敗. Error code: ' + IntToStr(ResultCode));
  end;
end;

procedure DeinitializeSetup();
begin
  InstallDfcf;
end;

函數 DeinitializeSetup 就是安裝結束時呼叫自定義函數 InstallDfcf

InstallDfcf 函數內部的 Exec 呼叫了 'dfcft8.exe'

還有更多..

Inno Setup 其實可客製化化的功能還有很多,官網檔案就仁者見仁智者見智了

全面詳細學習 Inno Setup 沒啥意義,用哪個功能直接瞭解這一塊兒就行,就是學習各種設定

現在有 chatgpt,還記啥設定呢,不明白可以直接問它,chatgpt 真是事半功倍啊,反正我就是這麼做的

我們該把時間用於更有意義的地方,比如摸魚!


部落格園: http://cnblogs.com/willian/
github: https://github.com/willian12345/