@@ -104,7 +104,7 @@ pub struct UpdaterBuilder {
104104 timeout : Option < Duration > ,
105105 proxy : Option < Url > ,
106106 installer_args : Vec < OsString > ,
107- nsis_installer_args : Vec < OsString > ,
107+ current_exe_args : Vec < OsString > ,
108108 on_before_exit : Option < OnBeforeExit > ,
109109}
110110
@@ -116,7 +116,7 @@ impl UpdaterBuilder {
116116 . as_ref ( )
117117 . map ( |w| w. installer_args . clone ( ) )
118118 . unwrap_or_default ( ) ,
119- nsis_installer_args : Vec :: new ( ) ,
119+ current_exe_args : Vec :: new ( ) ,
120120 current_version,
121121 config,
122122 version_comparator : None ,
@@ -245,7 +245,7 @@ impl UpdaterBuilder {
245245 proxy : self . proxy ,
246246 endpoints,
247247 installer_args : self . installer_args ,
248- nsis_installer_args : self . nsis_installer_args ,
248+ current_exe_args : self . current_exe_args ,
249249 arch,
250250 target,
251251 json_target,
@@ -257,21 +257,13 @@ impl UpdaterBuilder {
257257}
258258
259259impl UpdaterBuilder {
260- pub ( crate ) fn nsis_installer_arg < S > ( mut self , arg : S ) -> Self
261- where
262- S : Into < OsString > ,
263- {
264- self . nsis_installer_args . push ( arg. into ( ) ) ;
265- self
266- }
267-
268- pub ( crate ) fn nsis_installer_args < I , S > ( mut self , args : I ) -> Self
260+ pub ( crate ) fn current_exe_args < I , S > ( mut self , args : I ) -> Self
269261 where
270262 I : IntoIterator < Item = S > ,
271263 S : Into < OsString > ,
272264 {
273265 let args = args. into_iter ( ) . map ( |a| a. into ( ) ) . collect :: < Vec < _ > > ( ) ;
274- self . nsis_installer_args . extend_from_slice ( & args) ;
266+ self . current_exe_args . extend_from_slice ( & args) ;
275267 self
276268 }
277269}
@@ -294,7 +286,7 @@ pub struct Updater {
294286 #[ allow( unused) ]
295287 installer_args : Vec < OsString > ,
296288 #[ allow( unused) ]
297- nsis_installer_args : Vec < OsString > ,
289+ current_exe_args : Vec < OsString > ,
298290}
299291
300292impl Updater {
@@ -406,7 +398,7 @@ impl Updater {
406398 proxy : self . proxy . clone ( ) ,
407399 headers : self . headers . clone ( ) ,
408400 installer_args : self . installer_args . clone ( ) ,
409- nsis_installer_args : self . nsis_installer_args . clone ( ) ,
401+ current_exe_args : self . current_exe_args . clone ( ) ,
410402 } )
411403 } else {
412404 None
@@ -447,7 +439,7 @@ pub struct Update {
447439 #[ allow( unused) ]
448440 installer_args : Vec < OsString > ,
449441 #[ allow( unused) ]
450- nsis_installer_args : Vec < OsString > ,
442+ current_exe_args : Vec < OsString > ,
451443}
452444
453445impl Resource for Update { }
@@ -595,21 +587,36 @@ impl Update {
595587 let updater_type = Self :: extract ( bytes) ?;
596588
597589 let install_mode = self . config . install_mode ( ) ;
590+ let current_args = & self . current_exe_args ( ) [ 1 ..] ;
591+ let msi_args;
592+
598593 let installer_args: Vec < & OsStr > = match & updater_type {
599594 WindowsUpdaterType :: Nsis { .. } => install_mode
600595 . nsis_args ( )
601596 . iter ( )
602597 . map ( OsStr :: new)
603598 . chain ( once ( OsStr :: new ( "/UPDATE" ) ) )
604- . chain ( self . nsis_installer_args ( ) )
605- . chain ( self . installer_args ( ) )
606- . collect ( ) ,
607- WindowsUpdaterType :: Msi { path, .. } => [ OsStr :: new ( "/i" ) , path. as_os_str ( ) ]
608- . into_iter ( )
609- . chain ( install_mode. msiexec_args ( ) . iter ( ) . map ( OsStr :: new) )
610- . chain ( once ( OsStr :: new ( "/promptrestart" ) ) )
599+ . chain ( once ( OsStr :: new ( "/ARGS" ) ) )
600+ . chain ( current_args. to_vec ( ) )
611601 . chain ( self . installer_args ( ) )
612602 . collect ( ) ,
603+ WindowsUpdaterType :: Msi { path, .. } => {
604+ let escaped_args = current_args
605+ . iter ( )
606+ . map ( escape_msi_property_arg)
607+ . collect :: < Vec < _ > > ( )
608+ . join ( " " ) ;
609+ msi_args = OsString :: from ( format ! ( "LAUNCHAPPARGS=\" {escaped_args}\" " ) ) ;
610+
611+ [ OsStr :: new ( "/i" ) , path. as_os_str ( ) ]
612+ . into_iter ( )
613+ . chain ( install_mode. msiexec_args ( ) . iter ( ) . map ( OsStr :: new) )
614+ . chain ( once ( OsStr :: new ( "/promptrestart" ) ) )
615+ . chain ( self . installer_args ( ) )
616+ . chain ( once ( OsStr :: new ( "AUTOLAUNCHAPP=True" ) ) )
617+ . chain ( once ( msi_args. as_os_str ( ) ) )
618+ . collect ( )
619+ }
613620 } ;
614621
615622 if let Some ( on_before_exit) = self . on_before_exit . as_ref ( ) {
@@ -649,8 +656,8 @@ impl Update {
649656 . collect :: < Vec < _ > > ( )
650657 }
651658
652- fn nsis_installer_args ( & self ) -> Vec < & OsStr > {
653- self . nsis_installer_args
659+ fn current_exe_args ( & self ) -> Vec < & OsStr > {
660+ self . current_exe_args
654661 . iter ( )
655662 . map ( OsStr :: new)
656663 . collect :: < Vec < _ > > ( )
@@ -1026,6 +1033,32 @@ impl PathExt for PathBuf {
10261033 }
10271034}
10281035
1036+ #[ cfg( windows) ]
1037+ fn escape_msi_property_arg ( arg : impl AsRef < OsStr > ) -> String {
1038+ let mut arg = arg. as_ref ( ) . to_string_lossy ( ) . to_string ( ) ;
1039+
1040+ // Otherwise this argument will get lost in ShellExecute
1041+ if arg. is_empty ( ) {
1042+ return "\" \" \" \" " . to_string ( ) ;
1043+ } else if !arg. contains ( ' ' ) && !arg. contains ( '"' ) {
1044+ return arg;
1045+ }
1046+
1047+ if arg. contains ( '"' ) {
1048+ arg = arg. replace ( '"' , r#""""""# )
1049+ }
1050+
1051+ if arg. starts_with ( '-' ) {
1052+ if let Some ( ( a1, a2) ) = arg. split_once ( '=' ) {
1053+ format ! ( "{a1}=\" \" {a2}\" \" " )
1054+ } else {
1055+ format ! ( "\" \" {arg}\" \" " )
1056+ }
1057+ } else {
1058+ format ! ( "\" \" {arg}\" \" " )
1059+ }
1060+ }
1061+
10291062#[ cfg( test) ]
10301063mod tests {
10311064
@@ -1040,4 +1073,52 @@ mod tests {
10401073 PathBuf :: from( "\" C:\\ Users\\ Some User\\ AppData\\ tauri-example.exe\" " )
10411074 )
10421075 }
1076+
1077+ #[ test]
1078+ #[ cfg( windows) ]
1079+ fn it_escapes_correctly ( ) {
1080+ use crate :: updater:: escape_msi_property_arg;
1081+
1082+ // Explanation for quotes:
1083+ // The output of escape_msi_property_args() will be used in `LAUNCHAPPARGS=\"{HERE}\"`. This is the first quote level.
1084+ // To escape a quotation mark we use a second quotation mark, so "" is interpreted as " later.
1085+ // This means that the escaped strings can't ever have a single quotation mark!
1086+ // Now there are 3 major things to look out for to not break the msiexec call:
1087+ // 1) Wrap spaces in quotation marks, otherwise it will be interpreted as the end of the msiexec argument.
1088+ // 2) Escape escaping quotation marks, otherwise they will either end the msiexec argument or be ignored.
1089+ // 3) Escape emtpy args in quotation marks, otherwise the argument will get lost.
1090+ let cases = [
1091+ "something" ,
1092+ "--flag" ,
1093+ "--empty=" ,
1094+ "--arg=value" ,
1095+ "some space" , // This simulates `./my-app "some string"`.
1096+ "--arg value" , // -> This simulates `./my-app "--arg value"`. Same as above but it triggers the startsWith(`-`) logic.
1097+ "--arg=unwrapped space" , // `./my-app --arg="unwrapped space"`
1098+ "--arg=\" wrapped\" " , // `./my-app --args=""wrapped""`
1099+ "--arg=\" wrapped space\" " , // `./my-app --args=""wrapped space""`
1100+ "--arg=midword\" wrapped space\" " , // `./my-app --args=midword""wrapped""`
1101+ "" , // `./my-app '""'`
1102+ ] ;
1103+ let cases_escaped = [
1104+ "something" ,
1105+ "--flag" ,
1106+ "--empty=" ,
1107+ "--arg=value" ,
1108+ "\" \" some space\" \" " ,
1109+ "\" \" --arg value\" \" " ,
1110+ "--arg=\" \" unwrapped space\" \" " ,
1111+ r#"--arg=""""""wrapped"""""""# ,
1112+ r#"--arg=""""""wrapped space"""""""# ,
1113+ r#"--arg=""midword""""wrapped space"""""""# ,
1114+ "\" \" \" \" " ,
1115+ ] ;
1116+
1117+ // Just to be sure we didn't mess that up
1118+ assert_eq ! ( cases. len( ) , cases_escaped. len( ) ) ;
1119+
1120+ for ( orig, escaped) in cases. iter ( ) . zip ( cases_escaped) {
1121+ assert_eq ! ( escape_msi_property_arg( orig) , escaped) ;
1122+ }
1123+ }
10431124}
0 commit comments