Temporarily reverse Rust code

This commit is contained in:
Caelan Sayler
2025-04-30 15:03:43 +01:00
parent 33977eb666
commit ecca557fe3
13 changed files with 72 additions and 668 deletions

View File

@@ -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
"$SCRIPT_DIR/../../../build/Debug/net8.0/vpk" pack -u VelopackCSharpAvalonia -v $BUILD_VERSION -o "$RELEASE_DIR" -p "$PUBLISH_DIR"

View File

@@ -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<VelopackLocator> {
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();

View File

@@ -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<Vec<&str>>) -> Result<()> {
pub fn install(pkg: &mut BundleZip, install_to: Option<&PathBuf>, start_args: Option<Vec<&str>>) -> 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<i16>,
start_args: Option<Vec<&str>>,
) -> Result<()> {
fn install_impl(pkg: &mut BundleZip, locator: &VelopackLocator, tx: &std::sync::mpsc::Sender<i16>, start_args: Option<Vec<&str>>) -> Result<()> {
info!("Starting installation!");
// all application paths

View File

@@ -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();

View File

@@ -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 <FILE> "Enable file logging and set location").required(false).value_parser(value_parser!(PathBuf)))
.arg(arg!(-t --installto <DIR> "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 <FILE> "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::<clap::Error>() {
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<BundleZip> = 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<String> = 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.");
}

View File

@@ -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 <PID> "Wait for the specified process to terminate before applying the update").value_parser(value_parser!(u32)))
.arg(arg!(-p --package <FILE> "Update package to apply").value_parser(value_parser!(PathBuf)))
.arg(arg!(-t --installto <DIR> "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<Vec<&str>> {
matches.get_many::<String>("EXE_ARGS").map(|v| v.map(|f| f.as_str()).collect())
}
fn get_apply_args(matches: &ArgMatches) -> (OperationWait, bool, Option<&PathBuf>, Option<&PathBuf>, Option<Vec<&str>>) {
fn get_apply_args(matches: &ArgMatches) -> (OperationWait, bool, Option<&PathBuf>, Option<Vec<&str>>) {
let restart = !get_flag_or_false(&matches, "norestart");
let package = matches.get_one::<PathBuf>("package");
let install_to = matches.get_one::<PathBuf>("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<String> = 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);

View File

@@ -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;

View File

@@ -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<T: AsRef<OsStr>>(str: T) -> Result<T> {
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<u16>, 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<Vec<u16>> {
// Encode the command and arguments in a command line string such
// that the spawned process may recover them using CommandLineToArgvW.
let mut cmd: Vec<u16> = 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<HashMap<String, String>>) -> Result<(Option<*const c_void>, Vec<u16>)> {
// 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<String>) -> Result<(PCWSTR, Vec<u16>)> {
match d {
Some(dir) => {
let dir = OsString::from(dir);
let mut dir_str: Vec<u16> = 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::<TOKEN_ELEVATION>() 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<u32> for SafeProcessHandle {
// fn into(self) -> u32 {
// self.1
// }
// }
pub fn relaunch_self_as_admin(args: Vec<String>) -> Result<SafeProcessHandle> {
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<String>, work_dir: Option<String>) -> Result<SafeProcessHandle> {
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<Arg> = 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::<SHELLEXECUTEINFOW>() 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<String>,
work_dir: Option<String>,
set_env: Option<HashMap<String, String>>,
show_window: bool,
) -> Result<SafeProcessHandle> {
let exe_path = OsString::from(exe_path);
let exe_name = PCWSTR(exe_path.encode_wide().chain(Some(0)).collect::<Vec<_>>().as_mut_ptr());
let args: Vec<Arg> = 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::<STARTUPINFOW>() 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<Option<u32>> {
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(())
}

View File

@@ -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(&current_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) => {

View File

@@ -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());

View File

@@ -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
/// <inheritdoc />
public override SemanticVersion? CurrentlyInstalledVersion { get; }
private readonly Lazy<string?> _packagesDir;
/// <inheritdoc />
public override string? PackagesDir => _packagesDir.Value;
public override string? PackagesDir => CreateSubDirIfDoesNotExist(RootAppDir, "packages");
/// <inheritdoc />
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<string?> PossibleDirectories()
{
yield return RootAppDir;
yield return TempAppRootDirectory;
}
}
private string TempAppRootDirectory => Path.Combine(Path.GetTempPath(), "velopack_" + AppId);
}
}

View File

@@ -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);

View File

@@ -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()
}