Prechádzať zdrojové kódy

fix: Solve the confliction issues on auto updater failure (#273)

Lai Zn 1 rok pred
rodič
commit
0fd8174e77

+ 756 - 0
scripts/windows/installer.nsi

@@ -0,0 +1,756 @@
+; This file is copied from https://github.com/tauri-apps/tauri/blob/tauri-v1.5/tooling/bundler/src/bundle/windows/templates/installer.nsi
+; and edit to fit the needs of the project. the latest tauri 2.x has a different base nsi script.
+
+Unicode true
+; Set the compression algorithm. Default is LZMA.
+!if "{{compression}}" == ""
+  SetCompressor /SOLID lzma
+!else
+  SetCompressor /SOLID "{{compression}}"
+!endif
+
+!include MUI2.nsh
+!include FileFunc.nsh
+!include x64.nsh
+!include WordFunc.nsh
+!include "LogicLib.nsh"
+!include "StrFunc.nsh"
+${StrCase}
+${StrLoc}
+
+!define MANUFACTURER "{{manufacturer}}"
+!define PRODUCTNAME "{{product_name}}"
+!define VERSION "{{version}}"
+!define VERSIONWITHBUILD "{{version_with_build}}"
+!define SHORTDESCRIPTION "{{short_description}}"
+!define INSTALLMODE "{{install_mode}}"
+!define LICENSE "{{license}}"
+!define INSTALLERICON "{{installer_icon}}"
+!define SIDEBARIMAGE "{{sidebar_image}}"
+!define HEADERIMAGE "{{header_image}}"
+!define MAINBINARYNAME "{{main_binary_name}}"
+!define MAINBINARYSRCPATH "{{main_binary_path}}"
+!define BUNDLEID "{{bundle_id}}"
+!define COPYRIGHT "{{copyright}}"
+!define OUTFILE "{{out_file}}"
+!define ARCH "{{arch}}"
+!define PLUGINSPATH "{{additional_plugins_path}}"
+!define ALLOWDOWNGRADES "{{allow_downgrades}}"
+!define DISPLAYLANGUAGESELECTOR "{{display_language_selector}}"
+!define INSTALLWEBVIEW2MODE "{{install_webview2_mode}}"
+!define WEBVIEW2INSTALLERARGS "{{webview2_installer_args}}"
+!define WEBVIEW2BOOTSTRAPPERPATH "{{webview2_bootstrapper_path}}"
+!define WEBVIEW2INSTALLERPATH "{{webview2_installer_path}}"
+!define UNINSTKEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCTNAME}"
+!define MANUPRODUCTKEY "Software\${MANUFACTURER}\${PRODUCTNAME}"
+!define UNINSTALLERSIGNCOMMAND "{{uninstaller_sign_cmd}}"
+!define ESTIMATEDSIZE "{{estimated_size}}"
+
+Name "${PRODUCTNAME}"
+BrandingText "${COPYRIGHT}"
+OutFile "${OUTFILE}"
+
+VIProductVersion "${VERSIONWITHBUILD}"
+VIAddVersionKey "ProductName" "${PRODUCTNAME}"
+VIAddVersionKey "FileDescription" "${SHORTDESCRIPTION}"
+VIAddVersionKey "LegalCopyright" "${COPYRIGHT}"
+VIAddVersionKey "FileVersion" "${VERSION}"
+VIAddVersionKey "ProductVersion" "${VERSION}"
+
+; Plugins path, currently exists for linux only
+!if "${PLUGINSPATH}" != ""
+    !addplugindir "${PLUGINSPATH}"
+!endif
+
+!if "${UNINSTALLERSIGNCOMMAND}" != ""
+  !uninstfinalize '${UNINSTALLERSIGNCOMMAND}'
+!endif
+
+; Handle install mode, `perUser`, `perMachine` or `both`
+!if "${INSTALLMODE}" == "perMachine"
+  RequestExecutionLevel highest
+!endif
+
+!if "${INSTALLMODE}" == "currentUser"
+  RequestExecutionLevel user
+!endif
+
+!if "${INSTALLMODE}" == "both"
+  !define MULTIUSER_MUI
+  !define MULTIUSER_INSTALLMODE_INSTDIR "${PRODUCTNAME}"
+  !define MULTIUSER_INSTALLMODE_COMMANDLINE
+  !if "${ARCH}" == "x64"
+    !define MULTIUSER_USE_PROGRAMFILES64
+  !else if "${ARCH}" == "arm64"
+    !define MULTIUSER_USE_PROGRAMFILES64
+  !endif
+  !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY "${UNINSTKEY}"
+  !define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME "CurrentUser"
+  !define MULTIUSER_INSTALLMODEPAGE_SHOWUSERNAME
+  !define MULTIUSER_INSTALLMODE_FUNCTION RestorePreviousInstallLocation
+  !define MULTIUSER_EXECUTIONLEVEL Highest
+  !include MultiUser.nsh
+!endif
+
+; installer icon
+!if "${INSTALLERICON}" != ""
+  !define MUI_ICON "${INSTALLERICON}"
+!endif
+
+; installer sidebar image
+!if "${SIDEBARIMAGE}" != ""
+  !define MUI_WELCOMEFINISHPAGE_BITMAP "${SIDEBARIMAGE}"
+!endif
+
+; installer header image
+!if "${HEADERIMAGE}" != ""
+  !define MUI_HEADERIMAGE
+  !define MUI_HEADERIMAGE_BITMAP  "${HEADERIMAGE}"
+!endif
+
+; Define registry key to store installer language
+!define MUI_LANGDLL_REGISTRY_ROOT "HKCU"
+!define MUI_LANGDLL_REGISTRY_KEY "${MANUPRODUCTKEY}"
+!define MUI_LANGDLL_REGISTRY_VALUENAME "Installer Language"
+
+; Installer pages, must be ordered as they appear
+; 1. Welcome Page
+!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
+!insertmacro MUI_PAGE_WELCOME
+
+; 2. License Page (if defined)
+!if "${LICENSE}" != ""
+  !define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
+  !insertmacro MUI_PAGE_LICENSE "${LICENSE}"
+!endif
+
+; 3. Install mode (if it is set to `both`)
+!if "${INSTALLMODE}" == "both"
+  !define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
+  !insertmacro MULTIUSER_PAGE_INSTALLMODE
+!endif
+
+
+; 4. Custom page to ask user if he wants to reinstall/uninstall
+;    only if a previous installtion was detected
+Var ReinstallPageCheck
+Page custom PageReinstall PageLeaveReinstall
+Function PageReinstall
+  ; Uninstall previous WiX installation if exists.
+  ;
+  ; A WiX installer stores the isntallation info in registry
+  ; using a UUID and so we have to loop through all keys under
+  ; `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall`
+  ; and check if `DisplayName` and `Publisher` keys match ${PRODUCTNAME} and ${MANUFACTURER}
+  ;
+  ; This has a potentional issue that there maybe another installation that matches
+  ; our ${PRODUCTNAME} and ${MANUFACTURER} but wasn't installed by our WiX installer,
+  ; however, this should be fine since the user will have to confirm the uninstallation
+  ; and they can chose to abort it if doesn't make sense.
+  StrCpy $0 0
+  !insertmacro CheckAllVergeProcesses
+
+  wix_loop:
+    EnumRegKey $1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" $0
+    StrCmp $1 "" wix_done ; Exit loop if there is no more keys to loop on
+    IntOp $0 $0 + 1
+    ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "DisplayName"
+    ReadRegStr $R1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "Publisher"
+    StrCmp "$R0$R1" "${PRODUCTNAME}${MANUFACTURER}" 0 wix_loop
+    ReadRegStr $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "UninstallString"
+    ${StrCase} $R1 $R0 "L"
+    ${StrLoc} $R0 $R1 "msiexec" ">"
+    StrCmp $R0 0 0 wix_done
+    StrCpy $R7 "wix"
+    StrCpy $R6 "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1"
+    Goto compare_version
+  wix_done:
+
+  ; Check if there is an existing installation, if not, abort the reinstall page
+  ReadRegStr $R0 SHCTX "${UNINSTKEY}" ""
+  ReadRegStr $R1 SHCTX "${UNINSTKEY}" "UninstallString"
+  ${IfThen} "$R0$R1" == "" ${|} Abort ${|}
+
+  ; Compare this installar version with the existing installation
+  ; and modify the messages presented to the user accordingly
+  compare_version:
+  StrCpy $R4 "$(older)"
+  ${If} $R7 == "wix"
+    ReadRegStr $R0 HKLM "$R6" "DisplayVersion"
+  ${Else}
+    ReadRegStr $R0 SHCTX "${UNINSTKEY}" "DisplayVersion"
+  ${EndIf}
+  ${IfThen} $R0 == "" ${|} StrCpy $R4 "$(unknown)" ${|}
+
+  nsis_tauri_utils::SemverCompare "${VERSION}" $R0
+  Pop $R0
+  ; Reinstalling the same version
+  ${If} $R0 == 0
+    StrCpy $R1 "$(alreadyInstalledLong)"
+    StrCpy $R2 "$(addOrReinstall)"
+    StrCpy $R3 "$(uninstallApp)"
+    !insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(chooseMaintenanceOption)"
+    StrCpy $R5 "2"
+  ; Upgrading
+  ${ElseIf} $R0 == 1
+    StrCpy $R1 "$(olderOrUnknownVersionInstalled)"
+    StrCpy $R2 "$(uninstallBeforeInstalling)"
+    StrCpy $R3 "$(dontUninstall)"
+    !insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(choowHowToInstall)"
+    StrCpy $R5 "1"
+  ; Downgrading
+  ${ElseIf} $R0 == -1
+    StrCpy $R1 "$(newerVersionInstalled)"
+    StrCpy $R2 "$(uninstallBeforeInstalling)"
+    !if "${ALLOWDOWNGRADES}" == "true"
+      StrCpy $R3 "$(dontUninstall)"
+    !else
+      StrCpy $R3 "$(dontUninstallDowngrade)"
+    !endif
+    !insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(choowHowToInstall)"
+    StrCpy $R5 "1"
+  ${Else}
+    Abort
+  ${EndIf}
+
+  Call SkipIfPassive
+
+  nsDialogs::Create 1018
+  Pop $R4
+  ${IfThen} $(^RTL) == 1 ${|} nsDialogs::SetRTL $(^RTL) ${|}
+
+  ${NSD_CreateLabel} 0 0 100% 24u $R1
+  Pop $R1
+
+  ${NSD_CreateRadioButton} 30u 50u -30u 8u $R2
+  Pop $R2
+  ${NSD_OnClick} $R2 PageReinstallUpdateSelection
+
+  ${NSD_CreateRadioButton} 30u 70u -30u 8u $R3
+  Pop $R3
+  ; disable this radio button if downgrading and downgrades are disabled
+  !if "${ALLOWDOWNGRADES}" == "false"
+    ${IfThen} $R0 == -1 ${|} EnableWindow $R3 0 ${|}
+  !endif
+  ${NSD_OnClick} $R3 PageReinstallUpdateSelection
+
+  ; Check the first radio button if this the first time
+  ; we enter this page or if the second button wasn't
+  ; selected the last time we were on this page
+  ${If} $ReinstallPageCheck != 2
+    SendMessage $R2 ${BM_SETCHECK} ${BST_CHECKED} 0
+  ${Else}
+    SendMessage $R3 ${BM_SETCHECK} ${BST_CHECKED} 0
+  ${EndIf}
+
+  ${NSD_SetFocus} $R2
+  nsDialogs::Show
+FunctionEnd
+Function PageReinstallUpdateSelection
+  ${NSD_GetState} $R2 $R1
+  ${If} $R1 == ${BST_CHECKED}
+    StrCpy $ReinstallPageCheck 1
+  ${Else}
+    StrCpy $ReinstallPageCheck 2
+  ${EndIf}
+FunctionEnd
+Function PageLeaveReinstall
+  ${NSD_GetState} $R2 $R1
+
+  ; $R5 holds whether we are reinstalling the same version or not
+  ; $R5 == "1" -> different versions
+  ; $R5 == "2" -> same version
+  ;
+  ; $R1 holds the radio buttons state. its meaning is dependant on the context
+  StrCmp $R5 "1" 0 +2 ; Existing install is not the same version?
+    StrCmp $R1 "1" reinst_uninstall reinst_done ; $R1 == "1", then user chose to uninstall existing version, otherwise skip uninstalling
+  StrCmp $R1 "1" reinst_done ; Same version? skip uninstalling
+
+  reinst_uninstall:
+    HideWindow
+    ClearErrors
+
+    ${If} $R7 == "wix"
+      ReadRegStr $R1 HKLM "$R6" "UninstallString"
+      ExecWait '$R1' $0
+    ${Else}
+      ReadRegStr $4 SHCTX "${MANUPRODUCTKEY}" ""
+      ReadRegStr $R1 SHCTX "${UNINSTKEY}" "UninstallString"
+      ExecWait '$R1 /P _?=$4' $0
+    ${EndIf}
+
+    BringToFront
+
+    ${IfThen} ${Errors} ${|} StrCpy $0 2 ${|} ; ExecWait failed, set fake exit code
+
+    ${If} $0 <> 0
+    ${OrIf} ${FileExists} "$INSTDIR\${MAINBINARYNAME}.exe"
+      ${If} $0 = 1 ; User aborted uninstaller?
+        StrCmp $R5 "2" 0 +2 ; Is the existing install the same version?
+          Quit ; ...yes, already installed, we are done
+        Abort
+      ${EndIf}
+      MessageBox MB_ICONEXCLAMATION "$(unableToUninstall)"
+      Abort
+    ${Else}
+      StrCpy $0 $R1 1
+      ${IfThen} $0 == '"' ${|} StrCpy $R1 $R1 -1 1 ${|} ; Strip quotes from UninstallString
+      Delete $R1
+      RMDir $INSTDIR
+    ${EndIf}
+  reinst_done:
+FunctionEnd
+
+; 5. Choose install directoy page
+!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
+!insertmacro MUI_PAGE_DIRECTORY
+
+; 6. Start menu shortcut page
+!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
+Var AppStartMenuFolder
+!insertmacro MUI_PAGE_STARTMENU Application $AppStartMenuFolder
+
+; 7. Installation page
+!insertmacro MUI_PAGE_INSTFILES
+
+; 8. Finish page
+;
+; Don't auto jump to finish page after installation page,
+; because the installation page has useful info that can be used debug any issues with the installer.
+!define MUI_FINISHPAGE_NOAUTOCLOSE
+; Use show readme button in the finish page as a button create a desktop shortcut
+!define MUI_FINISHPAGE_SHOWREADME
+!define MUI_FINISHPAGE_SHOWREADME_TEXT "$(createDesktop)"
+!define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateDesktopShortcut
+; Show run app after installation.
+!define MUI_FINISHPAGE_RUN "$INSTDIR\${MAINBINARYNAME}.exe"
+!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
+!insertmacro MUI_PAGE_FINISH
+
+; Uninstaller Pages
+; 1. Confirm uninstall page
+Var DeleteAppDataCheckbox
+Var DeleteAppDataCheckboxState
+!define /ifndef WS_EX_LAYOUTRTL         0x00400000
+!define MUI_PAGE_CUSTOMFUNCTION_SHOW un.ConfirmShow
+Function un.ConfirmShow
+    FindWindow $1 "#32770" "" $HWNDPARENT ; Find inner dialog
+    ${If} $(^RTL) == 1
+      System::Call 'USER32::CreateWindowEx(i${__NSD_CheckBox_EXSTYLE}|${WS_EX_LAYOUTRTL},t"${__NSD_CheckBox_CLASS}",t "$(deleteAppData)",i${__NSD_CheckBox_STYLE},i 50,i 100,i 400, i 25,i$1,i0,i0,i0)i.s'
+    ${Else}
+      System::Call 'USER32::CreateWindowEx(i${__NSD_CheckBox_EXSTYLE},t"${__NSD_CheckBox_CLASS}",t "$(deleteAppData)",i${__NSD_CheckBox_STYLE},i 0,i 100,i 400, i 25,i$1,i0,i0,i0)i.s'
+    ${EndIf}
+    Pop $DeleteAppDataCheckbox
+    SendMessage $HWNDPARENT ${WM_GETFONT} 0 0 $1
+    SendMessage $DeleteAppDataCheckbox ${WM_SETFONT} $1 1
+FunctionEnd
+!define MUI_PAGE_CUSTOMFUNCTION_LEAVE un.ConfirmLeave
+Function un.ConfirmLeave
+    SendMessage $DeleteAppDataCheckbox ${BM_GETCHECK} 0 0 $DeleteAppDataCheckboxState
+FunctionEnd
+!insertmacro MUI_UNPAGE_CONFIRM
+
+; 2. Uninstalling Page
+!insertmacro MUI_UNPAGE_INSTFILES
+
+;Languages
+{{#each languages}}
+!insertmacro MUI_LANGUAGE "{{this}}"
+{{/each}}
+!insertmacro MUI_RESERVEFILE_LANGDLL
+{{#each language_files}}
+  !include "{{this}}"
+{{/each}}
+
+!macro SetContext
+  !if "${INSTALLMODE}" == "currentUser"
+    SetShellVarContext current
+  !else if "${INSTALLMODE}" == "perMachine"
+    SetShellVarContext all
+  !endif
+
+  ${If} ${RunningX64}
+    !if "${ARCH}" == "x64"
+      SetRegView 64
+    !else if "${ARCH}" == "arm64"
+      SetRegView 64
+    !else
+      SetRegView 32
+    !endif
+  ${EndIf}
+!macroend
+
+Var PassiveMode
+Function .onInit
+  ${GetOptions} $CMDLINE "/P" $PassiveMode
+  IfErrors +2 0
+    StrCpy $PassiveMode 1
+
+  !if "${DISPLAYLANGUAGESELECTOR}" == "true"
+    !insertmacro MUI_LANGDLL_DISPLAY
+  !endif
+
+  !insertmacro SetContext
+
+  ${If} $INSTDIR == ""
+    ; Set default install location
+    !if "${INSTALLMODE}" == "perMachine"
+      ${If} ${RunningX64}
+        !if "${ARCH}" == "x64"
+          StrCpy $INSTDIR "$PROGRAMFILES64\${PRODUCTNAME}"
+        !else if "${ARCH}" == "arm64"
+          StrCpy $INSTDIR "$PROGRAMFILES64\${PRODUCTNAME}"
+        !else
+          StrCpy $INSTDIR "$PROGRAMFILES\${PRODUCTNAME}"
+        !endif
+      ${Else}
+        StrCpy $INSTDIR "$PROGRAMFILES\${PRODUCTNAME}"
+      ${EndIf}
+    !else if "${INSTALLMODE}" == "currentUser"
+      StrCpy $INSTDIR "$LOCALAPPDATA\${PRODUCTNAME}"
+    !endif
+
+    Call RestorePreviousInstallLocation
+  ${EndIf}
+
+
+  !if "${INSTALLMODE}" == "both"
+    !insertmacro MULTIUSER_INIT
+  !endif
+FunctionEnd
+
+!macro CheckAllVergeProcesses
+    ; Check if Clash Verge.exe is running
+    nsis_tauri_utils::FindProcess "Clash Verge.exe"
+    ${If} $R0 != 0
+        ; Kill the process
+        !if "${INSTALLMODE}" == "currentUser"
+            nsis_tauri_utils::KillProcessCurrentUser "Clash Verge.exe"
+        !else
+            nsis_tauri_utils::KillProcess "Clash Verge.exe"
+        !endif
+    ${EndIf}
+
+    
+    ; Check if clash-verge-service.exe is running
+    nsis_tauri_utils::FindProcess "clash-verge-service.exe"
+    ${If} $R0 != 0
+        ; Kill the process
+        !if "${INSTALLMODE}" == "currentUser"
+            nsis_tauri_utils::KillProcessCurrentUser "clash-verge-service.exe"
+        !else
+            nsis_tauri_utils::KillProcess "clash-verge-service.exe"
+    ${EndIf}
+
+       
+    ; Check if clash-meta-alpha.exe is running
+    nsis_tauri_utils::FindProcess "clash-meta-alpha.exe"
+    ${If} $R0 != 0
+        ; Kill the process
+        !if "${INSTALLMODE}" == "currentUser"
+            nsis_tauri_utils::KillProcessCurrentUser "clash-meta-alpha.exe"
+        !else
+            nsis_tauri_utils::KillProcess "clash-meta-alpha.exe"
+    ${EndIf}
+
+    ; Check if clash-meta.exe is running
+    nsis_tauri_utils::FindProcess "clash-meta.exe"
+    ${If} $R0 != 0
+        ; Kill the process
+        !if "${INSTALLMODE}" == "currentUser"
+            nsis_tauri_utils::KillProcessCurrentUser "clash-meta.exe"
+        !else
+            nsis_tauri_utils::KillProcess "clash-meta.exe"
+    ${EndIf}
+!macroend
+
+Section
+  !insertmacro CheckAllVergeProcesses
+SectionEnd
+
+Section EarlyChecks
+  ; Abort silent installer if downgrades is disabled
+  !if "${ALLOWDOWNGRADES}" == "false"
+  IfSilent 0 silent_downgrades_done
+    ; If downgrading
+    ${If} $R0 == -1
+      System::Call 'kernel32::AttachConsole(i -1)i.r0'
+      ${If} $0 != 0
+        System::Call 'kernel32::GetStdHandle(i -11)i.r0'
+        System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color
+        FileWrite $0 "$(silentDowngrades)"
+      ${EndIf}
+      Abort
+    ${EndIf}
+  silent_downgrades_done:
+  !endif
+
+SectionEnd
+
+Section WebView2
+  ; Check if Webview2 is already installed and skip this section
+  ${If} ${RunningX64}
+    ReadRegStr $4 HKLM "SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
+  ${Else}
+    ReadRegStr $4 HKLM "SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
+  ${EndIf}
+  ReadRegStr $5 HKCU "SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
+
+  StrCmp $4 "" 0 webview2_done
+  StrCmp $5 "" 0 webview2_done
+
+  ; Webview2 install modes
+  !if "${INSTALLWEBVIEW2MODE}" == "downloadBootstrapper"
+    Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe"
+    DetailPrint "$(webview2Downloading)"
+    nsis_tauri_utils::download "https://go.microsoft.com/fwlink/p/?LinkId=2124703" "$TEMP\MicrosoftEdgeWebview2Setup.exe"
+    Pop $0
+    ${If} $0 == 0
+      DetailPrint "$(webview2DownloadSuccess)"
+    ${Else}
+      DetailPrint "$(webview2DownloadError)"
+      Abort "$(webview2AbortError)"
+    ${EndIf}
+    StrCpy $6 "$TEMP\MicrosoftEdgeWebview2Setup.exe"
+    Goto install_webview2
+  !endif
+
+  !if "${INSTALLWEBVIEW2MODE}" == "embedBootstrapper"
+    Delete "$TEMP\MicrosoftEdgeWebview2Setup.exe"
+    File "/oname=$TEMP\MicrosoftEdgeWebview2Setup.exe" "${WEBVIEW2BOOTSTRAPPERPATH}"
+    DetailPrint "$(installingWebview2)"
+    StrCpy $6 "$TEMP\MicrosoftEdgeWebview2Setup.exe"
+    Goto install_webview2
+  !endif
+
+  !if "${INSTALLWEBVIEW2MODE}" == "offlineInstaller"
+    Delete "$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe"
+    File "/oname=$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe" "${WEBVIEW2INSTALLERPATH}"
+    DetailPrint "$(installingWebview2)"
+    StrCpy $6 "$TEMP\MicrosoftEdgeWebView2RuntimeInstaller.exe"
+    Goto install_webview2
+  !endif
+
+  Goto webview2_done
+
+  install_webview2:
+    DetailPrint "$(installingWebview2)"
+    ; $6 holds the path to the webview2 installer
+    ExecWait "$6 ${WEBVIEW2INSTALLERARGS} /install" $1
+    ${If} $1 == 0
+      DetailPrint "$(webview2InstallSuccess)"
+    ${Else}
+      DetailPrint "$(webview2InstallError)"
+      Abort "$(webview2AbortError)"
+    ${EndIf}
+  webview2_done:
+SectionEnd
+
+!macro CheckIfAppIsRunning
+  !if "${INSTALLMODE}" == "currentUser"
+    nsis_tauri_utils::FindProcessCurrentUser "${MAINBINARYNAME}.exe"
+  !else
+    nsis_tauri_utils::FindProcess "${MAINBINARYNAME}.exe"
+  !endif
+  Pop $R0
+  ${If} $R0 = 0
+      IfSilent kill 0
+      ${IfThen} $PassiveMode != 1 ${|} MessageBox MB_OKCANCEL "$(appRunningOkKill)" IDOK kill IDCANCEL cancel ${|}
+      kill:
+        !if "${INSTALLMODE}" == "currentUser"
+          nsis_tauri_utils::KillProcessCurrentUser "${MAINBINARYNAME}.exe"
+        !else
+          nsis_tauri_utils::KillProcess "${MAINBINARYNAME}.exe"
+        !endif
+        Pop $R0
+        Sleep 500
+        ${If} $R0 = 0
+          Goto app_check_done
+        ${Else}
+          IfSilent silent ui
+          silent:
+            System::Call 'kernel32::AttachConsole(i -1)i.r0'
+            ${If} $0 != 0
+              System::Call 'kernel32::GetStdHandle(i -11)i.r0'
+              System::call 'kernel32::SetConsoleTextAttribute(i r0, i 0x0004)' ; set red color
+              FileWrite $0 "$(appRunning)$\n"
+            ${EndIf}
+            Abort
+          ui:
+            Abort "$(failedToKillApp)"
+        ${EndIf}
+      cancel:
+        Abort "$(appRunning)"
+  ${EndIf}
+  app_check_done:
+!macroend
+
+Section Install
+  SetOutPath $INSTDIR
+
+  !insertmacro CheckIfAppIsRunning
+
+  ; Copy main executable
+  File "${MAINBINARYSRCPATH}"
+
+  ; Copy resources
+  {{#each resources_dirs}}
+    CreateDirectory "$INSTDIR\\{{this}}"
+  {{/each}}
+  {{#each resources}}
+    File /a "/oname={{this.[1]}}" "{{@key}}"
+  {{/each}}
+
+  ; Copy external binaries
+  {{#each binaries}}
+    File /a "/oname={{this}}" "{{@key}}"
+  {{/each}}
+
+  ; Create uninstaller
+  WriteUninstaller "$INSTDIR\uninstall.exe"
+
+  ; Save $INSTDIR in registry for future installations
+  WriteRegStr SHCTX "${MANUPRODUCTKEY}" "" $INSTDIR
+
+  !if "${INSTALLMODE}" == "both"
+    ; Save install mode to be selected by default for the next installation such as updating
+    ; or when uninstalling
+    WriteRegStr SHCTX "${UNINSTKEY}" $MultiUser.InstallMode 1
+  !endif
+
+  ; Registry information for add/remove programs
+  WriteRegStr SHCTX "${UNINSTKEY}" "DisplayName" "${PRODUCTNAME}"
+  WriteRegStr SHCTX "${UNINSTKEY}" "DisplayIcon" "$\"$INSTDIR\${MAINBINARYNAME}.exe$\""
+  WriteRegStr SHCTX "${UNINSTKEY}" "DisplayVersion" "${VERSION}"
+  WriteRegStr SHCTX "${UNINSTKEY}" "Publisher" "${MANUFACTURER}"
+  WriteRegStr SHCTX "${UNINSTKEY}" "InstallLocation" "$\"$INSTDIR$\""
+  WriteRegStr SHCTX "${UNINSTKEY}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
+  WriteRegDWORD SHCTX "${UNINSTKEY}" "NoModify" "1"
+  WriteRegDWORD SHCTX "${UNINSTKEY}" "NoRepair" "1"
+  WriteRegDWORD SHCTX "${UNINSTKEY}" "EstimatedSize" "${ESTIMATEDSIZE}"
+
+  ; Create start menu shortcut (GUI)
+  !insertmacro MUI_STARTMENU_WRITE_BEGIN Application
+    Call CreateStartMenuShortcut
+  !insertmacro MUI_STARTMENU_WRITE_END
+
+  ; Create shortcuts for silent and passive installers, which
+  ; can be disabled by passing `/NS` flag
+  ; GUI installer has buttons for users to control creating them
+  IfSilent check_ns_flag 0
+  ${IfThen} $PassiveMode == 1 ${|} Goto check_ns_flag ${|}
+  Goto shortcuts_done
+  check_ns_flag:
+    ${GetOptions} $CMDLINE "/NS" $R0
+    IfErrors 0 shortcuts_done
+      Call CreateDesktopShortcut
+      Call CreateStartMenuShortcut
+  shortcuts_done:
+
+  ; Auto close this page for passive mode
+  ${IfThen} $PassiveMode == 1 ${|} SetAutoClose true ${|}
+SectionEnd
+
+Function .onInstSuccess
+  ; Check for `/R` flag only in silent and passive installers because
+  ; GUI installer has a toggle for the user to (re)start the app
+  IfSilent check_r_flag 0
+  ${IfThen} $PassiveMode == 1 ${|} Goto check_r_flag ${|}
+  Goto run_done
+  check_r_flag:
+    ${GetOptions} $CMDLINE "/R" $R0
+    IfErrors run_done 0
+      Exec '"$INSTDIR\${MAINBINARYNAME}.exe"'
+  run_done:
+FunctionEnd
+
+Function un.onInit
+  !insertmacro SetContext
+
+  !if "${INSTALLMODE}" == "both"
+    !insertmacro MULTIUSER_UNINIT
+  !endif
+
+  !insertmacro MUI_UNGETLANGUAGE
+FunctionEnd
+
+Section Uninstall
+  !insertmacro CheckIfAppIsRunning
+
+  ; Delete the app directory and its content from disk
+  ; Copy main executable
+  Delete "$INSTDIR\${MAINBINARYNAME}.exe"
+
+  ; Delete resources
+  {{#each resources}}
+    Delete "$INSTDIR\\{{this.[1]}}"
+  {{/each}}
+
+  ; Delete external binaries
+  {{#each binaries}}
+    Delete "$INSTDIR\\{{this}}"
+  {{/each}}
+
+  ; Delete uninstaller
+  Delete "$INSTDIR\uninstall.exe"
+
+  ${If} $DeleteAppDataCheckboxState == 1
+    RMDir /R /REBOOTOK "$INSTDIR"
+  ${Else}
+    {{#each resources_ancestors}}
+    RMDir /REBOOTOK "$INSTDIR\\{{this}}"
+    {{/each}}
+    RMDir "$INSTDIR"
+  ${EndIf}
+
+  ; Remove start menu shortcut
+  !insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder
+  Delete "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk"
+  RMDir "$SMPROGRAMS\$AppStartMenuFolder"
+
+  ; Remove desktop shortcuts
+  Delete "$DESKTOP\${MAINBINARYNAME}.lnk"
+
+  ; Remove registry information for add/remove programs
+  !if "${INSTALLMODE}" == "both"
+    DeleteRegKey SHCTX "${UNINSTKEY}"
+  !else if "${INSTALLMODE}" == "perMachine"
+    DeleteRegKey HKLM "${UNINSTKEY}"
+  !else
+    DeleteRegKey HKCU "${UNINSTKEY}"
+  !endif
+
+  DeleteRegValue HKCU "${MANUPRODUCTKEY}" "Installer Language"
+
+  ; Delete app data
+  ${If} $DeleteAppDataCheckboxState == 1
+    SetShellVarContext current
+    RmDir /r "$APPDATA\${BUNDLEID}"
+    RmDir /r "$LOCALAPPDATA\${BUNDLEID}"
+  ${EndIf}
+
+  ${GetOptions} $CMDLINE "/P" $R0
+  IfErrors +2 0
+    SetAutoClose true
+SectionEnd
+
+Function RestorePreviousInstallLocation
+  ReadRegStr $4 SHCTX "${MANUPRODUCTKEY}" ""
+  StrCmp $4 "" +2 0
+    StrCpy $INSTDIR $4
+FunctionEnd
+
+Function SkipIfPassive
+  ${IfThen} $PassiveMode == 1  ${|} Abort ${|}
+FunctionEnd
+
+Function CreateDesktopShortcut
+  CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
+  ApplicationID::Set "$DESKTOP\${MAINBINARYNAME}.lnk" "${BUNDLEID}"
+FunctionEnd
+
+Function CreateStartMenuShortcut
+  CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder"
+  CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
+  ApplicationID::Set "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "${BUNDLEID}"
+FunctionEnd

+ 3 - 1
src-tauri/tauri.windows.conf.json

@@ -17,7 +17,9 @@
           "displayLanguageSelector": true,
           "installerIcon": "icons/icon.ico",
           "languages": ["SimpChinese", "English"],
-          "license": "../LICENSE"
+          "license": "../LICENSE",
+          "installMode": "perMachine",
+          "template": "../scripts/windows/installer.nsi"
         }
       }
     }