From ecca557fe38227f5de752bb946ca6eafc036bb23 Mon Sep 17 00:00:00 2001 From: Caelan Sayler Date: Wed, 30 Apr 2025 15:03:43 +0100 Subject: [PATCH] Temporarily reverse Rust code --- samples/CSharpAvalonia/dev-scripts/build.sh | 2 +- src/bins/src/commands/apply_windows_impl.rs | 24 +- src/bins/src/commands/install.rs | 28 +- src/bins/src/commands/uninstall.rs | 13 +- src/bins/src/setup.rs | 117 +----- src/bins/src/update.rs | 63 +--- src/bins/src/windows/mod.rs | 1 - src/bins/src/windows/process.rs | 344 ------------------ src/bins/src/windows/util.rs | 32 +- src/bins/tests/commands.rs | 5 +- .../Locators/WindowsVelopackLocator.cs | 84 +---- src/lib-csharp/UpdateExe.cs | 3 - src/lib-rust/src/locator.rs | 24 +- 13 files changed, 72 insertions(+), 668 deletions(-) delete mode 100644 src/bins/src/windows/process.rs diff --git a/samples/CSharpAvalonia/dev-scripts/build.sh b/samples/CSharpAvalonia/dev-scripts/build.sh index 14087dcb..250db7b3 100755 --- a/samples/CSharpAvalonia/dev-scripts/build.sh +++ b/samples/CSharpAvalonia/dev-scripts/build.sh @@ -57,4 +57,4 @@ dotnet publish -c Release --self-contained -r "$RID" -o "$PUBLISH_DIR" -p:UseLoc echo "" echo "Building Velopack Release v$BUILD_VERSION" -"$SCRIPT_DIR/../../../build/Debug/net8.0/vpk" pack -u VelopackCSharpAvalonia -v $BUILD_VERSION -o "$RELEASE_DIR" -p "$PUBLISH_DIR" --msi \ No newline at end of file +"$SCRIPT_DIR/../../../build/Debug/net8.0/vpk" pack -u VelopackCSharpAvalonia -v $BUILD_VERSION -o "$RELEASE_DIR" -p "$PUBLISH_DIR" \ No newline at end of file diff --git a/src/bins/src/commands/apply_windows_impl.rs b/src/bins/src/commands/apply_windows_impl.rs index 7704a250..637ebf1e 100644 --- a/src/bins/src/commands/apply_windows_impl.rs +++ b/src/bins/src/commands/apply_windows_impl.rs @@ -1,7 +1,8 @@ use crate::{ dialogs, shared::{self}, - windows::{self, splash}, + // windows::locksmith, + windows::splash, }; use anyhow::{bail, Context, Result}; use std::sync::mpsc; @@ -39,30 +40,11 @@ use velopack::{bundle::load_bundle_from_file, constants, locator::VelopackLocato // } pub fn apply_package_impl(old_locator: &VelopackLocator, package: &PathBuf, run_hooks: bool) -> Result { - let root_path = old_locator.get_root_dir(); - let mut bundle = load_bundle_from_file(package)?; let new_app_manifest = bundle.read_manifest()?; let new_locator = old_locator.clone_self_with_new_manifest(&new_app_manifest); - if !windows::is_directory_writable(&root_path) { - if windows::process::is_process_elevated() { - bail!("The root directory is not writable & process is already admin."); - } else { - info!("Re-launching as administrator to update in {:?}", root_path); - - let package_string = package.to_string_lossy().to_string(); - let args = vec!["--norestart".to_string(), "--package".to_string(), package_string]; - let process_handle = windows::process::relaunch_self_as_admin(args)?; - //Wait for process to finish - let ms_to_wait = 5 * 1000 * 60; // 5 minutes - let pid = process_handle.pid(); - - shared::wait_for_pid_to_exit(pid, ms_to_wait)?; - return Ok(new_locator); - } - } - + let root_path = old_locator.get_root_dir(); let old_version = old_locator.get_manifest_version(); let new_version = new_locator.get_manifest_version(); diff --git a/src/bins/src/commands/install.rs b/src/bins/src/commands/install.rs index 7b1d07bf..0bb0c3e7 100644 --- a/src/bins/src/commands/install.rs +++ b/src/bins/src/commands/install.rs @@ -4,23 +4,24 @@ use crate::{ windows, }; use velopack::bundle::BundleZip; -use velopack::constants; use velopack::locator::*; +use velopack::constants; -use ::windows::core::PCWSTR; -use ::windows::Win32::Storage::FileSystem::GetDiskFreeSpaceExW; use anyhow::{anyhow, bail, Result}; use pretty_bytes_rust::pretty_bytes; use std::{ fs::{self}, - path::PathBuf, + path::{Path, PathBuf}, }; +use ::windows::core::PCWSTR; +use ::windows::Win32::Storage::FileSystem::GetDiskFreeSpaceExW; -pub fn install(pkg: &mut BundleZip, install_to: (PathBuf, bool), start_args: Option>) -> Result<()> { +pub fn install(pkg: &mut BundleZip, install_to: Option<&PathBuf>, start_args: Option>) -> Result<()> { // find and parse nuspec info!("Reading package manifest..."); let app = pkg.read_manifest()?; + info!("Package manifest loaded successfully."); info!(" Package ID: {}", &app.id); info!(" Package Version: {}", &app.version); @@ -30,13 +31,19 @@ pub fn install(pkg: &mut BundleZip, install_to: (PathBuf, bool), start_args: Opt info!(" Package Machine Architecture: {}", &app.machine_architecture); info!(" Package Runtime Dependencies: {}", &app.runtime_dependencies); - let (root_path, root_is_default) = install_to; - if !windows::prerequisite::prompt_and_install_all_missing(&app, None)? { info!("Cancelling setup. Pre-requisites not installed."); return Ok(()); } + info!("Determining install directory..."); + let (root_path, root_is_default) = if install_to.is_some() { + (install_to.unwrap().clone(), false) + } else { + let appdata = windows::known_path::get_local_app_data()?; + (Path::new(&appdata).join(&app.id), true) + }; + // path needs to exist for future operations (disk space etc) if !root_path.exists() { shared::retry_io(|| fs::create_dir_all(&root_path))?; @@ -145,12 +152,7 @@ pub fn install(pkg: &mut BundleZip, install_to: (PathBuf, bool), start_args: Opt Ok(()) } -fn install_impl( - pkg: &mut BundleZip, - locator: &VelopackLocator, - tx: &std::sync::mpsc::Sender, - start_args: Option>, -) -> Result<()> { +fn install_impl(pkg: &mut BundleZip, locator: &VelopackLocator, tx: &std::sync::mpsc::Sender, start_args: Option>) -> Result<()> { info!("Starting installation!"); // all application paths diff --git a/src/bins/src/commands/uninstall.rs b/src/bins/src/commands/uninstall.rs index a200ba42..3c0e0127 100644 --- a/src/bins/src/commands/uninstall.rs +++ b/src/bins/src/commands/uninstall.rs @@ -2,7 +2,7 @@ use crate::shared::{self}; use velopack::{constants, locator::VelopackLocator}; use crate::windows; -use anyhow::{bail, Result}; +use anyhow::Result; use std::fs::File; pub fn uninstall(locator: &VelopackLocator, delete_self: bool) -> Result<()> { @@ -10,17 +10,6 @@ pub fn uninstall(locator: &VelopackLocator, delete_self: bool) -> Result<()> { let root_path = locator.get_root_dir(); - if !windows::is_directory_writable(&root_path) { - if windows::process::is_process_elevated() { - bail!("The root directory is not writable & process is already admin."); - } else { - info!("Re-launching as administrator to uninstall from {:?}", root_path); - let args = vec!["uninstall".to_string()]; - windows::process::relaunch_self_as_admin(args)?; - return Ok(()); - } - } - fn _uninstall_impl(locator: &VelopackLocator) -> bool { let root_path = locator.get_root_dir(); diff --git a/src/bins/src/setup.rs b/src/bins/src/setup.rs index 31bca759..b6a29a57 100644 --- a/src/bins/src/setup.rs +++ b/src/bins/src/setup.rs @@ -8,9 +8,7 @@ use anyhow::{bail, Result}; use clap::{arg, value_parser, Command}; use memmap2::Mmap; use std::fs::File; -use std::path::Path; use std::{env, path::PathBuf}; -use velopack::bundle::BundleZip; use velopack_bins::*; #[used] @@ -67,7 +65,6 @@ fn main_inner() -> Result<()> { .arg(arg!(-v --verbose "Print debug messages to console")) .arg(arg!(-l --log "Enable file logging and set location").required(false).value_parser(value_parser!(PathBuf))) .arg(arg!(-t --installto "Installation directory to install the application").required(false).value_parser(value_parser!(PathBuf))) - .arg(arg!(-b --bootstrap "Just apply install files, do not write uninstall registry keys")) .arg(arg!([EXE_ARGS] "Arguments to pass to the started executable. Must be preceded by '--'.").required(false).last(true).num_args(0..)); if cfg!(debug_assertions) { @@ -75,34 +72,6 @@ fn main_inner() -> Result<()> { .arg(arg!(-d --debug "Debug mode, install from a nupkg file").required(false).value_parser(value_parser!(PathBuf))); } - if let Err(e) = run_inner(arg_config) { - let error_string = format!("An error has occurred: {:?}", e); - if let Ok(downcast) = e.downcast::() { - let output_string = downcast.to_string(); - match downcast.kind() { - clap::error::ErrorKind::DisplayHelp => { - println!("{output_string}"); - return Ok(()); - } - clap::error::ErrorKind::DisplayHelpOnMissingArgumentOrSubcommand => { - println!("{output_string}"); - return Ok(()); - } - clap::error::ErrorKind::DisplayVersion => { - println!("{output_string}"); - return Ok(()); - } - _ => {} - } - } - error!("{}", error_string); - dialogs::show_error("Setup Error", None, &error_string); - } - - Ok(()) -} - -fn run_inner(arg_config: Command) -> Result<()> { let matches = arg_config.try_get_matches()?; let silent = matches.get_flag("silent"); @@ -140,15 +109,13 @@ fn run_inner(arg_config: Command) -> Result<()> { bail!("This installer requires Windows 7 SPA1 or later and cannot run."); } - let file = File::open(env::current_exe()?)?; - let mmap = unsafe { Mmap::map(&file)? }; - let mut bundle: Option = None; - // in debug mode only, allow a nupkg to be passed in as the first argument if cfg!(debug_assertions) { if let Some(pkg) = debug { info!("Loading bundle from DEBUG nupkg file {:?}...", pkg); - bundle = Some(velopack::bundle::load_bundle_from_file(pkg)?); + let mut bundle = velopack::bundle::load_bundle_from_file(pkg)?; + commands::install(&mut bundle, install_to, exe_args)?; + return Ok(()); } } @@ -158,76 +125,14 @@ fn run_inner(arg_config: Command) -> Result<()> { // try to load the bundle from embedded zip if offset > 0 && length > 0 { - bundle = Some(velopack::bundle::load_bundle_from_memory(&mmap[offset as usize..(offset + length) as usize])?); + info!("Loading bundle from embedded zip..."); + let file = File::open(env::current_exe()?)?; + let mmap = unsafe { Mmap::map(&file)? }; + let zip_range: &[u8] = &mmap[offset as usize..(offset + length) as usize]; + let mut bundle = velopack::bundle::load_bundle_from_memory(&zip_range)?; + commands::install(&mut bundle, install_to, exe_args)?; + return Ok(()); } - if bundle.is_none() { - bail!("Could not find embedded zip file. Please contact the application author."); - } - - let mut bundle = bundle.unwrap(); - - info!("Reading package manifest..."); - let app = bundle.read_manifest()?; - - info!("Determining install directory..."); - let (root_path, root_is_default) = if install_to.is_some() { - (install_to.unwrap().clone(), false) - } else { - let appdata = windows::known_path::get_local_app_data()?; - (Path::new(&appdata).join(&app.id), true) - }; - - let bar = root_path.parent(); - info!("Parent dir: {:?}", bar); - - if let Some(parent_dir) = root_path.parent() { - info!("Checking if directory is writable: {:?}", parent_dir); - if !windows::is_directory_writable(parent_dir) { - if windows::process::is_process_elevated() { - bail!("The installation directory is not writable & process is already admin. Please select a different directory."); - } - - // re-launch as admin - info!("Re-launching as administrator to install to {:?}", root_path); - - let mut args: Vec = Vec::new(); - if silent { - args.push("--silent".to_string()); - } - - if verbose { - args.push("--verbose".to_string()); - } - - if let Some(debug) = debug { - args.push("--debug".to_string()); - args.push(debug.to_string_lossy().to_string()); - } - - if let Some(logfile) = logfile { - args.push("--log".to_string()); - args.push(logfile.to_string_lossy().to_string()); - } - - if let Some(install_to) = install_to { - args.push("--installto".to_string()); - args.push(install_to.to_string_lossy().to_string()); - } - - if let Some(exe_args) = exe_args { - args.push("--".to_string()); - for arg in exe_args { - args.push(arg.to_string()); - } - } - - windows::process::relaunch_self_as_admin(args)?; - info!("Successfully re-launched as administrator."); - return Ok(()); - } - } - - commands::install(&mut bundle, (root_path, root_is_default), exe_args)?; - Ok(()) + bail!("Could not find embedded zip file. Please contact the application author."); } diff --git a/src/bins/src/update.rs b/src/bins/src/update.rs index 05dee9cf..414131e8 100644 --- a/src/bins/src/update.rs +++ b/src/bins/src/update.rs @@ -6,9 +6,9 @@ extern crate log; use anyhow::{anyhow, bail, Result}; use clap::{arg, value_parser, ArgMatches, Command}; -use std::{env, path::PathBuf, time::Duration}; +use std::{env, path::PathBuf}; use velopack::locator::{auto_locate_app_manifest, LocationContext}; -use velopack::{constants, logging::*}; +use velopack::logging::*; use velopack_bins::{shared::OperationWait, *}; #[rustfmt::skip] @@ -22,7 +22,6 @@ fn root_command() -> Command { .arg(arg!(-w --wait "Wait for the parent process to terminate before applying the update").hide(true)) .arg(arg!(--waitPid "Wait for the specified process to terminate before applying the update").value_parser(value_parser!(u32))) .arg(arg!(-p --package "Update package to apply").value_parser(value_parser!(PathBuf))) - .arg(arg!(-t --installto "Installation directory for the application").required(false).value_parser(value_parser!(PathBuf))) .arg(arg!([EXE_ARGS] "Arguments to pass to the started executable. Must be preceded by '--'.").required(false).last(true).num_args(0..)) ) .subcommand(Command::new("start") @@ -192,71 +191,23 @@ fn get_exe_args(matches: &ArgMatches) -> Option> { matches.get_many::("EXE_ARGS").map(|v| v.map(|f| f.as_str()).collect()) } -fn get_apply_args(matches: &ArgMatches) -> (OperationWait, bool, Option<&PathBuf>, Option<&PathBuf>, Option>) { +fn get_apply_args(matches: &ArgMatches) -> (OperationWait, bool, Option<&PathBuf>, Option>) { let restart = !get_flag_or_false(&matches, "norestart"); let package = matches.get_one::("package"); - let install_to = matches.get_one::("installto"); let exe_args = get_exe_args(matches); let wait = get_op_wait(&matches); - (wait, restart, package, install_to, exe_args) + (wait, restart, package, exe_args) } fn apply(matches: &ArgMatches) -> Result<()> { - let (wait, restart, package, install_to, exe_args) = get_apply_args(matches); + let (wait, restart, package, exe_args) = get_apply_args(matches); info!("Command: Apply"); info!(" Restart: {:?}", restart); info!(" Wait: {:?}", wait); info!(" Package: {:?}", package); - info!(" App Location: {:?}", install_to); info!(" Exe Args: {:?}", exe_args); - let locator = match install_to { - None => auto_locate_app_manifest(LocationContext::IAmUpdateExe)?, - Some(path) => auto_locate_app_manifest(LocationContext::FromSpecifiedRootDir(path.to_path_buf()))?, - }; - - #[cfg(windows)] - if !windows::is_directory_writable(locator.get_root_dir()) { - if windows::process::is_process_elevated() { - bail!("The installation directory {:?} is not writable & process is already admin.", locator.get_root_dir_as_string()); - } else { - let mut args: Vec = Vec::new(); - if dialogs::get_silent() { - args.push("--silent".to_string()); - } - args.push("apply".to_string()); - args.push("--norestart".to_string()); - - if let Some(install_to) = install_to { - args.push("--installto".to_string()); - args.push(install_to.to_string_lossy().to_string()); - } - - if let Some(package) = package { - args.push("--package".to_string()); - args.push(package.to_string_lossy().to_string()); - } - - if let OperationWait::WaitPid(pid) = wait { - args.push("--waitPid".to_string()); - args.push(pid.to_string()); - } else if let OperationWait::WaitParent = wait { - args.push("--wait".to_string()); - } - - let safe_handle = windows::process::relaunch_self_as_admin(args)?; - info!("Successfully re-launched as administrator."); - - if restart { - info!("Waiting for the application to exit before restarting."); - windows::process::wait_process_timeout(safe_handle.handle(), Duration::from_secs(300))?; - info!("Restarting the application after the update."); - shared::start_package(&locator, exe_args, Some(constants::HOOK_ENV_RESTART))?; - } - - } - return Ok(()); - } + let locator = auto_locate_app_manifest(LocationContext::IAmUpdateExe)?; let _mutex = locator.try_get_exclusive_lock()?; let _ = commands::apply(&locator, restart, wait, package, exe_args, true)?; Ok(()) @@ -297,7 +248,7 @@ fn uninstall(_matches: &ArgMatches) -> Result<()> { fn test_cli_parse_handles_equals_spaces() { let command = vec!["C:\\Some Path\\With = Spaces\\Update.exe", "apply", "--package", "C:\\Some Path\\With = Spaces\\Package.zip"]; let matches = try_parse_command_line_matches(command.iter().map(|s| s.to_string()).collect()).unwrap(); - let (wait, restart, package, _install_to, exe_args) = get_apply_args(matches.subcommand_matches("apply").unwrap()); + let (wait, restart, package, exe_args) = get_apply_args(matches.subcommand_matches("apply").unwrap()); assert_eq!(wait, OperationWait::NoWait); assert_eq!(restart, true); diff --git a/src/bins/src/windows/mod.rs b/src/bins/src/windows/mod.rs index 707265f3..674cfb52 100644 --- a/src/bins/src/windows/mod.rs +++ b/src/bins/src/windows/mod.rs @@ -6,7 +6,6 @@ pub mod splash; pub mod known_path; pub mod strings; pub mod registry; -pub mod process; pub mod webview2; mod self_delete; diff --git a/src/bins/src/windows/process.rs b/src/bins/src/windows/process.rs deleted file mode 100644 index 89858208..00000000 --- a/src/bins/src/windows/process.rs +++ /dev/null @@ -1,344 +0,0 @@ -use std::{ - collections::HashMap, - ffi::{OsStr, OsString}, - os::{raw::c_void, windows::ffi::OsStrExt}, - time::Duration, -}; - -use anyhow::{bail, Result}; -use windows::{ - core::{PCWSTR, PWSTR}, - Win32::{ - Foundation::{CloseHandle, HANDLE, WAIT_OBJECT_0, WAIT_TIMEOUT}, - Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION}, - System::Threading::{ - CreateProcessW, GetCurrentProcess, GetExitCodeProcess, GetProcessId, OpenProcessToken, WaitForSingleObject, CREATE_NO_WINDOW, - PROCESS_CREATION_FLAGS, STARTUPINFOW, STARTUPINFOW_FLAGS, - }, - UI::{ - Shell::{ShellExecuteExW, SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW}, - WindowsAndMessaging::AllowSetForegroundWindow, - }, - }, -}; - -use super::strings::string_to_u16; - -enum Arg { - /// Add quotes (if needed) - Regular(OsString), - // Append raw string without quoting - //Raw(OsString), -} - -enum Quote { - // Every arg is quoted - Always, - // Whitespace and empty args are quoted - Auto, - // Arg appended without any changes (#29494) - //Never, -} - -fn ensure_no_nuls>(str: T) -> Result { - if str.as_ref().encode_wide().any(|b| b == 0) { - bail!("nul byte found in provided data"); - } else { - Ok(str) - } -} - -fn append_arg(cmd: &mut Vec, arg: &Arg, force_quotes: bool) -> Result<()> { - let (arg, quote) = match arg { - Arg::Regular(arg) => (arg, if force_quotes { Quote::Always } else { Quote::Auto }) - //Arg::Raw(arg) => (arg, Quote::Never), - }; - - // If an argument has 0 characters then we need to quote it to ensure - // that it actually gets passed through on the command line or otherwise - // it will be dropped entirely when parsed on the other end. - ensure_no_nuls(arg)?; - let arg_bytes = arg.as_encoded_bytes(); - let (quote, escape) = match quote { - Quote::Always => (true, true), - Quote::Auto => (arg_bytes.iter().any(|c| *c == b' ' || *c == b'\t') || arg_bytes.is_empty(), true), - //Quote::Never => (false, false), - }; - if quote { - cmd.push('"' as u16); - } - - let mut backslashes: usize = 0; - for x in arg.encode_wide() { - if escape { - if x == '\\' as u16 { - backslashes += 1; - } else { - if x == '"' as u16 { - // Add n+1 backslashes to total 2n+1 before internal '"'. - cmd.extend((0..=backslashes).map(|_| '\\' as u16)); - } - backslashes = 0; - } - } - cmd.push(x); - } - - if quote { - // Add n backslashes to total 2n before ending '"'. - cmd.extend((0..backslashes).map(|_| '\\' as u16)); - cmd.push('"' as u16); - } - Ok(()) -} - -fn make_command_line(argv0: Option<&OsStr>, args: &[Arg], force_quotes: bool) -> Result> { - // Encode the command and arguments in a command line string such - // that the spawned process may recover them using CommandLineToArgvW. - let mut cmd: Vec = Vec::new(); - - // Always quote the program name so CreateProcess to avoid ambiguity when - // the child process parses its arguments. - // Note that quotes aren't escaped here because they can't be used in arg0. - // But that's ok because file paths can't contain quotes. - if let Some(argv0) = argv0 { - cmd.push(b'"' as u16); - cmd.extend(argv0.encode_wide()); - cmd.push(b'"' as u16); - cmd.push(' ' as u16); - } - - for arg in args { - append_arg(&mut cmd, arg, force_quotes)?; - cmd.push(' ' as u16); - } - - cmd.push(0); - Ok(cmd) -} - -fn make_envp(maybe_env: Option>) -> Result<(Option<*const c_void>, Vec)> { - // On Windows we pass an "environment block" which is not a char**, but - // rather a concatenation of null-terminated k=v\0 sequences, with a final - // \0 to terminate. - if let Some(env) = maybe_env { - let mut blk = Vec::new(); - - // If there are no environment variables to set then signal this by - // pushing a null. - if env.is_empty() { - blk.push(0); - } - - for (k, v) in env { - let os_key = OsString::from(k); - let os_value = OsString::from(v); - blk.extend(ensure_no_nuls(os_key)?.encode_wide()); - blk.push('=' as u16); - blk.extend(ensure_no_nuls(os_value)?.encode_wide()); - blk.push(0); - } - blk.push(0); - Ok((Some(blk.as_ptr() as *mut c_void), blk)) - } else { - Ok((None, Vec::new())) - } -} - -fn make_dirp(d: Option) -> Result<(PCWSTR, Vec)> { - match d { - Some(dir) => { - let dir = OsString::from(dir); - let mut dir_str: Vec = ensure_no_nuls(dir)?.encode_wide().collect(); - dir_str.push(0); - Ok((PCWSTR(dir_str.as_ptr()), dir_str)) - } - None => Ok((PCWSTR::null(), Vec::new())), - } -} - -pub fn is_process_elevated() -> bool { - // Get the current process handle - let process = unsafe { GetCurrentProcess() }; - - // Variable to hold the process token - let mut token: HANDLE = HANDLE::default(); - - // Open the process token with the TOKEN_QUERY access rights - unsafe { - if OpenProcessToken(process, windows::Win32::Security::TOKEN_QUERY, &mut token).is_ok() { - // Allocate a buffer for the TOKEN_ELEVATION structure - let mut elevation = TOKEN_ELEVATION::default(); - let mut size: u32 = 0; - - let elevation_ptr: *mut core::ffi::c_void = &mut elevation as *mut _ as *mut _; - - // Query the token information to check if it is elevated - if GetTokenInformation(token, TokenElevation, Some(elevation_ptr), std::mem::size_of::() as u32, &mut size) - .is_ok() - { - // Return whether the token is elevated - let _ = CloseHandle(token); - return elevation.TokenIsElevated != 0; - } - } - } - - // Clean up the token handle - if !token.is_invalid() { - unsafe { - let _ = CloseHandle(token); - }; - } - - false -} - -pub struct SafeProcessHandle { - handle: HANDLE, - pid: u32, -} - -impl Drop for SafeProcessHandle { - fn drop(&mut self) { - if !self.handle.is_invalid() { - let _ = unsafe { CloseHandle(self.handle) }; - } - } -} - -impl SafeProcessHandle { - pub fn handle(&self) -> HANDLE { - self.handle - } - - pub fn pid(&self) -> u32 { - self.pid - } -} - -// impl Into for SafeProcessHandle { -// fn into(self) -> u32 { -// self.1 -// } -// } - -pub fn relaunch_self_as_admin(args: Vec) -> Result { - let exe = std::env::current_exe()?; - let exe_path = exe.to_string_lossy().into_owned(); - - run_process_as_admin(exe_path, args, None) -} - -pub fn run_process_as_admin(exe_path: String, args: Vec, work_dir: Option) -> Result { - let verb = string_to_u16("runas"); - let verb = PCWSTR(verb.as_ptr()); - - let exe = string_to_u16(exe_path); - let exe = PCWSTR(exe.as_ptr()); - - let args: Vec = args.iter().map(|a| Arg::Regular(a.into())).collect(); - - let params = make_command_line(None, &args, false)?; - let params = PCWSTR(params.as_ptr()); - - let work_dir = work_dir.map(|w| string_to_u16(w)).map(|f| PCWSTR(f.as_ptr())).unwrap_or(PCWSTR::null()); - - let mut exe_info: SHELLEXECUTEINFOW = SHELLEXECUTEINFOW { - cbSize: std::mem::size_of::() as u32, - fMask: SEE_MASK_NOCLOSEPROCESS, - lpVerb: verb, - lpFile: exe, - lpParameters: params, - lpDirectory: work_dir, - nShow: windows::Win32::UI::WindowsAndMessaging::SW_NORMAL.0, - ..Default::default() - }; - - unsafe { - ShellExecuteExW(&mut exe_info as *mut SHELLEXECUTEINFOW)?; - let process_id = GetProcessId(exe_info.hProcess); - let _ = AllowSetForegroundWindow(process_id); - - Ok(SafeProcessHandle { handle: exe_info.hProcess, pid: process_id }) - } -} - -pub fn run_process( - exe_path: String, - args: Vec, - work_dir: Option, - set_env: Option>, - show_window: bool, -) -> Result { - let exe_path = OsString::from(exe_path); - let exe_name = PCWSTR(exe_path.encode_wide().chain(Some(0)).collect::>().as_mut_ptr()); - - let args: Vec = args.iter().map(|a| Arg::Regular(a.into())).collect(); - let mut params = make_command_line(Some(&exe_path), &args, false)?; - let params = PWSTR(params.as_mut_ptr()); - - let mut pi = windows::Win32::System::Threading::PROCESS_INFORMATION::default(); - - let si = STARTUPINFOW { - cb: std::mem::size_of::() as u32, - lpReserved: PWSTR::null(), - lpDesktop: PWSTR::null(), - lpTitle: PWSTR::null(), - dwX: 0, - dwY: 0, - dwXSize: 0, - dwYSize: 0, - dwXCountChars: 0, - dwYCountChars: 0, - dwFillAttribute: 0, - dwFlags: STARTUPINFOW_FLAGS(0), - wShowWindow: 0, - cbReserved2: 0, - lpReserved2: std::ptr::null_mut(), - hStdInput: HANDLE(std::ptr::null_mut()), - hStdOutput: HANDLE(std::ptr::null_mut()), - hStdError: HANDLE(std::ptr::null_mut()), - }; - - let envp = make_envp(set_env)?; - let dirp = make_dirp(work_dir)?; - - let flags = if show_window { PROCESS_CREATION_FLAGS(0) } else { CREATE_NO_WINDOW }; - - unsafe { - CreateProcessW(exe_name, Option::Some(params), None, None, false, flags, envp.0, dirp.0, &si, &mut pi)?; - let _ = AllowSetForegroundWindow(pi.dwProcessId); - let _ = CloseHandle(pi.hThread); - } - - Ok(SafeProcessHandle{ handle: pi.hProcess, pid: pi.dwProcessId }) -} - -pub fn wait_process_timeout(process: HANDLE, dur: Duration) -> std::io::Result> { - let ms = dur - .as_secs() - .checked_mul(1000) - .and_then(|amt| amt.checked_add((dur.subsec_nanos() / 1_000_000) as u64)) - .expect("failed to convert duration to milliseconds"); - let ms: u32 = if ms > (u32::max_value() as u64) { u32::max_value() } else { ms as u32 }; - unsafe { - match WaitForSingleObject(process, ms) { - WAIT_OBJECT_0 => {} - WAIT_TIMEOUT => return Ok(None), - _ => return Err(std::io::Error::last_os_error()), - } - - let mut exit_code = 0; - GetExitCodeProcess(process, &mut exit_code)?; - - Ok(Some(exit_code)) - } -} - -pub fn kill_process(process: HANDLE) -> std::io::Result<()> { - unsafe { - let _ = CloseHandle(process); - } - Ok(()) -} diff --git a/src/bins/src/windows/util.rs b/src/bins/src/windows/util.rs index 2e91ab9b..ac966a6b 100644 --- a/src/bins/src/windows/util.rs +++ b/src/bins/src/windows/util.rs @@ -1,5 +1,7 @@ use std::{ + os::windows::process::CommandExt, path::{Path, PathBuf}, + process::Command as Process, time::Duration, }; @@ -7,52 +9,48 @@ use velopack::locator::VelopackLocator; use anyhow::{anyhow, Result}; use normpath::PathExt; +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::Foundation; +use crate::shared::{self, runtime_arch::RuntimeArch}; use crate::windows::strings::{string_to_u16, u16_to_string}; -use crate::{ - shared::{self, runtime_arch::RuntimeArch}, - windows::process, -}; pub fn run_hook(locator: &VelopackLocator, hook_name: &str, timeout_secs: u64) -> bool { let sw = simple_stopwatch::Stopwatch::start_new(); let root_dir = locator.get_root_dir(); let current_path = locator.get_current_bin_dir(); - let main_exe_path = locator.get_main_exe_path_as_string(); + let main_exe_path = locator.get_main_exe_path(); let ver_string = locator.get_manifest_version_full_string(); let args = vec![hook_name, &ver_string]; let mut success = false; info!("Running {} hook...", hook_name); - let cmd = process::run_process( - main_exe_path, - args.iter().map(|f| f.to_string()).collect(), - Some(current_path.to_string_lossy().to_string()), - None, - false, - ); + const CREATE_NO_WINDOW: u32 = 0x08000000; + let cmd = Process::new(&main_exe_path).args(args).current_dir(¤t_path).creation_flags(CREATE_NO_WINDOW).spawn(); if let Err(e) = cmd { warn!("Failed to start hook {}: {}", hook_name, e); return false; } - let cmd = cmd.unwrap(); + let mut cmd = cmd.unwrap(); + let _ = unsafe { AllowSetForegroundWindow(cmd.id()) }; - match process::wait_process_timeout(cmd.handle(), Duration::from_secs(timeout_secs)) { + match cmd.wait_timeout(Duration::from_secs(timeout_secs)) { Ok(Some(status)) => { - if status == 0 { + if status.success() { info!("Hook executed successfully (took {}ms)", sw.ms()); success = true; } else { - warn!("Hook exited with non-zero exit code: {}", status); + warn!("Hook exited with non-zero exit code: {}", status.code().unwrap_or(0)); } } Ok(None) => { - let _ = process::kill_process(cmd.handle()); + let _ = cmd.kill(); error!("Process timed out after {}s", timeout_secs); } Err(e) => { diff --git a/src/bins/tests/commands.rs b/src/bins/tests/commands.rs index 26f8c7ee..32a8bb79 100644 --- a/src/bins/tests/commands.rs +++ b/src/bins/tests/commands.rs @@ -11,7 +11,6 @@ use winsafe::{self as w, co}; use velopack::bundle::load_bundle_from_file; use velopack::locator::{auto_locate_app_manifest, LocationContext}; - #[cfg(target_os = "windows")] #[test] pub fn test_install_apply_uninstall() { @@ -41,7 +40,7 @@ pub fn test_install_apply_uninstall() { let tmp_dir = tempdir().unwrap(); let tmp_buf = tmp_dir.path().to_path_buf(); let mut tmp_zip = load_bundle_from_file(nupkg).unwrap(); - commands::install(&mut tmp_zip, (tmp_buf.clone(), false), None).unwrap(); + commands::install(&mut tmp_zip, Some(&tmp_buf), None).unwrap(); assert!(!lnk_desktop_1.exists()); // desktop is created during update assert!(lnk_start_1.exists()); @@ -89,7 +88,7 @@ pub fn test_install_preserve_symlinks() { let tmp_buf = tmp_dir.path().to_path_buf(); let mut tmp_zip = load_bundle_from_file(nupkg).unwrap(); - commands::install(&mut tmp_zip, (tmp_buf.clone(), false), None).unwrap(); + commands::install(&mut tmp_zip, Some(&tmp_buf), None).unwrap(); assert!(tmp_buf.join("current").join("actual").join("file.txt").exists()); assert!(tmp_buf.join("current").join("other").join("syml").exists()); diff --git a/src/lib-csharp/Locators/WindowsVelopackLocator.cs b/src/lib-csharp/Locators/WindowsVelopackLocator.cs index ca486e53..1e494ac4 100644 --- a/src/lib-csharp/Locators/WindowsVelopackLocator.cs +++ b/src/lib-csharp/Locators/WindowsVelopackLocator.cs @@ -1,7 +1,6 @@ -using System; -using System.Collections.Generic; +using System; +using System.Diagnostics; using System.IO; -using System.Linq; using System.Runtime.Versioning; using NuGet.Versioning; using Velopack.Logging; @@ -31,9 +30,8 @@ namespace Velopack.Locators /// public override SemanticVersion? CurrentlyInstalledVersion { get; } - private readonly Lazy _packagesDir; /// - public override string? PackagesDir => _packagesDir.Value; + public override string? PackagesDir => CreateSubDirIfDoesNotExist(RootAppDir, "packages"); /// public override IVelopackLogger Log { get; } @@ -56,8 +54,6 @@ namespace Velopack.Locators if (!VelopackRuntimeInfo.IsWindows) throw new NotSupportedException($"Cannot instantiate {nameof(WindowsVelopackLocator)} on a non-Windows system."); - _packagesDir = new(GetPackagesDir); - ProcessId = currentProcessId; var ourPath = ProcessExePath = currentProcessPath; @@ -66,7 +62,7 @@ namespace Velopack.Locators Log = combinedLog; using var initLog = new CachedVelopackLogger(combinedLog); - initLog.Info($"Initializing {nameof(WindowsVelopackLocator)}"); + initLog.Info($"Initialising {nameof(WindowsVelopackLocator)}"); // We try various approaches here. Firstly, if Update.exe is in the parent directory, // we use that. If it's not present, we search for a parent "current" or "app-{ver}" directory, @@ -124,90 +120,36 @@ namespace Velopack.Locators } } - if (Path.GetDirectoryName(UpdateExePath) is { } updateExeDirectory && - !PathUtil.IsDirectoryWritable(updateExeDirectory)) { - UpdateExePath = Path.Combine(TempAppRootDirectory, "Update.exe"); - } - - //bool fileLogCreated = false; - Exception? fileLogException = null; + bool fileLogCreated = false; if (!String.IsNullOrEmpty(AppId) && !String.IsNullOrEmpty(RootAppDir)) { try { var logFilePath = Path.Combine(RootAppDir, DefaultLoggingFileName); var fileLog = new FileVelopackLogger(logFilePath, currentProcessId); combinedLog.Add(fileLog); - //fileLogCreated = true; - } catch (Exception ex) { - fileLogException = ex; + fileLogCreated = true; + } catch (Exception ex2) { + initLog.Error("Unable to create default file logger: " + ex2); } } // if the RootAppDir was unwritable, or we don't know the app id, we could try to write to the temp folder instead. - Exception? tempFileLogException = null; - if (fileLogException is not null) { + if (!fileLogCreated) { try { var logFileName = String.IsNullOrEmpty(AppId) ? DefaultLoggingFileName : $"velopack_{AppId}.log"; var logFilePath = Path.Combine(Path.GetTempPath(), logFileName); var fileLog = new FileVelopackLogger(logFilePath, currentProcessId); combinedLog.Add(fileLog); - } catch (Exception ex) { - tempFileLogException = ex; + } catch (Exception ex2) { + initLog.Error("Unable to create temp folder file logger: " + ex2); } } - if (tempFileLogException is not null) { - //NB: fileLogException is not null here - initLog.Error("Unable to create file logger: " + new AggregateException(fileLogException!, tempFileLogException)); - } else if (fileLogException is not null) { - initLog.Info("Unable to create file logger; using temp directory for log instead"); - initLog.Trace($"File logger exception: {fileLogException}"); - } - if (AppId == null) { initLog.Warn( - $"Failed to initialize {nameof(WindowsVelopackLocator)}. This could be because the program is not installed or packaged properly."); + $"Failed to initialise {nameof(WindowsVelopackLocator)}. This could be because the program is not installed or packaged properly."); } else { - initLog.Info($"Initialized {nameof(WindowsVelopackLocator)} for {AppId} v{CurrentlyInstalledVersion}"); + initLog.Info($"Initialised {nameof(WindowsVelopackLocator)} for {AppId} v{CurrentlyInstalledVersion}"); } } - - private string? GetPackagesDir() - { - const string PackagesDirName = "packages"; - - string? writableRootDir = PossibleDirectories() - .FirstOrDefault(IsWritable); - - if (writableRootDir == null) { - Log.Warn("Unable to find a writable root directory for package."); - return null; - } - - Log.Trace("Using writable root directory: " + writableRootDir); - - return CreateSubDirIfDoesNotExist(writableRootDir, PackagesDirName); - - static bool IsWritable(string? directoryPath) - { - if (directoryPath == null) return false; - - try { - if (!Directory.Exists(directoryPath)) { - Directory.CreateDirectory(directoryPath); - } - return PathUtil.IsDirectoryWritable(directoryPath); - } catch { - return false; - } - } - - IEnumerable PossibleDirectories() - { - yield return RootAppDir; - yield return TempAppRootDirectory; - } - } - - private string TempAppRootDirectory => Path.Combine(Path.GetTempPath(), "velopack_" + AppId); } } \ No newline at end of file diff --git a/src/lib-csharp/UpdateExe.cs b/src/lib-csharp/UpdateExe.cs index 7206dc40..920c670c 100644 --- a/src/lib-csharp/UpdateExe.cs +++ b/src/lib-csharp/UpdateExe.cs @@ -91,9 +91,6 @@ namespace Velopack if (silent) args.Add("--silent"); args.Add("apply"); - args.Add("--installto"); - args.Add(locator.RootAppDir!); - var entry = toApply ?? locator.GetLatestLocalFullPackage(); if (entry != null && locator.PackagesDir != null) { var pkg = Path.Combine(locator.PackagesDir, entry.FileName); diff --git a/src/lib-rust/src/locator.rs b/src/lib-rust/src/locator.rs index e1da5f7d..58a9a8d5 100644 --- a/src/lib-rust/src/locator.rs +++ b/src/lib-rust/src/locator.rs @@ -1,9 +1,10 @@ use std::path::PathBuf; use semver::Version; use uuid::Uuid; + use crate::{ - bundle::{self, Manifest}, - util::{self}, Error, + bundle::{self, Manifest}, + util, Error, lockfile::LockFile }; @@ -131,15 +132,7 @@ impl VelopackLocator { /// Returns the path to the current app's packages directory. pub fn get_packages_dir(&self) -> PathBuf { - let path = self.paths.PackagesDir.clone(); - if self.is_local_machine_install() || (path.exists() && !util::is_directory_writable(&path)) { - let velopack_dir = std::env::temp_dir().join(format!("velopack_{}", self.manifest.id)); - if !velopack_dir.exists() { - std::fs::create_dir_all(&velopack_dir).unwrap(); - } - return velopack_dir.join("packages"); - } - path + self.paths.PackagesDir.clone() } /// Returns the path to the current app's packages directory as a string. @@ -305,15 +298,6 @@ impl VelopackLocator { Ok(lock_file) } - #[cfg(any(target_os = "windows", target_os = "macos"))] - /// Returns whether the current app is installed in one of the Program Files directory. - pub fn is_local_machine_install(&self) -> bool { - #[cfg(target_os = "windows")] - return self.paths.RootAppDir.starts_with("C:\\Program Files"); - #[cfg(target_os = "macos")] - return self.paths.RootAppDir.starts_with("/Applications"); - } - fn path_as_string(path: &PathBuf) -> String { path.to_string_lossy().to_string() }