浏览代码

feat(nsis): implement `passive` mode, closes #6955 (#6998)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
Amr Bashir 2 年之前
父节点
当前提交
df89ccc191

+ 5 - 0
.changes/nsis-downgrades.md

@@ -0,0 +1,5 @@
+---
+'tauri-bundler': 'patch'
+---
+
+Fix NSIS installer disabling `do not uninstall` button and silent installer aborting, if `allowDowngrades` was disabled even when we are not downgrading.

+ 5 - 0
.changes/nsis-passive-mode.md

@@ -0,0 +1,5 @@
+---
+'tauri': 'minor'
+---
+
+Support `passive` mode for NSIS updater.

+ 5 - 0
.changes/nsis-restart-flag.md

@@ -0,0 +1,5 @@
+---
+'tauri-bundler': 'minor'
+---
+
+For NSIS, Add support for `/P` to install or uninstall in passive mode, `/R` to (re)start the app and `/NS` to disable creating shortcuts in `silent` and `passive` modes.

+ 5 - 0
.changes/nsis-silent-kill.md

@@ -0,0 +1,5 @@
+---
+'tauri-bundler': 'minor'
+---
+
+NSIS `silent` and `passive` installer/updater will auto-kill the app if its running.

+ 5 - 0
.changes/nsis-silent-shortcuts.md

@@ -0,0 +1,5 @@
+---
+'tauri-bundler': 'patch'
+---
+
+Fix NSIS silent installer not creating Desktop and StartMenu shortcuts. Pass `/NS` to disable creating them.

+ 1 - 1
core/tauri-config-schema/schema.json

@@ -2822,7 +2822,7 @@
           ]
         },
         {
-          "description": "Specifies unattended mode, which means the installation only shows a progress bar. **msi (Wix) Only**",
+          "description": "Specifies unattended mode, which means the installation only shows a progress bar.",
           "type": "string",
           "enum": [
             "passive"

+ 2 - 1
core/tauri-utils/src/config.rs

@@ -2466,7 +2466,7 @@ pub enum WindowsUpdateInstallMode {
   /// The quiet mode means there's no user interaction required.
   /// Requires admin privileges if the installer does.
   Quiet,
-  /// Specifies unattended mode, which means the installation only shows a progress bar. **msi (Wix) Only**
+  /// Specifies unattended mode, which means the installation only shows a progress bar.
   Passive,
   // to add more modes, we need to check if the updater relaunch makes sense
   // i.e. for a full UI mode, the user can also mark the installer to start the app
@@ -2485,6 +2485,7 @@ impl WindowsUpdateInstallMode {
   /// Returns the associated nsis arguments.
   pub fn nsis_args(&self) -> &'static [&'static str] {
     match self {
+      Self::Passive => &["/P", "/R"],
       Self::Quiet => &["/S", "/R"],
       _ => &[],
     }

+ 5 - 9
core/tauri/src/updater/core.rs

@@ -735,15 +735,11 @@ fn copy_files_and_run<R: Read + Seek>(
     // If it's an `exe` we expect an installer not a runtime.
     if found_path.extension() == Some(OsStr::new("exe")) {
       // Run the EXE
-      let mut installer = Command::new(found_path);
-      if crate::utils::config::WindowsUpdateInstallMode::Quiet
-        == config.tauri.updater.windows.install_mode
-      {
-        installer.args(config.tauri.updater.windows.install_mode.nsis_args());
-      }
-      installer.args(&config.tauri.updater.windows.installer_args);
-
-      installer.spawn().expect("installer failed to start");
+      Command::new(found_path)
+        .args(config.tauri.updater.windows.install_mode.nsis_args())
+        .args(&config.tauri.updater.windows.installer_args)
+        .spawn()
+        .expect("installer failed to start");
 
       exit(0);
     } else if found_path.extension() == Some(OsStr::new("msi")) {

+ 108 - 64
tooling/bundler/src/bundle/windows/templates/installer.nsi

@@ -93,18 +93,20 @@ VIAddVersionKey "ProductVersion" "${VERSION}"
 !define MUI_LANGDLL_REGISTRY_KEY "${MANUPRODUCTKEY}"
 !define MUI_LANGDLL_REGISTRY_VALUENAME "Installer Language"
 
-; Installer pages, must be ordered as they should appear to the user
-;
+; 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
 
@@ -162,14 +164,14 @@ Function PageReinstall
     StrCpy $R2 "$(addOrReinstall)"
     StrCpy $R3 "$(uninstallApp)"
     !insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(chooseMaintenanceOption)"
-    StrCpy $R0 "2"
+    StrCpy $R5 "2"
   ; Upgrading
   ${ElseIf} $R0 == 1
     StrCpy $R1 "$(olderOrUnknownVersionInstalled)"
     StrCpy $R2 "$(uninstallBeforeInstalling)"
     StrCpy $R3 "$(dontUninstall)"
     !insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(choowHowToInstall)"
-    StrCpy $R0 "1"
+    StrCpy $R5 "1"
   ; Downgrading
   ${ElseIf} $R0 == -1
     StrCpy $R1 "$(newerVersionInstalled)"
@@ -180,16 +182,16 @@ Function PageReinstall
       StrCpy $R3 "$(dontUninstallDowngrade)"
     !endif
     !insertmacro MUI_HEADER_TEXT "$(alreadyInstalled)" "$(choowHowToInstall)"
-    StrCpy $R0 "1"
+    StrCpy $R5 "1"
   ${Else}
     Abort
   ${EndIf}
 
+  Call SkipIfPassive
+
   nsDialogs::Create 1018
   Pop $R4
-  ${If} $(^RTL) == 1
-    nsDialogs::SetRTL $(^RTL)
-  ${EndIf}
+  ${IfThen} $(^RTL) == 1 ${|} nsDialogs::SetRTL $(^RTL) ${|}
 
   ${NSD_CreateLabel} 0 0 100% 24u $R1
   Pop $R1
@@ -200,12 +202,15 @@ Function PageReinstall
 
   ${NSD_CreateRadioButton} 30u 70u -30u 8u $R3
   Pop $R3
-  ; disable this radio button if downgrades are not allowed
+  ; disable this radio button if downgrading and downgrades are disabled
   !if "${ALLOWDOWNGRADES}" == "false"
-    EnableWindow $R3 0
+    ${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}
@@ -213,14 +218,10 @@ Function PageReinstall
   ${EndIf}
 
   ${NSD_SetFocus} $R2
-
   nsDialogs::Show
 FunctionEnd
 Function PageReinstallUpdateSelection
-  Pop $R1
-
   ${NSD_GetState} $R2 $R1
-
   ${If} $R1 == ${BST_CHECKED}
     StrCpy $ReinstallPageCheck 1
   ${Else}
@@ -230,18 +231,19 @@ FunctionEnd
 Function PageLeaveReinstall
   ${NSD_GetState} $R2 $R1
 
-  ; $R0 holds whether we are reinstalling the same version or not
-  ; $R0 == "1" -> different versions
-  ; $R0 == "2" -> same version
+  ; $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 $R0 "1" 0 +2 ; Existing install is not the same version?
+  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
+    ExecWait '$R1 /P _?=$4' $0
 
     ${If} $R5 == "wix"
       ReadRegStr $R1 HKLM "$R6" "UninstallString"
@@ -259,7 +261,7 @@ Function PageLeaveReinstall
     ${If} $0 <> 0
     ${OrIf} ${FileExists} "$INSTDIR\${MAINBINARYNAME}.exe"
       ${If} $0 = 1 ; User aborted uninstaller?
-        StrCmp $R0 "2" 0 +2 ; Is the existing install the same version?
+        StrCmp $R5 "2" 0 +2 ; Is the existing install the same version?
           Quit ; ...yes, already installed, we are done
         Abort
       ${EndIf}
@@ -271,14 +273,15 @@ Function PageLeaveReinstall
       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
 
@@ -294,12 +297,9 @@ Var AppStartMenuFolder
 !define MUI_FINISHPAGE_SHOWREADME
 !define MUI_FINISHPAGE_SHOWREADME_TEXT "$(createDesktop)"
 !define MUI_FINISHPAGE_SHOWREADME_FUNCTION CreateDesktopShortcut
-Function CreateDesktopShortcut
-  CreateShortcut "$DESKTOP\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
-  ApplicationID::Set "$DESKTOP\${MAINBINARYNAME}.lnk" "${BUNDLEID}"
-FunctionEnd
 ; Show run app after installation.
 !define MUI_FINISHPAGE_RUN "$INSTDIR\${MAINBINARYNAME}.exe"
+!define MUI_PAGE_CUSTOMFUNCTION_PRE SkipIfPassive
 !insertmacro MUI_PAGE_FINISH
 
 ; Uninstaller Pages
@@ -337,7 +337,12 @@ FunctionEnd
   !include "{{this}}"
 {{/each}}
 
+Var PassiveMode
 Function .onInit
+  ${GetOptions} $CMDLINE "/P" $PassiveMode
+  IfErrors +2 0
+    StrCpy $PassiveMode 1
+
   !if "${DISPLAYLANGUAGESELECTOR}" == "true"
     !insertmacro MUI_LANGDLL_DISPLAY
   !endif
@@ -385,22 +390,27 @@ Function .onInit
   !endif
 FunctionEnd
 
+
 Section EarlyChecks
   ; Abort silent installer if downgrades is disabled
   !if "${ALLOWDOWNGRADES}" == "false"
-  IfSilent 0 done
-    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)"
+  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}
-    Abort
-  done:
+  silent_downgrades_done:
   !endif
+
 SectionEnd
 
-Section Webview2
+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"
@@ -409,8 +419,8 @@ Section Webview2
   ${EndIf}
   ReadRegStr $5 HKCU "SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" "pv"
 
-  StrCmp $4 "" 0 done
-  StrCmp $5 "" 0 done
+  StrCmp $4 "" 0 webview2_done
+  StrCmp $5 "" 0 webview2_done
 
   ; Webview2 install modes
   !if "${INSTALLWEBVIEW2MODE}" == "downloadBootstrapper"
@@ -444,7 +454,7 @@ Section Webview2
     Goto install_webview2
   !endif
 
-  Goto done
+  Goto webview2_done
 
   install_webview2:
     DetailPrint "$(installingWebview2)"
@@ -456,38 +466,38 @@ Section Webview2
       DetailPrint "$(webview2InstallError)"
       Abort "$(webview2AbortError)"
     ${EndIf}
-
-  done:
+  webview2_done:
 SectionEnd
 
 !macro CheckIfAppIsRunning
   nsis_tauri_utils::FindProcess "${MAINBINARYNAME}.exe"
   Pop $R0
   ${If} $R0 = 0
-    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:
-      MessageBox MB_OKCANCEL "$(appRunningOkKill)" IDOK ok IDCANCEL cancel
-      ok:
+      IfSilent kill 0
+      ${IfThen} $PassiveMode != 1 ${|} MessageBox MB_OKCANCEL "$(appRunningOkKill)" IDOK kill IDCANCEL cancel ${|}
+      kill:
         nsis_tauri_utils::KillProcess "${MAINBINARYNAME}.exe"
         Pop $R0
         Sleep 500
         ${If} $R0 = 0
-          Goto done
+          Goto app_check_done
         ${Else}
-          Abort "$(failedToKillApp)"
+          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}
-  done:
+  app_check_done:
 !macroend
 
 Section Install
@@ -534,24 +544,39 @@ Section Install
   IntFmt $0 "0x%08X" $0
   WriteRegDWORD SHCTX "${UNINSTKEY}" "EstimatedSize" "$0"
 
-  ; Create start menu shortcut
+  ; Create start menu shortcut (GUI)
   !insertmacro MUI_STARTMENU_WRITE_BEGIN Application
-    CreateDirectory "$SMPROGRAMS\$AppStartMenuFolder"
-    CreateShortcut "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
-    ApplicationID::Set "$SMPROGRAMS\$AppStartMenuFolder\${MAINBINARYNAME}.lnk" "${BUNDLEID}"
+    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
 
-Var Restart
 Function .onInstSuccess
-  ; Check for `/R` flag only in silent installer because
-  ; gui installer has a toggle for the user to restart the app
-  IfSilent 0 done
-    ${GetOptions} $CMDLINE "/R" $Restart
-    IfErrors done 0 ; if errors were found then `/R` wasn't passed, so we skip restarting
+  ; 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"'
-  done:
+  run_done:
 FunctionEnd
 
 Function un.onInit
@@ -619,6 +644,10 @@ Section Uninstall
   !endif
 
   DeleteRegValue HKCU "${MANUPRODUCTKEY}" "Installer Language"
+
+  ${GetOptions} $CMDLINE "/P" $R0
+  IfErrors +2 0
+    SetAutoClose true
 SectionEnd
 
 Function RestorePreviousInstallLocation
@@ -626,3 +655,18 @@ Function RestorePreviousInstallLocation
   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

+ 5 - 5
tooling/cli/Cargo.lock

@@ -2992,11 +2992,11 @@ dependencies = [
 
 [[package]]
 name = "serde_with"
-version = "2.3.3"
+version = "3.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe"
+checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513"
 dependencies = [
- "base64 0.13.1",
+ "base64 0.21.1",
  "chrono",
  "hex",
  "indexmap",
@@ -3008,9 +3008,9 @@ dependencies = [
 
 [[package]]
 name = "serde_with_macros"
-version = "2.3.3"
+version = "3.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f"
+checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070"
 dependencies = [
  "darling",
  "proc-macro2",

+ 1 - 1
tooling/cli/schema.json

@@ -2822,7 +2822,7 @@
           ]
         },
         {
-          "description": "Specifies unattended mode, which means the installation only shows a progress bar. **msi (Wix) Only**",
+          "description": "Specifies unattended mode, which means the installation only shows a progress bar.",
           "type": "string",
           "enum": [
             "passive"