mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Shortcut rework / Configure shortcuts on the vpk command line (#165)
This commit is contained in:
550
src/Rust/Cargo.lock
generated
550
src/Rust/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -52,14 +52,14 @@ regex = "1.10"
|
|||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
simplelog = "0.12"
|
simplelog = "0.12"
|
||||||
clap = "4.4"
|
clap = "4.5"
|
||||||
xml = "0.8"
|
xml = "0.8"
|
||||||
semver = "1.0"
|
semver = "1.0"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
wait-timeout = "0.2"
|
wait-timeout = "0.2"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.5"
|
||||||
strum = { version = "0.26", features = ["derive"] }
|
strum = { version = "0.26", features = ["derive"] }
|
||||||
ureq = { version = "2.9", default-features = false, features = [
|
ureq = { version = "2.10", default-features = false, features = [
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"gzip",
|
"gzip",
|
||||||
] }
|
] }
|
||||||
@@ -79,6 +79,7 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
time = "0.3"
|
time = "0.3"
|
||||||
os_info = "3.8"
|
os_info = "3.8"
|
||||||
|
bitflags = "2.6"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
native-dialog = "0.7"
|
native-dialog = "0.7"
|
||||||
@@ -87,15 +88,15 @@ dialog = "0.3"
|
|||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
fs_extra = "1.2"
|
fs_extra = "1.3"
|
||||||
memmap2 = "0.9"
|
memmap2 = "0.9"
|
||||||
winsafe = { version = "0.0.20", features = ["version", "user", "gui"] }
|
winsafe = { version = "0.0.20", features = ["gui"] }
|
||||||
image = { version = "0.25", default-features = false, features = [
|
image = { version = "0.25", default-features = false, features = [
|
||||||
"gif",
|
"gif",
|
||||||
"jpeg",
|
"jpeg",
|
||||||
"png",
|
"png",
|
||||||
] }
|
] }
|
||||||
windows = { version = "0.56", default-features = false, features = [
|
windows = { version = "0.57", default-features = false, features = [
|
||||||
"Win32_Foundation",
|
"Win32_Foundation",
|
||||||
"Win32_Security",
|
"Win32_Security",
|
||||||
"Win32_System_Com",
|
"Win32_System_Com",
|
||||||
@@ -104,6 +105,7 @@ windows = { version = "0.56", default-features = false, features = [
|
|||||||
"Win32_System_Threading",
|
"Win32_System_Threading",
|
||||||
"Win32_System_SystemInformation",
|
"Win32_System_SystemInformation",
|
||||||
"Win32_System_Variant",
|
"Win32_System_Variant",
|
||||||
|
"Win32_System_Environment",
|
||||||
"Win32_Storage_EnhancedStorage",
|
"Win32_Storage_EnhancedStorage",
|
||||||
"Win32_Storage_FileSystem",
|
"Win32_Storage_FileSystem",
|
||||||
"Win32_System_Com_StructuredStorage",
|
"Win32_System_Com_StructuredStorage",
|
||||||
@@ -111,24 +113,20 @@ windows = { version = "0.56", default-features = false, features = [
|
|||||||
"Win32_System_Threading",
|
"Win32_System_Threading",
|
||||||
"Win32_System_ProcessStatus",
|
"Win32_System_ProcessStatus",
|
||||||
"Win32_System_WindowsProgramming",
|
"Win32_System_WindowsProgramming",
|
||||||
|
"Win32_System_LibraryLoader",
|
||||||
"Win32_UI_Shell_Common",
|
"Win32_UI_Shell_Common",
|
||||||
"Win32_UI_Shell_PropertiesSystem",
|
"Win32_UI_Shell_PropertiesSystem",
|
||||||
] }
|
"Win32_UI_WindowsAndMessaging",
|
||||||
windows-sys = { version = "0.52", default-features = false, features = [
|
|
||||||
"Win32_Foundation",
|
|
||||||
"Win32_Security",
|
|
||||||
"Win32_Storage",
|
|
||||||
"Win32_Storage_FileSystem",
|
|
||||||
"Win32_System_Kernel",
|
"Win32_System_Kernel",
|
||||||
"Win32_System_Threading",
|
|
||||||
"Win32_System_WindowsProgramming",
|
|
||||||
"Wdk",
|
"Wdk",
|
||||||
"Wdk_System",
|
"Wdk_System",
|
||||||
"Wdk_System_Threading",
|
"Wdk_System_Threading",
|
||||||
] }
|
] }
|
||||||
normpath = "1.0.1"
|
normpath = "1.2"
|
||||||
webview2-com = "0.30"
|
webview2-com = "0.31"
|
||||||
libloading = "0.8"
|
libloading = "0.8"
|
||||||
|
strsim = "0.11"
|
||||||
|
same-file = "1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.9"
|
tempfile = "3.9"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
max_width = 160
|
max_width = 140
|
||||||
use_small_heuristics = "Max"
|
use_small_heuristics = "Max"
|
||||||
indent_style = "Visual"
|
indent_style = "Visual"
|
||||||
unstable_features = true
|
unstable_features = true
|
||||||
|
|||||||
@@ -16,7 +16,15 @@ fn ropycopy<P1: AsRef<Path>, P2: AsRef<Path>>(source: &P1, dest: &P2) -> Result<
|
|||||||
let dest = dest.as_ref();
|
let dest = dest.as_ref();
|
||||||
|
|
||||||
// robocopy C:\source\something.new C:\destination\something /MIR /ZB /W:5 /R:5 /MT:8 /LOG:C:\logs\copy_log.txt
|
// robocopy C:\source\something.new C:\destination\something /MIR /ZB /W:5 /R:5 /MT:8 /LOG:C:\logs\copy_log.txt
|
||||||
let cmd = std::process::Command::new("robocopy").arg(source).arg(dest).arg("/MIR").arg("/IS").arg("/W:1").arg("/R:5").arg("/MT:2").output()?;
|
let cmd = std::process::Command::new("robocopy")
|
||||||
|
.arg(source)
|
||||||
|
.arg(dest)
|
||||||
|
.arg("/MIR")
|
||||||
|
.arg("/IS")
|
||||||
|
.arg("/W:1")
|
||||||
|
.arg("/R:5")
|
||||||
|
.arg("/MT:2")
|
||||||
|
.output()?;
|
||||||
|
|
||||||
let stdout = String::from_utf8_lossy(&cmd.stdout);
|
let stdout = String::from_utf8_lossy(&cmd.stdout);
|
||||||
let stderr = String::from_utf8_lossy(&cmd.stderr);
|
let stderr = String::from_utf8_lossy(&cmd.stderr);
|
||||||
@@ -33,28 +41,28 @@ fn ropycopy<P1: AsRef<Path>, P2: AsRef<Path>>(source: &P1, dest: &P2) -> Result<
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_package_impl<'a>(root_path: &PathBuf, app: &Manifest, package: &PathBuf, runhooks: bool) -> Result<Manifest> {
|
pub fn apply_package_impl(root_path: &PathBuf, old_app: &Manifest, package: &PathBuf, run_hooks: bool) -> Result<Manifest> {
|
||||||
let bundle = bundle::load_bundle_from_file(package)?;
|
let bundle = bundle::load_bundle_from_file(package)?;
|
||||||
let manifest = bundle.read_manifest()?;
|
let new_app = bundle.read_manifest()?;
|
||||||
|
|
||||||
let found_version = (manifest.version).to_owned();
|
let found_version = (new_app.version).to_owned();
|
||||||
info!("Applying package to current: {}", found_version);
|
info!("Applying package to current: {} (old version {})", found_version, old_app.version);
|
||||||
|
|
||||||
if !crate::windows::prerequisite::prompt_and_install_all_missing(&manifest, Some(&app.version))? {
|
if !crate::windows::prerequisite::prompt_and_install_all_missing(&new_app, Some(&old_app.version))? {
|
||||||
bail!("Stopping apply. Pre-requisites are missing and user cancelled.");
|
bail!("Stopping apply. Pre-requisites are missing and user cancelled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let packages_dir = app.get_packages_path(root_path);
|
let packages_dir = old_app.get_packages_path(root_path);
|
||||||
let packages_dir = Path::new(&packages_dir);
|
let packages_dir = Path::new(&packages_dir);
|
||||||
let current_dir = app.get_current_path(root_path);
|
let current_dir = old_app.get_current_path(root_path);
|
||||||
let temp_path_new = packages_dir.join(format!("tmp_{}", shared::random_string(16)));
|
let temp_path_new = packages_dir.join(format!("tmp_{}", shared::random_string(16)));
|
||||||
let temp_path_old = packages_dir.join(format!("tmp_{}", shared::random_string(16)));
|
let temp_path_old = packages_dir.join(format!("tmp_{}", shared::random_string(16)));
|
||||||
|
|
||||||
// open a dialog showing progress...
|
// open a dialog showing progress...
|
||||||
let (mut tx, _) = mpsc::channel::<i16>();
|
let (mut tx, _) = mpsc::channel::<i16>();
|
||||||
if !dialogs::get_silent() {
|
if !dialogs::get_silent() {
|
||||||
let title = format!("{} Update", &manifest.title);
|
let title = format!("{} Update", &new_app.title);
|
||||||
let message = format!("Installing update {}...", &manifest.version);
|
let message = format!("Installing update {}...", &new_app.version);
|
||||||
tx = splash::show_progress_dialog(title, message);
|
tx = splash::show_progress_dialog(title, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,15 +76,15 @@ pub fn apply_package_impl<'a>(root_path: &PathBuf, app: &Manifest, package: &Pat
|
|||||||
let _ = tx.send(splash::MSG_INDEFINITE);
|
let _ = tx.send(splash::MSG_INDEFINITE);
|
||||||
|
|
||||||
// second, run application hooks (but don't care if it fails)
|
// second, run application hooks (but don't care if it fails)
|
||||||
if runhooks {
|
if run_hooks {
|
||||||
crate::windows::run_hook(app, root_path, "--veloapp-obsolete", 15);
|
crate::windows::run_hook(old_app, root_path, "--veloapp-obsolete", 15);
|
||||||
} else {
|
} else {
|
||||||
info!("Skipping --veloapp-obsolete hook.");
|
info!("Skipping --veloapp-obsolete hook.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// third, we try _REALLY HARD_ to stop the package
|
// third, we try _REALLY HARD_ to stop the package
|
||||||
let _ = shared::force_stop_package(root_path);
|
let _ = shared::force_stop_package(root_path);
|
||||||
if winsafe::IsWindows10OrGreater() == Ok(true) && !locksmith::close_processes_locking_dir(&app.title, ¤t_dir) {
|
if winsafe::IsWindows10OrGreater() == Ok(true) && !locksmith::close_processes_locking_dir(&old_app.title, ¤t_dir) {
|
||||||
bail!("Failed to close processes locking directory / user cancelled.");
|
bail!("Failed to close processes locking directory / user cancelled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,9 +116,10 @@ pub fn apply_package_impl<'a>(root_path: &PathBuf, app: &Manifest, package: &Pat
|
|||||||
let _ = tx.send(splash::MSG_CLOSE);
|
let _ = tx.send(splash::MSG_CLOSE);
|
||||||
|
|
||||||
info!("Showing error dialog...");
|
info!("Showing error dialog...");
|
||||||
let title = format!("{} Update", &manifest.title);
|
let title = format!("{} Update", &new_app.title);
|
||||||
let header = "Failed to update";
|
let header = "Failed to update";
|
||||||
let body = format!("Failed to update {} to version {}. Please check the logs for more details.", &manifest.title, &manifest.version);
|
let body =
|
||||||
|
format!("Failed to update {} to version {}. Please check the logs for more details.", &new_app.title, &new_app.version);
|
||||||
dialogs::show_error(&title, Some(header), &body);
|
dialogs::show_error(&title, Some(header), &body);
|
||||||
|
|
||||||
bail!("Fatal error performing update.");
|
bail!("Fatal error performing update.");
|
||||||
@@ -119,17 +128,25 @@ pub fn apply_package_impl<'a>(root_path: &PathBuf, app: &Manifest, package: &Pat
|
|||||||
|
|
||||||
// from this point on, we're past the point of no return and should not bail
|
// from this point on, we're past the point of no return and should not bail
|
||||||
// sixth, we write the uninstall entry
|
// sixth, we write the uninstall entry
|
||||||
if let Err(e) = manifest.write_uninstall_entry(root_path) {
|
if let Err(e) = new_app.write_uninstall_entry(root_path) {
|
||||||
warn!("Failed to write uninstall entry ({}).", e);
|
warn!("Failed to write uninstall entry ({}).", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// seventh, we run the post-install hooks
|
// seventh, we run the post-install hooks
|
||||||
if runhooks {
|
if run_hooks {
|
||||||
crate::windows::run_hook(&manifest, &root_path, "--veloapp-updated", 15);
|
crate::windows::run_hook(&new_app, &root_path, "--veloapp-updated", 15);
|
||||||
} else {
|
} else {
|
||||||
info!("Skipping --veloapp-updated hook.");
|
info!("Skipping --veloapp-updated hook.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update application shortcuts
|
||||||
|
// should try and remove the temp dirs before recalculating the shortcuts,
|
||||||
|
// because windows may try to use the "Distributed Link Tracking and Object Identifiers (DLT) service"
|
||||||
|
// to update the shortcut to point at the temp/renamed location
|
||||||
|
let _ = remove_dir_all::remove_dir_all(&temp_path_new);
|
||||||
|
let _ = remove_dir_all::remove_dir_all(&temp_path_old);
|
||||||
|
crate::windows::create_or_update_manifest_lnks(root_path, &new_app, Some(old_app));
|
||||||
|
|
||||||
// done!
|
// done!
|
||||||
info!("Package applied successfully.");
|
info!("Package applied successfully.");
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -139,5 +156,5 @@ pub fn apply_package_impl<'a>(root_path: &PathBuf, app: &Manifest, package: &Pat
|
|||||||
let _ = remove_dir_all::remove_dir_all(&temp_path_new);
|
let _ = remove_dir_all::remove_dir_all(&temp_path_new);
|
||||||
let _ = remove_dir_all::remove_dir_all(&temp_path_old);
|
let _ = remove_dir_all::remove_dir_all(&temp_path_old);
|
||||||
action?;
|
action?;
|
||||||
Ok(manifest)
|
Ok(new_app)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ use crate::{
|
|||||||
shared::{self, bundle, runtime_arch::RuntimeArch},
|
shared::{self, bundle, runtime_arch::RuntimeArch},
|
||||||
windows,
|
windows,
|
||||||
};
|
};
|
||||||
|
use ::windows::core::PCWSTR;
|
||||||
|
use ::windows::Win32::Storage::FileSystem::GetDiskFreeSpaceExW;
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use memmap2::Mmap;
|
use memmap2::Mmap;
|
||||||
use pretty_bytes_rust::pretty_bytes;
|
use pretty_bytes_rust::pretty_bytes;
|
||||||
@@ -11,15 +13,14 @@ use std::{
|
|||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
use winsafe::{self as w, co};
|
|
||||||
|
|
||||||
pub fn install(debug_pkg: Option<&PathBuf>, install_to: Option<&PathBuf>) -> Result<()> {
|
pub fn install(debug_pkg: Option<&PathBuf>, install_to: Option<&PathBuf>) -> Result<()> {
|
||||||
let osinfo = os_info::get();
|
let osinfo = os_info::get();
|
||||||
let osarch = RuntimeArch::from_current_system();
|
let osarch = RuntimeArch::from_current_system();
|
||||||
info!("OS: {osinfo}, Arch={osarch:#?}");
|
info!("OS: {osinfo}, Arch={osarch:#?}");
|
||||||
|
|
||||||
if !w::IsWindows7OrGreater()? {
|
if !windows::is_windows_7_sp1_or_greater() {
|
||||||
bail!("This installer requires Windows 7 or later and cannot run.");
|
bail!("This installer requires Windows 7 SPA1 or later and cannot run.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let file = File::open(env::current_exe()?)?;
|
let file = File::open(env::current_exe()?)?;
|
||||||
@@ -51,7 +52,7 @@ pub fn install(debug_pkg: Option<&PathBuf>, install_to: Option<&PathBuf>) -> Res
|
|||||||
let (root_path, root_is_default) = if install_to.is_some() {
|
let (root_path, root_is_default) = if install_to.is_some() {
|
||||||
(install_to.unwrap().clone(), false)
|
(install_to.unwrap().clone(), false)
|
||||||
} else {
|
} else {
|
||||||
let appdata = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::LocalAppData, co::KF::DONT_UNEXPAND, None)?;
|
let appdata = windows::known_path::get_local_app_data()?;
|
||||||
(Path::new(&appdata).join(&app.id), true)
|
(Path::new(&appdata).join(&app.id), true)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,18 +67,26 @@ pub fn install(debug_pkg: Option<&PathBuf>, install_to: Option<&PathBuf>) -> Res
|
|||||||
// do we have enough disk space?
|
// do we have enough disk space?
|
||||||
let (compressed_size, extracted_size) = pkg.calculate_size();
|
let (compressed_size, extracted_size) = pkg.calculate_size();
|
||||||
let required_space = compressed_size + extracted_size + (50 * 1000 * 1000); // archive + velopack overhead
|
let required_space = compressed_size + extracted_size + (50 * 1000 * 1000); // archive + velopack overhead
|
||||||
|
|
||||||
let mut free_space: u64 = 0;
|
let mut free_space: u64 = 0;
|
||||||
w::GetDiskFreeSpaceEx(Some(&root_path_str), None, None, Some(&mut free_space))?;
|
let root_pcwstr = windows::strings::string_to_u16(root_path_str);
|
||||||
if free_space < required_space {
|
let root_pcwstr: PCWSTR = PCWSTR(root_pcwstr.as_ptr());
|
||||||
bail!(
|
if let Ok(()) = unsafe { GetDiskFreeSpaceExW(root_pcwstr, None, None, Some(&mut free_space)) } {
|
||||||
"{} requires at least {} disk space to be installed. There is only {} available.",
|
if free_space < required_space {
|
||||||
&app.title,
|
bail!(
|
||||||
pretty_bytes(required_space, None),
|
"{} requires at least {} disk space to be installed. There is only {} available.",
|
||||||
pretty_bytes(free_space, None)
|
&app.title,
|
||||||
);
|
pretty_bytes(required_space, None),
|
||||||
|
pretty_bytes(free_space, None)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("There is {} free space available at destination, this package requires {}.", pretty_bytes(free_space, None), pretty_bytes(required_space, None));
|
info!(
|
||||||
|
"There is {} free space available at destination, this package requires {}.",
|
||||||
|
pretty_bytes(free_space, None),
|
||||||
|
pretty_bytes(required_space, None)
|
||||||
|
);
|
||||||
|
|
||||||
// does this app support this OS / architecture?
|
// does this app support this OS / architecture?
|
||||||
if !app.os_min_version.is_empty() && !windows::is_os_version_or_greater(&app.os_min_version)? {
|
if !app.os_min_version.is_empty() && !windows::is_os_version_or_greater(&app.os_min_version)? {
|
||||||
@@ -99,8 +108,9 @@ pub fn install(debug_pkg: Option<&PathBuf>, install_to: Option<&PathBuf>) -> Res
|
|||||||
}
|
}
|
||||||
info!("User chose to overwrite existing installation.");
|
info!("User chose to overwrite existing installation.");
|
||||||
|
|
||||||
shared::force_stop_package(&root_path)
|
shared::force_stop_package(&root_path).map_err(|z| {
|
||||||
.map_err(|z| anyhow!("Failed to stop application ({}), please close the application and try running the installer again.", z))?;
|
anyhow!("Failed to stop application ({}), please close the application and try running the installer again.", z)
|
||||||
|
})?;
|
||||||
|
|
||||||
root_path_renamed = format!("{}_{}", root_path_str, shared::random_string(8));
|
root_path_renamed = format!("{}_{}", root_path_str, shared::random_string(8));
|
||||||
info!("Renaming existing directory to '{}' to allow rollback...", root_path_renamed);
|
info!("Renaming existing directory to '{}' to allow rollback...", root_path_renamed);
|
||||||
@@ -180,13 +190,17 @@ fn install_impl(pkg: &bundle::BundleInfo, root_path: &PathBuf, tx: &std::sync::m
|
|||||||
bail!("The main executable could not be found in the package. Please contact the application author.");
|
bail!("The main executable could not be found in the package. Please contact the application author.");
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Creating new default shortcuts...");
|
info!("Creating shortcuts...");
|
||||||
let _ = windows::create_default_lnks(&root_path, &app);
|
windows::create_or_update_manifest_lnks(&root_path, &app, None);
|
||||||
|
|
||||||
info!("Starting process install hook");
|
info!("Starting process install hook");
|
||||||
if windows::run_hook(&app, &root_path, "--veloapp-install", 30) == false {
|
if !windows::run_hook(&app, &root_path, "--veloapp-install", 30) {
|
||||||
let setup_name = format!("{} Setup {}", app.title, app.version);
|
let setup_name = format!("{} Setup {}", app.title, app.version);
|
||||||
dialogs::show_warn(&setup_name, None, "Installation has completed, but the application install hook failed. It may not have installed correctly.");
|
dialogs::show_warn(
|
||||||
|
&setup_name,
|
||||||
|
None,
|
||||||
|
"Installation has completed, but the application install hook failed. It may not have installed correctly.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = tx.send(100);
|
let _ = tx.send(100);
|
||||||
|
|||||||
@@ -13,7 +13,13 @@ use std::{
|
|||||||
};
|
};
|
||||||
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
||||||
|
|
||||||
pub fn start_impl(root_dir: &PathBuf, app: &Manifest, exe_name: Option<&String>, exe_args: Option<Vec<&str>>, legacy_args: Option<&String>) -> Result<()> {
|
pub fn start_impl(
|
||||||
|
root_dir: &PathBuf,
|
||||||
|
app: &Manifest,
|
||||||
|
exe_name: Option<&String>,
|
||||||
|
exe_args: Option<Vec<&str>>,
|
||||||
|
legacy_args: Option<&String>,
|
||||||
|
) -> Result<()> {
|
||||||
match shared::has_app_prefixed_folder(root_dir) {
|
match shared::has_app_prefixed_folder(root_dir) {
|
||||||
Ok(has_prefix) => {
|
Ok(has_prefix) => {
|
||||||
if has_prefix {
|
if has_prefix {
|
||||||
@@ -86,21 +92,19 @@ fn try_legacy_migration(root_dir: &PathBuf, app: &bundle::Manifest) -> Result<()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info!("Removing old shortcuts...");
|
||||||
|
win::remove_all_shortcuts_for_root_dir(root_dir);
|
||||||
|
|
||||||
|
let mut modified_app = app.clone();
|
||||||
|
modified_app.shortcut_locations = "".to_string(); // reset, so we install new shortcuts
|
||||||
|
|
||||||
info!("Applying latest full package...");
|
info!("Applying latest full package...");
|
||||||
let buf = Path::new(&package.file_path).to_path_buf();
|
let buf = Path::new(&package.file_path).to_path_buf();
|
||||||
super::apply(root_dir, app, false, OperationWait::NoWait, Some(&buf), None, false)?;
|
super::apply(root_dir, &modified_app, false, OperationWait::NoWait, Some(&buf), None, false)?;
|
||||||
|
|
||||||
info!("Removing old app-* folders...");
|
info!("Removing old app-* folders...");
|
||||||
shared::delete_app_prefixed_folders(root_dir)?;
|
shared::delete_app_prefixed_folders(root_dir)?;
|
||||||
let _ = remove_dir_all::remove_dir_all(root_dir.join("staging"));
|
let _ = remove_dir_all::remove_dir_all(root_dir.join("staging"));
|
||||||
|
|
||||||
info!("Removing old shortcuts...");
|
|
||||||
if let Err(e) = win::remove_all_shortcuts_for_root_dir(root_dir) {
|
|
||||||
warn!("Failed to remove shortcuts ({}).", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Creating new default shortcuts...");
|
|
||||||
let _ = win::create_default_lnks(root_dir, app);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,8 @@ pub fn uninstall(root_path: &PathBuf, app: &Manifest, delete_self: bool) -> Resu
|
|||||||
// run uninstall hook
|
// run uninstall hook
|
||||||
windows::run_hook(&app, root_path, "--veloapp-uninstall", 60);
|
windows::run_hook(&app, root_path, "--veloapp-uninstall", 60);
|
||||||
|
|
||||||
if let Err(e) = windows::remove_all_shortcuts_for_root_dir(&root_path) {
|
// remove all shortcuts pointing to the app
|
||||||
error!("Unable to remove shortcuts ({}).", e);
|
windows::remove_all_shortcuts_for_root_dir(&root_path);
|
||||||
// finished_with_errors = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Removing directory '{}'", root_path.to_string_lossy());
|
info!("Removing directory '{}'", root_path.to_string_lossy());
|
||||||
if let Err(e) = shared::retry_io(|| remove_dir_all::remove_dir_but_not_self(&root_path)) {
|
if let Err(e) = shared::retry_io(|| remove_dir_all::remove_dir_but_not_self(&root_path)) {
|
||||||
|
|||||||
@@ -373,7 +373,10 @@ impl BundleInfo<'_> {
|
|||||||
for i in 0..archive.len() {
|
for i in 0..archive.len() {
|
||||||
let file = archive.by_index(i)?;
|
let file = archive.by_index(i)?;
|
||||||
let key = file.enclosed_name().ok_or_else(|| {
|
let key = file.enclosed_name().ok_or_else(|| {
|
||||||
anyhow!("Could not extract file safely ({}). Ensure no paths in archive are absolute or point to a path outside the archive.", file.name())
|
anyhow!(
|
||||||
|
"Could not extract file safely ({}). Ensure no paths in archive are absolute or point to a path outside the archive.",
|
||||||
|
file.name()
|
||||||
|
)
|
||||||
})?;
|
})?;
|
||||||
files.push(key.to_string_lossy().to_string());
|
files.push(key.to_string_lossy().to_string());
|
||||||
}
|
}
|
||||||
@@ -396,6 +399,8 @@ pub struct Manifest {
|
|||||||
pub os: String,
|
pub os: String,
|
||||||
pub os_min_version: String,
|
pub os_min_version: String,
|
||||||
pub channel: String,
|
pub channel: String,
|
||||||
|
pub shortcut_locations: String,
|
||||||
|
pub shortcut_amuid: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
@@ -436,7 +441,8 @@ impl Manifest {
|
|||||||
let uninstall_cmd = format!("\"{}\" --uninstall", updater_path);
|
let uninstall_cmd = format!("\"{}\" --uninstall", updater_path);
|
||||||
let uninstall_quiet = format!("\"{}\" --uninstall --silent", updater_path);
|
let uninstall_quiet = format!("\"{}\" --uninstall --silent", updater_path);
|
||||||
|
|
||||||
let reg_uninstall = w::HKEY::CURRENT_USER.RegCreateKeyEx(Self::UNINST_STR, None, co::REG_OPTION::NoValue, co::KEY::CREATE_SUB_KEY, None)?.0;
|
let reg_uninstall =
|
||||||
|
w::HKEY::CURRENT_USER.RegCreateKeyEx(Self::UNINST_STR, None, co::REG_OPTION::NoValue, co::KEY::CREATE_SUB_KEY, None)?.0;
|
||||||
let reg_app = reg_uninstall.RegCreateKeyEx(&self.id, None, co::REG_OPTION::NoValue, co::KEY::ALL_ACCESS, None)?.0;
|
let reg_app = reg_uninstall.RegCreateKeyEx(&self.id, None, co::REG_OPTION::NoValue, co::KEY::ALL_ACCESS, None)?.0;
|
||||||
reg_app.RegSetKeyValue(None, Some("DisplayIcon"), w::RegistryValue::Sz(main_exe_path))?;
|
reg_app.RegSetKeyValue(None, Some("DisplayIcon"), w::RegistryValue::Sz(main_exe_path))?;
|
||||||
reg_app.RegSetKeyValue(None, Some("DisplayName"), w::RegistryValue::Sz(self.title.to_owned()))?;
|
reg_app.RegSetKeyValue(None, Some("DisplayName"), w::RegistryValue::Sz(self.title.to_owned()))?;
|
||||||
@@ -454,7 +460,8 @@ impl Manifest {
|
|||||||
}
|
}
|
||||||
pub fn remove_uninstall_entry(&self) -> Result<()> {
|
pub fn remove_uninstall_entry(&self) -> Result<()> {
|
||||||
info!("Removing uninstall registry keys...");
|
info!("Removing uninstall registry keys...");
|
||||||
let reg_uninstall = w::HKEY::CURRENT_USER.RegCreateKeyEx(Self::UNINST_STR, None, co::REG_OPTION::NoValue, co::KEY::CREATE_SUB_KEY, None)?.0;
|
let reg_uninstall =
|
||||||
|
w::HKEY::CURRENT_USER.RegCreateKeyEx(Self::UNINST_STR, None, co::REG_OPTION::NoValue, co::KEY::CREATE_SUB_KEY, None)?.0;
|
||||||
reg_uninstall.RegDeleteKey(&self.id)?;
|
reg_uninstall.RegDeleteKey(&self.id)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -497,6 +504,10 @@ pub fn read_manifest_from_string(xml: &str) -> Result<Manifest> {
|
|||||||
obj.os_min_version = text;
|
obj.os_min_version = text;
|
||||||
} else if el_name == "channel" {
|
} else if el_name == "channel" {
|
||||||
obj.channel = text;
|
obj.channel = text;
|
||||||
|
} else if el_name == "shortcutLocations" {
|
||||||
|
obj.shortcut_locations = text;
|
||||||
|
} else if el_name == "shortcutAmuid" {
|
||||||
|
obj.shortcut_amuid = text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(XmlEvent::EndElement { .. }) => {
|
Ok(XmlEvent::EndElement { .. }) => {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ fn get_download_agent() -> Result<ureq::Agent> {
|
|||||||
let mut tls_builder = native_tls::TlsConnector::builder();
|
let mut tls_builder = native_tls::TlsConnector::builder();
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
if !winsafe::IsWindows10OrGreater()? {
|
if !crate::windows::is_windows_10_or_greater() {
|
||||||
warn!("DANGER: Discontinued OS version. TLS certificate verification will be disabled.");
|
warn!("DANGER: Discontinued OS version. TLS certificate verification will be disabled.");
|
||||||
warn!("DANGER: Discontinued OS version. TLS certificate verification will be disabled.");
|
warn!("DANGER: Discontinued OS version. TLS certificate verification will be disabled.");
|
||||||
warn!("DANGER: Discontinued OS version. TLS certificate verification will be disabled.");
|
warn!("DANGER: Discontinued OS version. TLS certificate verification will be disabled.");
|
||||||
@@ -65,7 +65,10 @@ fn get_download_agent() -> Result<ureq::Agent> {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_download_uses_tls_and_encoding_correctly() {
|
fn test_download_uses_tls_and_encoding_correctly() {
|
||||||
assert_eq!(download_url_as_string("https://dotnetcli.blob.core.windows.net/dotnet/WindowsDesktop/5.0/latest.version").unwrap(), "5.0.17");
|
assert_eq!(
|
||||||
|
download_url_as_string("https://dotnetcli.blob.core.windows.net/dotnet/WindowsDesktop/5.0/latest.version").unwrap(),
|
||||||
|
"5.0.17"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use ::windows::Win32::System::ProcessStatus::EnumProcesses;
|
||||||
|
use ::windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
@@ -7,10 +9,8 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::Command as Process,
|
process::Command as Process,
|
||||||
};
|
};
|
||||||
use windows::Win32::System::ProcessStatus::EnumProcesses;
|
use windows::Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation};
|
||||||
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
use windows::Win32::System::Threading::{GetCurrentProcess, PROCESS_BASIC_INFORMATION};
|
||||||
use windows_sys::Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation};
|
|
||||||
use windows_sys::Win32::System::Threading::{GetCurrentProcess, PROCESS_BASIC_INFORMATION};
|
|
||||||
use winsafe::{self as w, co, prelude::*};
|
use winsafe::{self as w, co, prelude::*};
|
||||||
|
|
||||||
use super::bundle::{self, EntryNameInfo, Manifest};
|
use super::bundle::{self, EntryNameInfo, Manifest};
|
||||||
@@ -35,7 +35,7 @@ pub fn wait_for_parent_to_exit(ms_to_wait: u32) -> Result<()> {
|
|||||||
let mut info = PROCESS_BASIC_INFORMATION {
|
let mut info = PROCESS_BASIC_INFORMATION {
|
||||||
AffinityMask: 0,
|
AffinityMask: 0,
|
||||||
BasePriority: 0,
|
BasePriority: 0,
|
||||||
ExitStatus: 0,
|
ExitStatus: Default::default(),
|
||||||
InheritedFromUniqueProcessId: 0,
|
InheritedFromUniqueProcessId: 0,
|
||||||
PebBaseAddress: std::ptr::null_mut(),
|
PebBaseAddress: std::ptr::null_mut(),
|
||||||
UniqueProcessId: 0,
|
UniqueProcessId: 0,
|
||||||
@@ -43,10 +43,9 @@ pub fn wait_for_parent_to_exit(ms_to_wait: u32) -> Result<()> {
|
|||||||
|
|
||||||
let info_ptr: *mut ::core::ffi::c_void = &mut info as *mut _ as *mut ::core::ffi::c_void;
|
let info_ptr: *mut ::core::ffi::c_void = &mut info as *mut _ as *mut ::core::ffi::c_void;
|
||||||
let info_size = std::mem::size_of::<PROCESS_BASIC_INFORMATION>() as u32;
|
let info_size = std::mem::size_of::<PROCESS_BASIC_INFORMATION>() as u32;
|
||||||
let hr = unsafe { NtQueryInformationProcess(handle, basic_info, info_ptr, info_size, return_length_ptr) };
|
let hres = unsafe { NtQueryInformationProcess(handle, basic_info, info_ptr, info_size, return_length_ptr) };
|
||||||
|
if hres.is_err() {
|
||||||
if hr != 0 {
|
return Err(anyhow!("Failed to query process information: {:?}", hres));
|
||||||
return Err(anyhow!("Failed to query process information: {}", hr));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.InheritedFromUniqueProcessId <= 1 {
|
if info.InheritedFromUniqueProcessId <= 1 {
|
||||||
@@ -346,7 +345,7 @@ fn get_all_packages(root_path: &PathBuf) -> Vec<EntryNameInfo> {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_running_processes_finds_cargo() {
|
fn test_get_running_processes_finds_cargo() {
|
||||||
let profile = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::Profile, co::KF::DONT_UNEXPAND, None).unwrap();
|
let profile = crate::windows::known_path::get_user_profile().unwrap();
|
||||||
let path = Path::new(&profile);
|
let path = Path::new(&profile);
|
||||||
let rustup = path.join(".rustup");
|
let rustup = path.join(".rustup");
|
||||||
|
|
||||||
|
|||||||
61
src/Rust/src/windows/known_path.rs
Normal file
61
src/Rust/src/windows/known_path.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use std::path::Path;
|
||||||
|
use windows::{
|
||||||
|
core::GUID,
|
||||||
|
Win32::UI::Shell::{
|
||||||
|
FOLDERID_Desktop, FOLDERID_Downloads, FOLDERID_LocalAppData, FOLDERID_Profile, FOLDERID_ProgramFilesX64, FOLDERID_ProgramFilesX86,
|
||||||
|
FOLDERID_RoamingAppData, FOLDERID_StartMenu, FOLDERID_Startup, SHGetKnownFolderPath,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn get_known_folder(rfid: *const GUID) -> Result<String> {
|
||||||
|
unsafe {
|
||||||
|
let flag = windows::Win32::UI::Shell::KNOWN_FOLDER_FLAG(0);
|
||||||
|
let result = SHGetKnownFolderPath(rfid, flag, None)?;
|
||||||
|
super::strings::pwstr_to_string(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_local_app_data() -> Result<String> {
|
||||||
|
get_known_folder(&FOLDERID_LocalAppData)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_roaming_app_data() -> Result<String> {
|
||||||
|
get_known_folder(&FOLDERID_RoamingAppData)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_user_desktop() -> Result<String> {
|
||||||
|
get_known_folder(&FOLDERID_Desktop)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_user_profile() -> Result<String> {
|
||||||
|
get_known_folder(&FOLDERID_Profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_start_menu() -> Result<String> {
|
||||||
|
let start_menu = get_known_folder(&FOLDERID_StartMenu)?;
|
||||||
|
let programs_path = Path::new(&start_menu).join("Programs");
|
||||||
|
Ok(programs_path.to_string_lossy().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_startup() -> Result<String> {
|
||||||
|
get_known_folder(&FOLDERID_Startup)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_downloads() -> Result<String> {
|
||||||
|
get_known_folder(&FOLDERID_Downloads)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_program_files_x64() -> Result<String> {
|
||||||
|
get_known_folder(&FOLDERID_ProgramFilesX64)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_program_files_x86() -> Result<String> {
|
||||||
|
get_known_folder(&FOLDERID_ProgramFilesX86)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_user_pinned() -> Result<String> {
|
||||||
|
let pinned_str = get_roaming_app_data()?;
|
||||||
|
let pinned_path = Path::new(&pinned_str).join("Microsoft\\Internet Explorer\\Quick Launch\\User Pinned");
|
||||||
|
Ok(pinned_path.to_string_lossy().to_string())
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use windows_sys::Win32::System::LibraryLoader::{SetDefaultDllDirectories, LOAD_LIBRARY_SEARCH_SYSTEM32};
|
use windows::Win32::System::LibraryLoader::{SetDefaultDllDirectories, LOAD_LIBRARY_SEARCH_SYSTEM32};
|
||||||
/// This attempts to defend against malicious DLLs that may sit alongside
|
/// This attempts to defend against malicious DLLs that may sit alongside
|
||||||
/// our binary in the user's download folder.
|
/// our binary in the user's download folder.
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@@ -7,6 +7,6 @@ pub fn pre_main_sideload_mitigation() {
|
|||||||
// For DLLs loaded at load time, this relies on the `delayload` linker flag.
|
// For DLLs loaded at load time, this relies on the `delayload` linker flag.
|
||||||
// This is only necessary prior to Windows 10 RS1. See build.rs for details.
|
// This is only necessary prior to Windows 10 RS1. See build.rs for details.
|
||||||
unsafe {
|
unsafe {
|
||||||
SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32);
|
let _ = SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ pub mod mitigate;
|
|||||||
pub mod prerequisite;
|
pub mod prerequisite;
|
||||||
pub mod runtimes;
|
pub mod runtimes;
|
||||||
pub mod splash;
|
pub mod splash;
|
||||||
|
pub mod known_path;
|
||||||
|
pub mod strings;
|
||||||
|
|
||||||
mod self_delete;
|
mod self_delete;
|
||||||
mod shortcuts;
|
mod shortcuts;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ use crate::shared::{bundle, dialogs, download};
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use winsafe::{self as w, co};
|
|
||||||
|
|
||||||
pub fn prompt_and_install_all_missing(app: &bundle::Manifest, updating_from: Option<&semver::Version>) -> Result<bool> {
|
pub fn prompt_and_install_all_missing(app: &bundle::Manifest, updating_from: Option<&semver::Version>) -> Result<bool> {
|
||||||
info!("Checking application pre-requisites...");
|
info!("Checking application pre-requisites...");
|
||||||
@@ -38,7 +37,7 @@ pub fn prompt_and_install_all_missing(app: &bundle::Manifest, updating_from: Opt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let downloads = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::Downloads, co::KF::DONT_UNEXPAND, None)?;
|
let downloads = super::known_path::get_downloads()?;
|
||||||
let downloads = Path::new(downloads.as_str());
|
let downloads = Path::new(downloads.as_str());
|
||||||
|
|
||||||
info!("Downloading {} missing pre-requisites...", missing.len());
|
info!("Downloading {} missing pre-requisites...", missing.len());
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ use anyhow::{anyhow, bail, Result};
|
|||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::process::Command as Process;
|
use std::process::Command as Process;
|
||||||
use std::{collections::HashMap, fs, path::Path};
|
use std::{collections::HashMap, fs, path::Path};
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
use winsafe::{self as w, co, prelude::*};
|
use winsafe::{self as w, co, prelude::*};
|
||||||
|
|
||||||
const REDIST_2015_2022_X86: &str = "https://aka.ms/vs/17/release/vc_redist.x86.exe";
|
const REDIST_2015_2022_X86: &str = "https://aka.ms/vs/17/release/vc_redist.x86.exe";
|
||||||
const REDIST_2015_2022_X64: &str = "https://aka.ms/vs/17/release/vc_redist.x64.exe";
|
const REDIST_2015_2022_X64: &str = "https://aka.ms/vs/17/release/vc_redist.x64.exe";
|
||||||
const REDIST_2015_2022_ARM64: &str = "https://aka.ms/vs/17/release/vc_redist.arm64.exe";
|
const REDIST_2015_2022_ARM64: &str = "https://aka.ms/vs/17/release/vc_redist.arm64.exe";
|
||||||
@@ -307,7 +307,7 @@ fn get_dotnet_base_path(runtime_arch: RuntimeArch, runtime_type: DotnetRuntimeTy
|
|||||||
|
|
||||||
// it's easy to check if we're looking for x86 dotnet because it's always in the same place.
|
// it's easy to check if we're looking for x86 dotnet because it's always in the same place.
|
||||||
if runtime_arch == RuntimeArch::X86 {
|
if runtime_arch == RuntimeArch::X86 {
|
||||||
let pf32 = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::ProgramFilesX86, co::KF::DONT_UNEXPAND, None)?;
|
let pf32 = super::known_path::get_program_files_x86()?;
|
||||||
let join = Path::new(&pf32).join("dotnet").join(dotnet_path);
|
let join = Path::new(&pf32).join("dotnet").join(dotnet_path);
|
||||||
let result = join.to_str().ok_or_else(|| anyhow!("Unable to convert path to string."))?;
|
let result = join.to_str().ok_or_else(|| anyhow!("Unable to convert path to string."))?;
|
||||||
return Ok(result.to_string());
|
return Ok(result.to_string());
|
||||||
@@ -315,7 +315,7 @@ fn get_dotnet_base_path(runtime_arch: RuntimeArch, runtime_type: DotnetRuntimeTy
|
|||||||
|
|
||||||
// this only works in a 64 bit process, otherwise it throws
|
// this only works in a 64 bit process, otherwise it throws
|
||||||
#[cfg(not(target_arch = "x86"))]
|
#[cfg(not(target_arch = "x86"))]
|
||||||
let pf64 = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::ProgramFilesX64, co::KF::DONT_UNEXPAND, None)?;
|
let pf64 = super::known_path::get_program_files_x64()?;
|
||||||
|
|
||||||
// set by WOW64 for x86 processes. https://learn.microsoft.com/windows/win32/winprog64/wow64-implementation-details
|
// set by WOW64 for x86 processes. https://learn.microsoft.com/windows/win32/winprog64/wow64-implementation-details
|
||||||
#[cfg(target_arch = "x86")]
|
#[cfg(target_arch = "x86")]
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,8 +9,8 @@ use std::{
|
|||||||
sync::mpsc::{self, Receiver, Sender},
|
sync::mpsc::{self, Receiver, Sender},
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
use w::WString;
|
use winsafe::guard::DeleteObjectGuard;
|
||||||
use winsafe::{self as w, co, guard::DeleteObjectGuard, gui, prelude::*};
|
use winsafe::{self as w, co, gui, prelude::*, WString};
|
||||||
|
|
||||||
const TMR_GIF: usize = 1;
|
const TMR_GIF: usize = 1;
|
||||||
const MSG_NOMESSAGE: i16 = -99;
|
const MSG_NOMESSAGE: i16 = -99;
|
||||||
|
|||||||
30
src/Rust/src/windows/strings.rs
Normal file
30
src/Rust/src/windows/strings.rs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use windows::core::{HSTRING, PCWSTR, PWSTR};
|
||||||
|
|
||||||
|
pub fn string_to_u16<P: AsRef<str>>(input: P) -> Vec<u16> {
|
||||||
|
let input = input.as_ref();
|
||||||
|
input.encode_utf16().chain(Some(0)).collect::<Vec<u16>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pwstr_to_string(input: PWSTR) -> Result<String> {
|
||||||
|
unsafe {
|
||||||
|
let hstring = input.to_hstring()?;
|
||||||
|
let string = hstring.to_string_lossy();
|
||||||
|
Ok(string.trim_end_matches('\0').to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pcwstr_to_string(input: PCWSTR) -> Result<String> {
|
||||||
|
unsafe {
|
||||||
|
let hstring = input.to_hstring()?;
|
||||||
|
let string = hstring.to_string_lossy();
|
||||||
|
Ok(string.trim_end_matches('\0').to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn u16_to_string<T: AsRef<[u16]>>(input: T) -> Result<String> {
|
||||||
|
let input = input.as_ref();
|
||||||
|
let hstring = HSTRING::from_wide(input)?;
|
||||||
|
let string = hstring.to_string_lossy();
|
||||||
|
Ok(string.trim_end_matches('\0').to_string())
|
||||||
|
}
|
||||||
@@ -1,22 +1,24 @@
|
|||||||
use crate::shared::{self, runtime_arch::RuntimeArch};
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use normpath::PathExt;
|
|
||||||
use std::{
|
use std::{
|
||||||
os::windows::process::CommandExt,
|
os::windows::process::CommandExt,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::Command as Process,
|
process::Command as Process,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use normpath::PathExt;
|
||||||
use wait_timeout::ChildExt;
|
use wait_timeout::ChildExt;
|
||||||
|
use windows::core::PCWSTR;
|
||||||
|
use windows::Win32::Storage::FileSystem::GetLongPathNameW;
|
||||||
|
use windows::Win32::System::SystemInformation::{VerSetConditionMask, VerifyVersionInfoW, OSVERSIONINFOEXW, VER_FLAGS};
|
||||||
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
use windows::Win32::UI::WindowsAndMessaging::AllowSetForegroundWindow;
|
||||||
use windows::{
|
use windows::Win32::{
|
||||||
core::PCWSTR,
|
Foundation::{self, GetLastError},
|
||||||
Win32::{
|
System::Threading::CreateMutexW,
|
||||||
Foundation::{self, GetLastError},
|
|
||||||
System::Threading::CreateMutexW,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use winsafe::{self as w, co};
|
|
||||||
|
use crate::shared::{self, runtime_arch::RuntimeArch};
|
||||||
|
use crate::windows::strings::{string_to_u16, u16_to_string};
|
||||||
|
|
||||||
pub fn run_hook(app: &shared::bundle::Manifest, root_path: &PathBuf, hook_name: &str, timeout_secs: u64) -> bool {
|
pub fn run_hook(app: &shared::bundle::Manifest, root_path: &PathBuf, hook_name: &str, timeout_secs: u64) -> bool {
|
||||||
let sw = simple_stopwatch::Stopwatch::start_new();
|
let sw = simple_stopwatch::Stopwatch::start_new();
|
||||||
@@ -76,16 +78,63 @@ impl Drop for MutexDropGuard {
|
|||||||
pub fn create_global_mutex(app: &shared::bundle::Manifest) -> Result<MutexDropGuard> {
|
pub fn create_global_mutex(app: &shared::bundle::Manifest) -> Result<MutexDropGuard> {
|
||||||
let mutex_name = format!("velopack-{}", &app.id);
|
let mutex_name = format!("velopack-{}", &app.id);
|
||||||
info!("Attempting to open global system mutex: '{}'", &mutex_name);
|
info!("Attempting to open global system mutex: '{}'", &mutex_name);
|
||||||
let encoded = mutex_name.encode_utf16().chain([0u16]).collect::<Vec<u16>>();
|
let encodedu16 = super::strings::string_to_u16(mutex_name);
|
||||||
let pw = PCWSTR(encoded.as_ptr());
|
let encoded = PCWSTR(encodedu16.as_ptr());
|
||||||
let mutex = unsafe { CreateMutexW(None, true, pw) }?;
|
let mutex = unsafe { CreateMutexW(None, true, encoded) }?;
|
||||||
match unsafe { GetLastError() } {
|
match unsafe { GetLastError() } {
|
||||||
Foundation::ERROR_SUCCESS => Ok(MutexDropGuard { mutex }),
|
Foundation::ERROR_SUCCESS => Ok(MutexDropGuard { mutex }),
|
||||||
Foundation::ERROR_ALREADY_EXISTS => Err(anyhow!("Another installer or updater for this application is running, quit that process and try again.")),
|
Foundation::ERROR_ALREADY_EXISTS => {
|
||||||
|
Err(anyhow!("Another installer or updater for this application is running, quit that process and try again."))
|
||||||
|
}
|
||||||
err => Err(anyhow!("Unable to create global mutex. Error code {:?}", err)),
|
err => Err(anyhow!("Unable to create global mutex. Error code {:?}", err)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn expand_environment_strings<P: AsRef<str>>(input: P) -> Result<String> {
|
||||||
|
use windows::Win32::System::Environment::ExpandEnvironmentStringsW;
|
||||||
|
let encoded_u16 = super::strings::string_to_u16(input);
|
||||||
|
let encoded = PCWSTR(encoded_u16.as_ptr());
|
||||||
|
let mut buffer_size = unsafe { ExpandEnvironmentStringsW(encoded, None) };
|
||||||
|
if buffer_size == 0 {
|
||||||
|
return Err(anyhow!(windows::core::Error::from_win32()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buffer: Vec<u16> = vec![0; buffer_size as usize];
|
||||||
|
buffer_size = unsafe { ExpandEnvironmentStringsW(encoded, Some(&mut buffer)) };
|
||||||
|
if buffer_size == 0 {
|
||||||
|
return Err(anyhow!(windows::core::Error::from_win32()));
|
||||||
|
}
|
||||||
|
|
||||||
|
super::strings::u16_to_string(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_expand_environment_strings() {
|
||||||
|
assert_eq!(expand_environment_strings("%windir%").unwrap(), "C:\\Windows");
|
||||||
|
assert_eq!(expand_environment_strings("%windir%\\system32").unwrap(), "C:\\Windows\\system32");
|
||||||
|
assert_eq!(expand_environment_strings("%windir%\\system32\\").unwrap(), "C:\\Windows\\system32\\");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_long_path<P: AsRef<str>>(str: P) -> Result<String> {
|
||||||
|
let str = str.as_ref().to_string();
|
||||||
|
let str = string_to_u16(str);
|
||||||
|
let str = PCWSTR(str.as_ptr());
|
||||||
|
// SAFETY: str is a valid wide string, this call will return required size of buffer
|
||||||
|
let len = unsafe { GetLongPathNameW(str, None) };
|
||||||
|
if len == 0 {
|
||||||
|
return Err(anyhow!(windows::core::Error::from_win32()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut vec = vec![0u16; len as usize];
|
||||||
|
let len = unsafe { GetLongPathNameW(str, Some(vec.as_mut_slice())) };
|
||||||
|
if len == 0 {
|
||||||
|
return Err(anyhow!(windows::core::Error::from_win32()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = u16_to_string(vec)?;
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_sub_path<P1: AsRef<Path>, P2: AsRef<Path>>(path: P1, parent: P2) -> Result<bool> {
|
pub fn is_sub_path<P1: AsRef<Path>, P2: AsRef<Path>>(path: P1, parent: P2) -> Result<bool> {
|
||||||
let path = path.as_ref().to_string_lossy().to_lowercase();
|
let path = path.as_ref().to_string_lossy().to_lowercase();
|
||||||
let parent = parent.as_ref().to_string_lossy().to_lowercase();
|
let parent = parent.as_ref().to_string_lossy().to_lowercase();
|
||||||
@@ -104,8 +153,8 @@ pub fn is_sub_path<P1: AsRef<Path>, P2: AsRef<Path>>(path: P1, parent: P2) -> Re
|
|||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = w::ExpandEnvironmentStrings(&path)?;
|
let path = expand_environment_strings(&path)?;
|
||||||
let parent = w::ExpandEnvironmentStrings(&parent)?;
|
let parent = expand_environment_strings(&parent)?;
|
||||||
|
|
||||||
let path = Path::new(&path);
|
let path = Path::new(&path);
|
||||||
let parent = Path::new(&parent);
|
let parent = Path::new(&parent);
|
||||||
@@ -119,8 +168,24 @@ pub fn is_sub_path<P1: AsRef<Path>, P2: AsRef<Path>>(path: P1, parent: P2) -> Re
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calls GetFullPathNameW
|
// calls GetFullPathNameW
|
||||||
let path = path.normalize_virtually()?.as_path().to_string_lossy().to_lowercase();
|
let path = path.normalize().or_else(|_| path.normalize_virtually())?;
|
||||||
let parent = parent.normalize_virtually()?.as_path().to_string_lossy().to_lowercase();
|
let parent = parent.normalize().or_else(|_| parent.normalize_virtually())?;
|
||||||
|
|
||||||
|
let mut path = path.as_path().to_string_lossy().to_string();
|
||||||
|
let mut parent = parent.as_path().to_string_lossy().to_string();
|
||||||
|
|
||||||
|
// calls GetLongPathNameW
|
||||||
|
match get_long_path(&path) {
|
||||||
|
Ok(p) => path = p,
|
||||||
|
Err(e) => warn!("Failed to get long path for '{}': {}", path, e),
|
||||||
|
}
|
||||||
|
match get_long_path(&parent) {
|
||||||
|
Ok(p) => parent = p,
|
||||||
|
Err(e) => warn!("Failed to get long path for '{}': {}", parent, e),
|
||||||
|
}
|
||||||
|
|
||||||
|
path = path.to_lowercase();
|
||||||
|
parent = parent.to_lowercase();
|
||||||
|
|
||||||
let path = PathBuf::from(path);
|
let path = PathBuf::from(path);
|
||||||
let parent = PathBuf::from(parent);
|
let parent = PathBuf::from(parent);
|
||||||
@@ -169,10 +234,7 @@ fn test_is_sub_path_works_with_non_existing_paths() {
|
|||||||
let path = PathBuf::from(r"C:\AppData\JamLogic");
|
let path = PathBuf::from(r"C:\AppData\JamLogic");
|
||||||
let parent = PathBuf::from(r"C:\AppData\JamLogicDev");
|
let parent = PathBuf::from(r"C:\AppData\JamLogicDev");
|
||||||
assert!(!is_sub_path(&path, &parent).unwrap());
|
assert!(!is_sub_path(&path, &parent).unwrap());
|
||||||
|
assert!(!is_sub_path(&parent, &path).unwrap());
|
||||||
let path = PathBuf::from(r"C:\AppData\JamLogicDev");
|
|
||||||
let parent = PathBuf::from(r"C:\AppData\JamLogic");
|
|
||||||
assert!(!is_sub_path(&path, &parent).unwrap());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -202,15 +264,58 @@ fn test_is_sub_path_works_with_empty_paths() {
|
|||||||
assert!(!is_sub_path(&path, &parent).unwrap());
|
assert!(!is_sub_path(&path, &parent).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Version condition mask constants defined as per Windows SDK
|
||||||
|
const VER_GREATER_EQUAL: u8 = 3;
|
||||||
|
const VER_MINORVERSION: VER_FLAGS = VER_FLAGS(0x0000001);
|
||||||
|
const VER_MAJORVERSION: VER_FLAGS = VER_FLAGS(0x0000002);
|
||||||
|
const VER_BUILDNUMBER: VER_FLAGS = VER_FLAGS(0x0000004);
|
||||||
|
const VER_SERVICEPACKMAJOR: VER_FLAGS = VER_FLAGS(0x0000020);
|
||||||
|
|
||||||
|
fn is_os_version_or_greater_internal(major: u16, minor: u16, build: u16, service_pack: u16) -> bool {
|
||||||
|
let flags = VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut mask: u64 = 0;
|
||||||
|
mask = VerSetConditionMask(mask, VER_MAJORVERSION, VER_GREATER_EQUAL);
|
||||||
|
mask = VerSetConditionMask(mask, VER_MINORVERSION, VER_GREATER_EQUAL);
|
||||||
|
mask = VerSetConditionMask(mask, VER_BUILDNUMBER, VER_GREATER_EQUAL);
|
||||||
|
mask = VerSetConditionMask(mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
|
||||||
|
|
||||||
|
let mut osvi: OSVERSIONINFOEXW = Default::default();
|
||||||
|
osvi.dwMajorVersion = major.into();
|
||||||
|
osvi.dwMinorVersion = minor.into();
|
||||||
|
osvi.dwBuildNumber = build.into();
|
||||||
|
osvi.wServicePackMajor = service_pack.into();
|
||||||
|
|
||||||
|
VerifyVersionInfoW(&mut osvi, flags, mask).is_ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_windows_10_or_greater() -> bool {
|
||||||
|
is_os_version_or_greater_internal(10, 0, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_windows_7_sp1_or_greater() -> bool {
|
||||||
|
is_os_version_or_greater_internal(6, 1, 0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_windows_8_or_greater() -> bool {
|
||||||
|
is_os_version_or_greater_internal(6, 2, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_windows_8_1_or_greater() -> bool {
|
||||||
|
is_os_version_or_greater_internal(6, 3, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_os_version_or_greater(version: &str) -> Result<bool> {
|
pub fn is_os_version_or_greater(version: &str) -> Result<bool> {
|
||||||
let (mut major, mut minor, mut build, _) = shared::parse_version(version)?;
|
let (mut major, mut minor, mut build, _) = shared::parse_version(version)?;
|
||||||
|
|
||||||
if major < 8 {
|
if major < 8 {
|
||||||
return Ok(w::IsWindows7OrGreater()?);
|
return Ok(is_windows_7_sp1_or_greater());
|
||||||
}
|
}
|
||||||
|
|
||||||
if major == 8 {
|
if major == 8 {
|
||||||
return Ok(if minor >= 1 { w::IsWindows8Point1OrGreater()? } else { w::IsWindows8OrGreater()? });
|
return Ok(if minor >= 1 { is_windows_8_or_greater() } else { is_windows_8_1_or_greater() });
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions
|
// https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions
|
||||||
@@ -222,20 +327,7 @@ pub fn is_os_version_or_greater(version: &str) -> Result<bool> {
|
|||||||
minor = 0;
|
minor = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if major == 10 && build <= 0 {
|
Ok(is_os_version_or_greater_internal(major.try_into()?, minor.try_into()?, build.try_into()?, 0))
|
||||||
return Ok(w::IsWindows10OrGreater()?);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut mask: u64 = 0;
|
|
||||||
mask = w::VerSetConditionMask(mask, co::VER_MASK::MAJORVERSION, co::VER_COND::GREATER_EQUAL);
|
|
||||||
mask = w::VerSetConditionMask(mask, co::VER_MASK::MINORVERSION, co::VER_COND::GREATER_EQUAL);
|
|
||||||
mask = w::VerSetConditionMask(mask, co::VER_MASK::BUILDNUMBER, co::VER_COND::GREATER_EQUAL);
|
|
||||||
|
|
||||||
let mut osvi: w::OSVERSIONINFOEX = Default::default();
|
|
||||||
osvi.dwMajorVersion = major;
|
|
||||||
osvi.dwMinorVersion = minor;
|
|
||||||
osvi.dwBuildNumber = build;
|
|
||||||
return Ok(w::VerifyVersionInfo(&mut osvi, co::VER_MASK::MAJORVERSION | co::VER_MASK::MINORVERSION | co::VER_MASK::BUILDNUMBER, mask)?);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use std::{fs, path::Path, path::PathBuf};
|
|||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
use velopack::*;
|
use velopack::*;
|
||||||
|
|
||||||
|
use velopack::logging::trace_logger;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
use winsafe::{self as w, co};
|
use winsafe::{self as w, co};
|
||||||
|
|
||||||
@@ -13,16 +14,26 @@ use winsafe::{self as w, co};
|
|||||||
#[test]
|
#[test]
|
||||||
pub fn test_install_apply_uninstall() {
|
pub fn test_install_apply_uninstall() {
|
||||||
dialogs::set_silent(true);
|
dialogs::set_silent(true);
|
||||||
|
trace_logger();
|
||||||
|
|
||||||
let fixtures = find_fixtures();
|
let fixtures = find_fixtures();
|
||||||
|
|
||||||
let app_id = "AvaloniaCrossPlat";
|
let app_id = "AvaloniaCrossPlat";
|
||||||
let pkg_name = "AvaloniaCrossPlat-1.0.11-win-full.nupkg";
|
let pkg_name = "AvaloniaCrossPlat-1.0.11-win-full.nupkg";
|
||||||
|
|
||||||
let startmenu = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::StartMenu, co::KF::DONT_UNEXPAND, None).unwrap();
|
let start_menu = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::StartMenu, co::KF::DONT_UNEXPAND, None).unwrap();
|
||||||
let lnk_path = Path::new(&startmenu).join("Programs").join(format!("{}.lnk", app_id));
|
let start_menu = Path::new(&start_menu).join("Programs");
|
||||||
if lnk_path.exists() {
|
let desktop = w::SHGetKnownFolderPath(&co::KNOWNFOLDERID::Desktop, co::KF::DONT_UNEXPAND, None).unwrap();
|
||||||
fs::remove_file(&lnk_path).unwrap();
|
let desktop = Path::new(&desktop);
|
||||||
}
|
|
||||||
|
let lnk_start_1 = start_menu.join(format!("{}.lnk", app_id));
|
||||||
|
let lnk_desktop_1 = desktop.join(format!("{}.lnk", app_id));
|
||||||
|
let lnk_start_2 = start_menu.join(format!("{}.lnk", "AvaloniaCross Updated"));
|
||||||
|
let lnk_desktop_2 = desktop.join(format!("{}.lnk", "AvaloniaCross Updated"));
|
||||||
|
let _ = fs::remove_file(&lnk_start_1);
|
||||||
|
let _ = fs::remove_file(&lnk_desktop_1);
|
||||||
|
let _ = fs::remove_file(&lnk_start_2);
|
||||||
|
let _ = fs::remove_file(&lnk_desktop_2);
|
||||||
|
|
||||||
let nupkg = fixtures.join(pkg_name);
|
let nupkg = fixtures.join(pkg_name);
|
||||||
|
|
||||||
@@ -30,26 +41,38 @@ pub fn test_install_apply_uninstall() {
|
|||||||
let tmp_buf = tmp_dir.path().to_path_buf();
|
let tmp_buf = tmp_dir.path().to_path_buf();
|
||||||
commands::install(Some(&nupkg), Some(&tmp_buf)).unwrap();
|
commands::install(Some(&nupkg), Some(&tmp_buf)).unwrap();
|
||||||
|
|
||||||
assert!(lnk_path.exists());
|
assert!(!lnk_desktop_1.exists()); // desktop is created during update
|
||||||
|
assert!(lnk_start_1.exists());
|
||||||
|
|
||||||
assert!(tmp_buf.join("Update.exe").exists());
|
assert!(tmp_buf.join("Update.exe").exists());
|
||||||
assert!(tmp_buf.join("current").join("AvaloniaCrossPlat.exe").exists());
|
assert!(tmp_buf.join("current").join("AvaloniaCrossPlat.exe").exists());
|
||||||
assert!(tmp_buf.join("current").join("sq.version").exists());
|
assert!(tmp_buf.join("current").join("sq.version").exists());
|
||||||
|
|
||||||
let (root_dir, app) = shared::detect_manifest_from_update_path(&tmp_buf.join("Update.exe")).unwrap();
|
let (root_dir, app) = shared::detect_manifest_from_update_path(&tmp_buf.join("Update.exe")).unwrap();
|
||||||
assert_eq!(app_id, app.id);
|
assert_eq!(app_id, app.id);
|
||||||
assert!(semver::Version::parse("1.0.11").unwrap() == app.version);
|
assert_eq!(semver::Version::parse("1.0.11").unwrap(), app.version);
|
||||||
|
|
||||||
let pkg_name_apply = "AvaloniaCrossPlat-1.0.15-win-full.nupkg";
|
let pkg_name_apply = "AvaloniaCrossPlat-1.0.15-win-full.nupkg";
|
||||||
let nupkg_apply = fixtures.join(pkg_name_apply);
|
let nupkg_apply = fixtures.join(pkg_name_apply);
|
||||||
commands::apply(&root_dir, &app, false, shared::OperationWait::NoWait, Some(&nupkg_apply), None, false).unwrap();
|
commands::apply(&root_dir, &app, false, shared::OperationWait::NoWait, Some(&nupkg_apply), None, false).unwrap();
|
||||||
|
|
||||||
|
// shortcuts are renamed, and desktop is created
|
||||||
|
assert!(!lnk_desktop_1.exists());
|
||||||
|
assert!(!lnk_start_1.exists());
|
||||||
|
assert!(lnk_desktop_2.exists());
|
||||||
|
assert!(lnk_start_2.exists());
|
||||||
|
|
||||||
let (root_dir, app) = shared::detect_manifest_from_update_path(&tmp_buf.join("Update.exe")).unwrap();
|
let (root_dir, app) = shared::detect_manifest_from_update_path(&tmp_buf.join("Update.exe")).unwrap();
|
||||||
assert!(semver::Version::parse("1.0.15").unwrap() == app.version);
|
assert_eq!(semver::Version::parse("1.0.15").unwrap(), app.version);
|
||||||
|
|
||||||
commands::uninstall(&root_dir, &app, false).unwrap();
|
commands::uninstall(&root_dir, &app, false).unwrap();
|
||||||
assert!(!tmp_buf.join("current").exists());
|
assert!(!tmp_buf.join("current").exists());
|
||||||
assert!(tmp_buf.join(".dead").exists());
|
assert!(tmp_buf.join(".dead").exists());
|
||||||
assert!(!lnk_path.exists());
|
|
||||||
|
assert!(!lnk_desktop_1.exists());
|
||||||
|
assert!(!lnk_start_1.exists());
|
||||||
|
assert!(!lnk_desktop_2.exists());
|
||||||
|
assert!(!lnk_start_2.exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions>
|
|||||||
// add nuspec metadata
|
// add nuspec metadata
|
||||||
ExtraNuspecMetadata["runtimeDependencies"] = GetRuntimeDependencies();
|
ExtraNuspecMetadata["runtimeDependencies"] = GetRuntimeDependencies();
|
||||||
ExtraNuspecMetadata["shortcutLocations"] = GetShortcutLocations();
|
ExtraNuspecMetadata["shortcutLocations"] = GetShortcutLocations();
|
||||||
ExtraNuspecMetadata["shortcutAmuid"] = Utility.CreateGuidFromHash(Options.PackId).ToString();
|
ExtraNuspecMetadata["shortcutAmuid"] = Utility.GetAppUserModelId(Options.PackId);
|
||||||
|
|
||||||
// copy files to temp dir, so we can modify them
|
// copy files to temp dir, so we can modify them
|
||||||
var dir = TempDir.CreateSubdirectory("PreprocessPackDirWin");
|
var dir = TempDir.CreateSubdirectory("PreprocessPackDirWin");
|
||||||
|
|||||||
@@ -52,12 +52,10 @@ public class WindowsPackCommand : PackCommand
|
|||||||
.SetHidden()
|
.SetHidden()
|
||||||
.SetDefault(10);
|
.SetDefault(10);
|
||||||
|
|
||||||
//AddOption<string>((v) => Shortcuts = v, "--shortcuts")
|
AddOption<string>((v) => Shortcuts = v, "--shortcuts")
|
||||||
// .SetDescription("List of locations to install shortcuts to during setup.")
|
.SetDescription("List of locations to install shortcuts to during setup.")
|
||||||
// .SetArgumentHelpName("LOC")
|
.SetArgumentHelpName("LOC")
|
||||||
// .SetDefault("Desktop,StartMenuRoot")
|
.SetDefault("Desktop,StartMenuRoot");
|
||||||
// .SetHidden(true); // this argument currently has no effect
|
|
||||||
Shortcuts = "Desktop,StartMenuRoot";
|
|
||||||
|
|
||||||
if (VelopackRuntimeInfo.IsWindows) {
|
if (VelopackRuntimeInfo.IsWindows) {
|
||||||
var signParams = AddOption<string>((v) => SignParameters = v, "--signParams", "-n")
|
var signParams = AddOption<string>((v) => SignParameters = v, "--signParams", "-n")
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ namespace Velopack
|
|||||||
if (String.IsNullOrWhiteSpace(channel) || channel.ToLower() == "win") {
|
if (String.IsNullOrWhiteSpace(channel) || channel.ToLower() == "win") {
|
||||||
return "RELEASES";
|
return "RELEASES";
|
||||||
}
|
}
|
||||||
|
|
||||||
// all other cases the RELEASES file includes the channel name.
|
// all other cases the RELEASES file includes the channel name.
|
||||||
return $"RELEASES-{channel.ToLower()}";
|
return $"RELEASES-{channel.ToLower()}";
|
||||||
}
|
}
|
||||||
@@ -212,10 +213,14 @@ namespace Velopack
|
|||||||
|
|
||||||
public static void Retry(this Action block, int retries = 4, int retryDelay = 250, ILogger? logger = null)
|
public static void Retry(this Action block, int retries = 4, int retryDelay = 250, ILogger? logger = null)
|
||||||
{
|
{
|
||||||
Retry(() => {
|
Retry(
|
||||||
block();
|
() => {
|
||||||
return true;
|
block();
|
||||||
}, retries, retryDelay, logger);
|
return true;
|
||||||
|
},
|
||||||
|
retries,
|
||||||
|
retryDelay,
|
||||||
|
logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T Retry<T>(this Func<T> block, int retries = 4, int retryDelay = 250, ILogger? logger = null)
|
public static T Retry<T>(this Func<T> block, int retries = 4, int retryDelay = 250, ILogger? logger = null)
|
||||||
@@ -237,10 +242,14 @@ namespace Velopack
|
|||||||
|
|
||||||
public static Task RetryAsync(this Func<Task> block, int retries = 4, int retryDelay = 250, ILogger? logger = null)
|
public static Task RetryAsync(this Func<Task> block, int retries = 4, int retryDelay = 250, ILogger? logger = null)
|
||||||
{
|
{
|
||||||
return RetryAsync(async () => {
|
return RetryAsync(
|
||||||
await block().ConfigureAwait(false);
|
async () => {
|
||||||
return true;
|
await block().ConfigureAwait(false);
|
||||||
}, retries, retryDelay, logger);
|
return true;
|
||||||
|
},
|
||||||
|
retries,
|
||||||
|
retryDelay,
|
||||||
|
logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<T> RetryAsync<T>(this Func<Task<T>> block, int retries = 4, int retryDelay = 250, ILogger? logger = null)
|
public static async Task<T> RetryAsync<T>(this Func<Task<T>> block, int retries = 4, int retryDelay = 250, ILogger? logger = null)
|
||||||
@@ -276,11 +285,12 @@ namespace Velopack
|
|||||||
{
|
{
|
||||||
return Task.WhenAll(
|
return Task.WhenAll(
|
||||||
from partition in Partitioner.Create(source).GetPartitions(degreeOfParallelism)
|
from partition in Partitioner.Create(source).GetPartitions(degreeOfParallelism)
|
||||||
select Task.Run(async () => {
|
select Task.Run(
|
||||||
using (partition)
|
async () => {
|
||||||
while (partition.MoveNext())
|
using (partition)
|
||||||
await body(partition.Current).ConfigureAwait(false);
|
while (partition.MoveNext())
|
||||||
}));
|
await body(partition.Current).ConfigureAwait(false);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -299,6 +309,7 @@ namespace Velopack
|
|||||||
else
|
else
|
||||||
safeName.Append('_');
|
safeName.Append('_');
|
||||||
}
|
}
|
||||||
|
|
||||||
safeFileName = safeName.ToString();
|
safeFileName = safeName.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,13 +476,16 @@ namespace Velopack
|
|||||||
// retry a few times. if a directory in this tree is open in Windows Explorer,
|
// retry a few times. if a directory in this tree is open in Windows Explorer,
|
||||||
// it might be locked for a little while WE cleans up handles
|
// it might be locked for a little while WE cleans up handles
|
||||||
try {
|
try {
|
||||||
Retry(() => {
|
Retry(
|
||||||
try {
|
() => {
|
||||||
deleteMe();
|
try {
|
||||||
} catch (DirectoryNotFoundException) {
|
deleteMe();
|
||||||
return; // good!
|
} catch (DirectoryNotFoundException) {
|
||||||
}
|
return; // good!
|
||||||
}, retries: 4, retryDelay: 50);
|
}
|
||||||
|
},
|
||||||
|
retries: 4,
|
||||||
|
retryDelay: 50);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
logger?.Warn(ex, $"Unable to delete child '{fileSystemInfo.FullName}'");
|
logger?.Warn(ex, $"Unable to delete child '{fileSystemInfo.FullName}'");
|
||||||
throw;
|
throw;
|
||||||
@@ -519,10 +533,9 @@ namespace Velopack
|
|||||||
// .OrderByDescending(x => x.Version);
|
// .OrderByDescending(x => x.Version);
|
||||||
//}
|
//}
|
||||||
|
|
||||||
public static string GetAppUserModelId(string packageId, string exeName)
|
public static string GetAppUserModelId(string packageId)
|
||||||
{
|
{
|
||||||
return String.Format("com.velopack.{0}.{1}", packageId.Replace(" ", ""),
|
return $"velopack.{packageId}";
|
||||||
exeName.Replace(".exe", "").Replace(" ", ""));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsHttpUrl(string urlOrPath)
|
public static bool IsHttpUrl(string urlOrPath)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
@@ -22,6 +23,9 @@ namespace Velopack
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class VelopackApp
|
public sealed class VelopackApp
|
||||||
{
|
{
|
||||||
|
[DllImport("shell32.dll", SetLastError = true)]
|
||||||
|
private static extern void SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID);
|
||||||
|
|
||||||
internal static ILogger DefaultLogger { get; private set; } = NullLogger.Instance;
|
internal static ILogger DefaultLogger { get; private set; } = NullLogger.Instance;
|
||||||
|
|
||||||
internal static IVelopackLocator? DefaultLocator { get; private set; }
|
internal static IVelopackLocator? DefaultLocator { get; private set; }
|
||||||
@@ -165,6 +169,12 @@ namespace Velopack
|
|||||||
|
|
||||||
log.Info("Starting Velopack App (Run).");
|
log.Info("Starting Velopack App (Run).");
|
||||||
|
|
||||||
|
if (VelopackRuntimeInfo.IsWindows && locator.AppId != null) {
|
||||||
|
var appUserModelId = Utility.GetAppUserModelId(locator.AppId);
|
||||||
|
log.Info($"Setting current process explicit AppUserModelID to '{appUserModelId}'");
|
||||||
|
SetCurrentProcessExplicitAppUserModelID(appUserModelId);
|
||||||
|
}
|
||||||
|
|
||||||
// first, we run any fast exit hooks
|
// first, we run any fast exit hooks
|
||||||
VelopackHook defaultBlock = ((v) => { });
|
VelopackHook defaultBlock = ((v) => { });
|
||||||
var fastExitlookup = new[] {
|
var fastExitlookup = new[] {
|
||||||
@@ -227,6 +237,7 @@ namespace Velopack
|
|||||||
if (package.Type == VelopackAssetType.Full && (package.Version == latestLocal?.Version || package.Version == myVersion)) {
|
if (package.Type == VelopackAssetType.Full && (package.Version == latestLocal?.Version || package.Version == myVersion)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
log.Info("Removing old package: " + package.FileName);
|
log.Info("Removing old package: " + package.FileName);
|
||||||
var p = Path.Combine(pkgPath, package.FileName);
|
var p = Path.Combine(pkgPath, package.FileName);
|
||||||
@@ -245,6 +256,7 @@ namespace Velopack
|
|||||||
log.Error(ex, $"Error occurred executing user defined Velopack hook. (firstrun)");
|
log.Error(ex, $"Error occurred executing user defined Velopack hook. (firstrun)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (restarted) {
|
if (restarted) {
|
||||||
try {
|
try {
|
||||||
_restarted?.Invoke(myVersion);
|
_restarted?.Invoke(myVersion);
|
||||||
@@ -261,4 +273,4 @@ namespace Velopack
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,6 +196,7 @@ public class WindowsPackTests
|
|||||||
PackVersion = version,
|
PackVersion = version,
|
||||||
TargetRuntime = RID.Parse("win-x64"),
|
TargetRuntime = RID.Parse("win-x64"),
|
||||||
PackDirectory = tmpOutput,
|
PackDirectory = tmpOutput,
|
||||||
|
Shortcuts = "Desktop,StartMenuRoot",
|
||||||
};
|
};
|
||||||
|
|
||||||
var runner = GetPackRunner(logger);
|
var runner = GetPackRunner(logger);
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user