Преглед на файлове

feat: retain cli args when relaunching after update, closes #7402 (#7718)

* feat: retain cli args when relaunching after update, closes #7402

* 1.61 compatible OsString join

* fix msi impl as well

* fix tests

* Update .changes/tauri-bundler-nsis-args.md

Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.studio>

* Update .changes/tauri-updater-retain-args.md

Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.studio>

* more typos

* fix update args

* pull args from Env

* check if not empty

* pin memchr

* Update core.rs

* Update core.rs

* move /args

* fix build

* lint

* more lints

---------

Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.studio>
Amr Bashir преди 1 година
родител
ревизия
8ce51cec3b

+ 5 - 0
.changes/tauri-bundler-nsis-args.md

@@ -0,0 +1,5 @@
+---
+'tauri-bundler': 'minor:feat'
+---
+
+On Windows, NSIS installer now supports `/ARGS` flag to pass arguments to be used when launching the app after installation, only works if `/R` is used.

+ 5 - 0
.changes/tauri-updater-retain-args.md

@@ -0,0 +1,5 @@
+---
+'tauri': 'minor:enhance'
+---
+
+On Windows, retain command line args when relaunching the app after an update. Supports NSIS and WiX (without elevated update task).

+ 1 - 0
Cargo.lock

@@ -4030,6 +4030,7 @@ dependencies = [
  "cocoa",
  "data-url",
  "dirs-next",
+ "dunce",
  "embed_plist",
  "encoding_rs",
  "flate2",

+ 1 - 4
core/tauri-runtime-wry/src/system_tray.rs

@@ -3,10 +3,7 @@
 // SPDX-License-Identifier: MIT
 
 pub use tauri_runtime::{
-  menu::{
-    Menu, MenuEntry, MenuItem, MenuUpdate, Submenu, SystemTrayMenu, SystemTrayMenuEntry,
-    SystemTrayMenuItem, TrayHandle,
-  },
+  menu::{MenuUpdate, SystemTrayMenu, SystemTrayMenuEntry, SystemTrayMenuItem, TrayHandle},
   Icon, SystemTrayEvent,
 };
 use wry::application::event_loop::EventLoopWindowTarget;

+ 3 - 0
core/tauri-utils/src/config.rs

@@ -2586,6 +2586,9 @@ impl WindowsUpdateInstallMode {
   }
 
   /// Returns the associated nsis arguments.
+  ///
+  /// [WindowsUpdateInstallMode::Passive] will return `["/P", "/R"]`
+  /// [WindowsUpdateInstallMode::Quiet] will return `["/S", "/R"]`
   pub fn nsis_args(&self) -> &'static [&'static str] {
     match self {
       Self::Passive => &["/P", "/R"],

+ 1 - 0
core/tauri/Cargo.toml

@@ -112,6 +112,7 @@ cocoa = "0.24"
 objc = "0.2"
 
 [target."cfg(windows)".dependencies]
+dunce = "1"
 webview2-com = "0.19.1"
 win7-notifications = { version = "0.4", optional = true }
 

+ 51 - 51
core/tauri/src/event.rs

@@ -225,6 +225,57 @@ impl Listeners {
   }
 }
 
+pub fn unlisten_js(listeners_object_name: String, event_name: String, event_id: u32) -> String {
+  format!(
+    "
+      (function () {{
+        const listeners = (window['{listeners_object_name}'] || {{}})['{event_name}']
+        if (listeners) {{
+          const index = window['{listeners_object_name}']['{event_name}'].findIndex(e => e.id === {event_id})
+          if (index > -1) {{
+            window['{listeners_object_name}']['{event_name}'].splice(index, 1)
+          }}
+        }}
+      }})()
+    ",
+  )
+}
+
+pub fn listen_js(
+  listeners_object_name: String,
+  event: String,
+  event_id: u32,
+  window_label: Option<String>,
+  handler: String,
+) -> String {
+  format!(
+    "
+    (function () {{
+      if (window['{listeners}'] === void 0) {{
+        Object.defineProperty(window, '{listeners}', {{ value: Object.create(null) }});
+      }}
+      if (window['{listeners}'][{event}] === void 0) {{
+        Object.defineProperty(window['{listeners}'], {event}, {{ value: [] }});
+      }}
+      const eventListeners = window['{listeners}'][{event}]
+      const listener = {{
+        id: {event_id},
+        windowLabel: {window_label},
+        handler: {handler}
+      }};
+      eventListeners.push(listener);
+    }})()
+  ",
+    listeners = listeners_object_name,
+    window_label = if let Some(l) = window_label {
+      crate::runtime::window::assert_label_is_valid(&l);
+      format!("'{l}'")
+    } else {
+      "null".to_owned()
+    },
+  )
+}
+
 #[cfg(test)]
 mod test {
   use super::*;
@@ -298,54 +349,3 @@ mod test {
     }
   }
 }
-
-pub fn unlisten_js(listeners_object_name: String, event_name: String, event_id: u32) -> String {
-  format!(
-    "
-      (function () {{
-        const listeners = (window['{listeners_object_name}'] || {{}})['{event_name}']
-        if (listeners) {{
-          const index = window['{listeners_object_name}']['{event_name}'].findIndex(e => e.id === {event_id})
-          if (index > -1) {{
-            window['{listeners_object_name}']['{event_name}'].splice(index, 1)
-          }}
-        }}
-      }})()
-    ",
-  )
-}
-
-pub fn listen_js(
-  listeners_object_name: String,
-  event: String,
-  event_id: u32,
-  window_label: Option<String>,
-  handler: String,
-) -> String {
-  format!(
-    "
-    (function () {{
-      if (window['{listeners}'] === void 0) {{
-        Object.defineProperty(window, '{listeners}', {{ value: Object.create(null) }});
-      }}
-      if (window['{listeners}'][{event}] === void 0) {{
-        Object.defineProperty(window['{listeners}'], {event}, {{ value: [] }});
-      }}
-      const eventListeners = window['{listeners}'][{event}]
-      const listener = {{
-        id: {event_id},
-        windowLabel: {window_label},
-        handler: {handler}
-      }};
-      eventListeners.push(listener);
-    }})()
-  ",
-    listeners = listeners_object_name,
-    window_label = if let Some(l) = window_label {
-      crate::runtime::window::assert_label_is_valid(&l);
-      format!("'{l}'")
-    } else {
-      "null".to_owned()
-    },
-  )
-}

+ 37 - 14
core/tauri/src/updater/core.rs

@@ -706,6 +706,7 @@ impl<R: Runtime> Update<R> {
         &self.extract_path,
         self.with_elevated_task,
         &self.app.config(),
+        &self.app.env(),
       )?;
       #[cfg(not(target_os = "windows"))]
       copy_files_and_run(archive_buffer, &self.extract_path)?;
@@ -805,6 +806,7 @@ fn copy_files_and_run<R: Read + Seek>(
   _extract_path: &Path,
   with_elevated_task: bool,
   config: &crate::Config,
+  env: &crate::Env,
 ) -> Result {
   // FIXME: We need to create a memory buffer with the MSI and then run it.
   //        (instead of extracting the MSI to a temp path)
@@ -830,6 +832,8 @@ fn copy_files_and_run<R: Read + Seek>(
     |p| format!("{p}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"),
   );
 
+  let current_exe_args = env.args.clone();
+
   for path in paths {
     let found_path = path?.path();
     // we support 2 type of files exe & msi for now
@@ -842,29 +846,39 @@ fn copy_files_and_run<R: Read + Seek>(
       installer_path.push("\"");
 
       let installer_args = [
-        config.tauri.updater.windows.install_mode.nsis_args(),
+        config
+          .tauri
+          .updater
+          .windows
+          .install_mode
+          .nsis_args()
+          .iter()
+          .map(ToString::to_string)
+          .collect(),
+        vec!["/ARGS".to_string()],
+        current_exe_args,
         config
           .tauri
           .updater
           .windows
           .installer_args
           .iter()
-          .map(AsRef::as_ref)
-          .collect::<Vec<_>>()
-          .as_slice(),
+          .map(ToString::to_string)
+          .collect::<Vec<_>>(),
       ]
       .concat();
 
       // Run the EXE
       let mut cmd = Command::new(powershell_path);
       cmd
-        .args(["-NoProfile", "-WindowStyle", "Hidden"])
-        .args(["Start-Process"])
+        .args(["-NoProfile", "-WindowStyle", "Hidden", "Start-Process"])
         .arg(installer_path);
       if !installer_args.is_empty() {
         cmd.arg("-ArgumentList").arg(installer_args.join(", "));
       }
-      cmd.spawn().expect("installer failed to start");
+      cmd
+        .spawn()
+        .expect("Running NSIS installer from powershell has failed to start");
 
       exit(0);
     } else if found_path.extension() == Some(OsStr::new("msi")) {
@@ -908,10 +922,10 @@ fn copy_files_and_run<R: Read + Seek>(
       }
 
       // we need to wrap the current exe path in quotes for Start-Process
-      let mut current_exe_arg = std::ffi::OsString::new();
-      current_exe_arg.push("\"");
-      current_exe_arg.push(current_exe()?);
-      current_exe_arg.push("\"");
+      let mut current_executable = std::ffi::OsString::new();
+      current_executable.push("\"");
+      current_executable.push(dunce::simplified(&current_exe()?));
+      current_executable.push("\"");
 
       let mut msi_path = std::ffi::OsString::new();
       msi_path.push("\"\"\"");
@@ -933,7 +947,9 @@ fn copy_files_and_run<R: Read + Seek>(
       .concat();
 
       // run the installer and relaunch the application
-      let powershell_install_res = Command::new(powershell_path)
+      let mut powershell_cmd = Command::new(powershell_path);
+
+      powershell_cmd
         .args(["-NoProfile", "-WindowStyle", "Hidden"])
         .args([
           "Start-Process",
@@ -946,8 +962,15 @@ fn copy_files_and_run<R: Read + Seek>(
         .arg(&msi_path)
         .arg(format!(", {}, /promptrestart;", installer_args.join(", ")))
         .arg("Start-Process")
-        .arg(current_exe_arg)
-        .spawn();
+        .arg(current_executable);
+
+      if !current_exe_args.is_empty() {
+        powershell_cmd
+          .arg("-ArgumentList")
+          .arg(current_exe_args.join(", "));
+      }
+
+      let powershell_install_res = powershell_cmd.spawn();
       if powershell_install_res.is_err() {
         // fallback to running msiexec directly - relaunch won't be available
         // we use this here in case powershell fails in an older machine somehow

+ 2 - 1
tooling/bundler/src/bundle/windows/templates/installer.nsi

@@ -606,7 +606,8 @@ Function .onInstSuccess
   check_r_flag:
     ${GetOptions} $CMDLINE "/R" $R0
     IfErrors run_done 0
-      Exec '"$INSTDIR\${MAINBINARYNAME}.exe"'
+      ${GetOptions} $CMDLINE "/ARGS" $R0
+      Exec '"$INSTDIR\${MAINBINARYNAME}.exe" $R0'
   run_done:
 FunctionEnd