diff --git a/.changes/nsis-downgrades.md b/.changes/nsis-downgrades.md new file mode 100644 index 000000000..b0a297758 --- /dev/null +++ b/.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. diff --git a/.changes/nsis-passive-mode.md b/.changes/nsis-passive-mode.md new file mode 100644 index 000000000..8f90fd5ac --- /dev/null +++ b/.changes/nsis-passive-mode.md @@ -0,0 +1,5 @@ +--- +'tauri': 'minor' +--- + +Support `passive` mode for NSIS updater. diff --git a/.changes/nsis-restart-flag.md b/.changes/nsis-restart-flag.md new file mode 100644 index 000000000..889aa4fdb --- /dev/null +++ b/.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. diff --git a/.changes/nsis-silent-kill.md b/.changes/nsis-silent-kill.md new file mode 100644 index 000000000..ee1e30ef4 --- /dev/null +++ b/.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. diff --git a/.changes/nsis-silent-shortcuts.md b/.changes/nsis-silent-shortcuts.md new file mode 100644 index 000000000..b9cf4ec53 --- /dev/null +++ b/.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. diff --git a/core/tauri-config-schema/schema.json b/core/tauri-config-schema/schema.json index b8bd3bb0f..ca60fb912 100644 --- a/core/tauri-config-schema/schema.json +++ b/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" diff --git a/core/tauri-utils/src/config.rs b/core/tauri-utils/src/config.rs index 864a06025..5754cda9e 100644 --- a/core/tauri-utils/src/config.rs +++ b/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"], _ => &[], } diff --git a/core/tauri/src/updater/core.rs b/core/tauri/src/updater/core.rs index 27305b63b..48d29a944 100644 --- a/core/tauri/src/updater/core.rs +++ b/core/tauri/src/updater/core.rs @@ -735,15 +735,11 @@ fn copy_files_and_run( // 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")) { diff --git a/tooling/bundler/src/bundle/windows/templates/installer.nsi b/tooling/bundler/src/bundle/windows/templates/installer.nsi index 0fe8eb9ce..dcb9948c4 100644 --- a/tooling/bundler/src/bundle/windows/templates/installer.nsi +++ b/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 diff --git a/tooling/cli/Cargo.lock b/tooling/cli/Cargo.lock index 24e0b3aa6..60dfccdcd 100644 --- a/tooling/cli/Cargo.lock +++ b/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", diff --git a/tooling/cli/schema.json b/tooling/cli/schema.json index b8bd3bb0f..ca60fb912 100644 --- a/tooling/cli/schema.json +++ b/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"