Windows relaunches after an update (#1748)
After the MSI installer finishes, wait for msiexec and start the installed exe; relaunch is detached on Windows.
This commit is contained in:
1 parent
a16226288f
commit
201e055259
4 files changed
+56
-6
No files matched your search
@@ -94,10 +94,20 @@ impl eframe::App for App {
|
||||
solidcraft_ui_egui::recovery_ui::close(&mut self.solve);
|
||||
solidcraft_engine::crash::end_session();
|
||||
// An update asked to restart into the new version or to run its installer.
|
||||
if let Some((program, args)) = solidcraft_ui_egui::update::take_launch(&mut self.solve)
|
||||
&& let Err(e) = std::process::Command::new(&program).args(&args).spawn()
|
||||
{
|
||||
log::error!("update: starting {}: {e}", program.display());
|
||||
if let Some((program, args)) = solidcraft_ui_egui::update::take_launch(&mut self.solve) {
|
||||
let mut command = std::process::Command::new(&program);
|
||||
command.args(&args);
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
// Not tied to this process: its own process group, and no console (the PowerShell
|
||||
// that waits for an installer is hidden; the app itself is detached).
|
||||
let hidden_shell = program.file_stem().is_some_and(|n| n.eq_ignore_ascii_case("powershell"));
|
||||
command.creation_flags(0x0000_0200 | if hidden_shell { 0x0800_0000 } else { 0x0000_0008 });
|
||||
}
|
||||
if let Err(e) = command.spawn() {
|
||||
log::error!("update: starting {}: {e}", program.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,8 +697,13 @@ fn poll_install(app: &mut SolveApp) {
|
||||
app.updater.job = None;
|
||||
match &result {
|
||||
Ok(install::Outcome::Installer(program, args)) => {
|
||||
// The installer replaces this program: start it once SolidCraft has closed.
|
||||
app.updater.launch = Some((program.clone(), args.clone()));
|
||||
// The installer replaces this program: start it once SolidCraft has closed. On
|
||||
// Windows the installer is followed by the new version (see install::relaunch_after).
|
||||
app.updater.launch = Some(match std::env::current_exe() {
|
||||
#[cfg(windows)]
|
||||
Ok(exe) => install::relaunch_after(program, args, &exe),
|
||||
_ => (program.clone(), args.clone()),
|
||||
});
|
||||
crate::documents::request_quit(app);
|
||||
}
|
||||
Ok(install::Outcome::Opened(dmg)) => {
|
||||
|
||||
@@ -107,6 +107,26 @@ pub fn sha256_file(path: &Path) -> Result<String, String> {
|
||||
Ok(h.finalize().iter().map(|b| format!("{b:02x}")).collect())
|
||||
}
|
||||
|
||||
/// The command that runs an installer, waits for it to finish and then starts `exe` (the
|
||||
/// installed program), so the app comes back after an update. Windows has no `wait && start` in
|
||||
/// a plain process spawn, so PowerShell does the waiting; the installer's exit code is ignored on
|
||||
/// purpose: when the user cancels it, the old version is still there to start.
|
||||
pub fn relaunch_after(installer: &Path, args: &[String], exe: &Path) -> (PathBuf, Vec<String>) {
|
||||
// PowerShell single-quoted string: only `'` needs doubling.
|
||||
let q = |s: &str| format!("'{}'", s.replace('\'', "''"));
|
||||
// msiexec splits its command line itself, so an argument with spaces needs its own quotes.
|
||||
let list = args.iter().map(|a| if a.contains(' ') { q(&format!("\"{a}\"")) } else { q(a) }).collect::<Vec<_>>().join(",");
|
||||
let script = format!(
|
||||
"Start-Process -FilePath {} -ArgumentList {list} -Wait; Start-Process -FilePath {}",
|
||||
q(&installer.to_string_lossy()),
|
||||
q(&exe.to_string_lossy())
|
||||
);
|
||||
(
|
||||
PathBuf::from("powershell"),
|
||||
vec!["-NoProfile".into(), "-NonInteractive".into(), "-WindowStyle".into(), "Hidden".into(), "-Command".into(), script],
|
||||
)
|
||||
}
|
||||
|
||||
/// `sums` is signed by `key` (minisign, prehashed) with a trusted comment naming `tag`.
|
||||
pub fn verify_signature(sums: &[u8], signature: &str, key: &str, tag: &str) -> Result<(), String> {
|
||||
let key = minisign_verify::PublicKey::from_base64(key.trim()).map_err(|e| format!("update signing key: {e}"))?;
|
||||
|
||||
@@ -203,3 +203,18 @@ fn installers_and_disk_images_are_handed_over() {
|
||||
assert_eq!(apply(&Method::Dmg, &pkg).unwrap(), Outcome::Opened(pkg.clone()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_installer_is_followed_by_the_new_version() {
|
||||
let args = vec!["/i".to_string(), r"C:\Users\a b\Temp\solidcraft-update\x.msi".to_string(), "/passive".to_string()];
|
||||
let (program, out) = relaunch_after(Path::new("msiexec"), &args, Path::new(r"C:\Program Files\SolidCraft\it's.exe"));
|
||||
assert_eq!(program, PathBuf::from("powershell"));
|
||||
assert_eq!(
|
||||
out.last().unwrap(),
|
||||
&concat!(
|
||||
r#"Start-Process -FilePath 'msiexec' -ArgumentList '/i','"C:\Users\a b\Temp\solidcraft-update\x.msi"','/passive' -Wait; "#,
|
||||
r"Start-Process -FilePath 'C:\Program Files\SolidCraft\it''s.exe'"
|
||||
)
|
||||
);
|
||||
assert!(out.iter().any(|a| a == "-Command"));
|
||||
}
|
||||
Reference in new issue
Block a user