Updater: recognise an MSI install outside Program Files (#1870) (#1873)

Thanks @SychPL.
This commit is contained in:
Mateusz Sych authored and GitHub committed 2026-10-17 04:08:46 -04:00
1 parent a2245b2f51
commit ff7f3204d5
5 files changed
+63 -6

No files matched your search

+27 -3
View File
@@ -144,7 +144,7 @@ pub enum InstallKind {
Rpm,
/// The plain `.tar.gz`, unpacked somewhere.
LinuxTarball,
/// Installed by the `.msi` (under Program Files).
/// Installed by the `.msi` (under Program Files, or in the folder the installer recorded).
WindowsMsi,
/// The portable `.zip`, unpacked somewhere.
WindowsPortable,
@@ -165,6 +165,9 @@ pub struct Probe<'a> {
/// The running executable.
pub exe: Option<PathBuf>,
pub exists: &'a dyn Fn(&Path) -> bool,
/// Where the Windows installer put SolidCraft (`HKLM\Software\SolidCraft`, `InstallDir`): a
/// folder chosen in its wizard can be anywhere, not only under Program Files (#1870).
pub msi_dir: Option<PathBuf>,
}
pub fn detect(p: &Probe) -> InstallKind {
@@ -202,7 +205,8 @@ pub fn detect(p: &Probe) -> InstallKind {
}
"windows" => {
let under = |k: &str| env(k).is_some_and(|dir| starts_with_ci(&exe, Path::new(&dir)));
if under("ProgramFiles") || under("ProgramW6432") || under("ProgramFiles(x86)") {
let installed = p.msi_dir.as_deref().is_some_and(|dir| starts_with_ci(&exe, dir));
if installed || under("ProgramFiles") || under("ProgramW6432") || under("ProgramFiles(x86)") {
return InstallKind::WindowsMsi;
}
if exe.parent().is_some_and(|d| (p.exists)(&d.join("solidcraft-cli.exe"))) {
@@ -240,10 +244,30 @@ pub fn this_install() -> InstallKind {
{
let env = |k: &str| std::env::var(k).ok();
let exists = |p: &Path| p.exists();
detect(&Probe { os: std::env::consts::OS, release_build: release_build(), env: &env, exe: std::env::current_exe().ok(), exists: &exists })
let msi_dir = msi_install_dir();
detect(&Probe {
os: std::env::consts::OS,
release_build: release_build(),
env: &env,
exe: std::env::current_exe().ok(),
exists: &exists,
msi_dir,
})
}
}
/// The folder the Windows installer recorded (solidcraft.wxs writes it for upgrades), if any.
#[cfg(windows)]
fn msi_install_dir() -> Option<PathBuf> {
let key = winreg::RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE).open_subkey(r"Software\SolidCraft").ok()?;
key.get_value::<String, _>("InstallDir").ok().map(PathBuf::from)
}
#[cfg(all(not(windows), not(target_arch = "wasm32")))]
fn msi_install_dir() -> Option<PathBuf> {
None
}
impl InstallKind {
/// The package manager updates it: Download isn't the way (the window shows the command).
pub fn system_managed(&self) -> bool {
+29 -2
View File
@@ -32,7 +32,7 @@ fn probe(os: &str, release: bool, env: &[(&str, &str)], exe: &str, files: &[&str
let files: Vec<PathBuf> = files.iter().map(PathBuf::from).collect();
let get = |k: &str| env.get(k).cloned();
let exists = |p: &Path| files.iter().any(|f| f == p);
detect(&Probe { os, release_build: release, env: &get, exe: Some(PathBuf::from(exe)), exists: &exists })
detect(&Probe { os, release_build: release, env: &get, exe: Some(PathBuf::from(exe)), exists: &exists, msi_dir: None })
}
#[test]
@@ -92,7 +92,34 @@ fn portable_windows_layout_is_recognised() {
let exe = PathBuf::from("/portable/solidcraft.exe");
let get = |k: &str| (k == "ProgramFiles").then(|| "C:\\Program Files".to_string());
let exists = |p: &Path| p == Path::new("/portable/solidcraft-cli.exe");
assert_eq!(detect(&Probe { os: "windows", release_build: true, env: &get, exe: Some(exe), exists: &exists }), InstallKind::WindowsPortable);
assert_eq!(
detect(&Probe { os: "windows", release_build: true, env: &get, exe: Some(exe), exists: &exists, msi_dir: None }),
InstallKind::WindowsPortable
);
}
/// #1870: the installer's wizard can put SolidCraft anywhere, and it puts solidcraft-cli.exe
/// beside it as the portable zip does. The folder it recorded is what tells its copy apart.
#[test]
fn an_msi_install_outside_program_files_is_recognised() {
let exe = PathBuf::from(r"D:\Program Files\SolidCraft\solidcraft.exe");
let get = |k: &str| match k {
"ProgramFiles" | "ProgramW6432" => Some(r"C:\Program Files".to_string()),
"ProgramFiles(x86)" => Some(r"C:\Program Files (x86)".to_string()),
_ => None,
};
// solidcraft-cli.exe is beside it.
let exists = |_: &Path| true;
let kind = |msi_dir: Option<&str>| {
detect(&Probe { os: "windows", release_build: true, env: &get, exe: Some(exe.clone()), exists: &exists, msi_dir: msi_dir.map(PathBuf::from) })
};
// The installer records [INSTALLFOLDER], which ends in a backslash; Windows paths ignore case.
assert_eq!(kind(Some(r"D:\Program Files\SolidCraft\")), InstallKind::WindowsMsi);
assert_eq!(kind(Some(r"d:\program files\solidcraft")), InstallKind::WindowsMsi);
// No installer copy here: the portable zip, as before.
assert_eq!(kind(None), InstallKind::WindowsPortable);
assert_eq!(kind(Some("")), InstallKind::WindowsPortable);
assert_eq!(kind(Some(r"D:\Program Files\SolidCraft2\")), InstallKind::WindowsPortable, "a folder whose name only starts the same");
}
/// The assets of v0.10.0, as published.