使用Electron开发了一个软件,为了方便,将运行中生成的一些文件(工作目录)放在了userData目录下,因为默认userData在windows系统是在C盘,而且生成的文件还很大,为了防止把C盘撑满,考虑安装时可由用户选择安装至其他位置。
经过一番搜索和左拼右凑后,写了如下nsis脚本,算是实现了此功能。
!addplugindir Plugins
!include nsDialogs.nsh
!include InstallOptions.nsh
!include LogicLib.nsh
; 在选择安装目录后设置数据保存目录页面
!macro customPageAfterChangeDir
; 定义常量和变量
!define PRODUCT_UNINST_ROOT_KEY "HKLM"
!define SWP_NOMOVE 0x0002
!define SWP_NOZORDER 0x0004
; typedef struct _RECT {
; LONG left;
; LONG top;
; LONG right;
; LONG bottom;
; } RECT, *PRECT;
!define stRECT "(i, i, i, i) i"
; 定义数据目录位置
Var DataDir
Var HWND
; 自定义数据目录选择页面
Page Custom MyPageCreate
Function MyPageCreate
;读取之前安装的值
ReadRegStr $R0 ${PRODUCT_UNINST_ROOT_KEY} "${INSTALL_REGISTRY_KEY}_data" "DataDir"
${If} $R0 != ""
StrCpy $DataDir $R0
${Else}
StrCpy $DataDir "C:\ProgramData\${APP_PACKAGE_NAME}" ; 默认数据目录
${EndIf}
; 更新安装时跳过选择数据目录
${ifnot} ${isUpdated}
InitPluginsdir
; A real installer would not write the .ini like this, it would extract a file
WriteIniStr "$PLUGINSDIR\datadir.ini" Settings NumFields 3
WriteIniStr "$PLUGINSDIR\datadir.ini" "Field 1" Type Label
WriteIniStr "$PLUGINSDIR\datadir.ini" "Field 1" Text "选择数据保存目录"
WriteIniStr "$PLUGINSDIR\datadir.ini" "Field 1" Left 0
WriteIniStr "$PLUGINSDIR\datadir.ini" "Field 1" Right 310
WriteIniStr "$PLUGINSDIR\datadir.ini" "Field 1" Top 0
WriteIniStr "$PLUGINSDIR\datadir.ini" "Field 1" Bottom 26
WriteIniStr "$PLUGINSDIR\datadir.ini" "Field 2" Type GroupBox
WriteIniStr "$PLUGINSDIR\datadir.ini" "Field 2" Text "数据保存目录"
WriteIniStr "$PLUGINSDIR\datadir.ini" "Field 2" Left 0
WriteIniStr "$PLUGINSDIR\datadir.ini" "Field 2" Right 298
WriteIniStr "$PLUGINSDIR\datadir.ini" "Field 2" Top 70
WriteIniStr "$PLUGINSDIR\datadir.ini" "Field 2" Bottom 106
WriteIniStr "$PLUGINSDIR\datadir.ini" "Field 3" Type DirRequest
WriteIniStr "$PLUGINSDIR\datadir.ini" "Field 3" State $DataDir
WriteIniStr "$PLUGINSDIR\datadir.ini" "Field 3" Left 10
WriteIniStr "$PLUGINSDIR\datadir.ini" "Field 3" Right 254
WriteIniStr "$PLUGINSDIR\datadir.ini" "Field 3" Top 84
WriteIniStr "$PLUGINSDIR\datadir.ini" "Field 3" Bottom 97
!insertmacro INSTALLOPTIONS_INITDIALOG "datadir.ini"
; 修改数据目录浏览按钮文字
Pop $HWND
; Get HWND of browse button.
GetDlgItem $R0 $HWND 1203
; Set its text.
SendMessage $R0 ${WM_SETTEXT} "" "STR:浏览(B)..."
; Get the dimensions of the button.
System::Call `*${stRECT} .R1`
System::Call `user32::GetClientRect(i $R0, i R1)`
System::Call `*$R1${stRECT} (, , .R2, .R3)`
; Change the button width.
IntOp $R2 $R2 * 3
; Set the new width.
System::Call `user32::SetWindowPos(i $R0, i 0, i 0, i 0, i $R2, i $R3, i ${SWP_NOMOVE} | ${SWP_NOZORDER})`
System::Free $R1
; Display the page.
!insertmacro INSTALLOPTIONS_SHOW
Pop $R0
${endif}
FunctionEnd
Section -Post
; 更新安装时跳过选择数据目录
${ifnot} ${isUpdated}
; 数据目录写入注册表
ReadINIStr $DataDir "$PLUGINSDIR\datadir.ini" "Field 3" State
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${INSTALL_REGISTRY_KEY}_data" "DataDir" $DataDir
${endif}
SectionEnd
!macroend
; 启动应用
!macro RunApp
${StdUtils.ExecShellAsUser} $0 "$launchLink" "open" ""
!macroend
!macro customInstall
; 数据目录存入配置文件
WriteIniStr "$INSTDIR\config.ini" "dir" DataDir $DataDir
!macroend
因为使用electron-builder打包,在其nsis代码中找到了customPageAfterChangeDir这个宏,从名字看起来是自定义选择安装目录后的页面,正好符合需求,然后向$PLUGINSDIR\datadir.ini文件写入页面上的控件定义,再由
!insertmacro INSTALLOPTIONS_INITDIALOG "datadir.ini"`
生成页面,包含一段文字描述,一个GroupBox,一个目录选择控件。
之后在Section -Post里将选择的目录写入注册表,这里没有写入默认的INSTALLREGISTRYKEY而是另建了一个{INSTALL_REGISTRY_KEY}而是另建了一个INSTALLREGISTRYKEY而是另建了一个{INSTALL_REGISTRY_KEY}_data是因为首次安装注册表可以找到写入的数据目录,一旦覆盖安装,数据目录直接消失了,没找到原因,可能是更新安装的时序没搞顺,干脆另立门户,存到${INSTALL_REGISTRY_KEY}_data里,这下咋样都不会被清掉了。
同时Electron更改userData需要在app ready事件触发前调用app.setPath(‘userData’, ‘dir’),找了下读取注册表的库,基本都是异步的,无法保证这个条件,所以在nsis脚本里又向安装目录下的config.ini写了一次数据目录的值,这样使用fs.readFileSync可以同步读取文件并调用app.setPath设置userData。打包后测试安装时能正常选择,启动时也能读取并设置到指定目录,在此记录下。