mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Temporarily reverse Rust code
This commit is contained in:
@@ -57,4 +57,4 @@ dotnet publish -c Release --self-contained -r "$RID" -o "$PUBLISH_DIR" -p:UseLoc
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "Building Velopack Release v$BUILD_VERSION"
|
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"
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
dialogs,
|
dialogs,
|
||||||
shared::{self},
|
shared::{self},
|
||||||
windows::{self, splash},
|
// windows::locksmith,
|
||||||
|
windows::splash,
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use std::sync::mpsc;
|
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> {
|
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 mut bundle = load_bundle_from_file(package)?;
|
||||||
let new_app_manifest = bundle.read_manifest()?;
|
let new_app_manifest = bundle.read_manifest()?;
|
||||||
let new_locator = old_locator.clone_self_with_new_manifest(&new_app_manifest);
|
let new_locator = old_locator.clone_self_with_new_manifest(&new_app_manifest);
|
||||||
|
|
||||||
if !windows::is_directory_writable(&root_path) {
|
let root_path = old_locator.get_root_dir();
|
||||||
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 old_version = old_locator.get_manifest_version();
|
let old_version = old_locator.get_manifest_version();
|
||||||
let new_version = new_locator.get_manifest_version();
|
let new_version = new_locator.get_manifest_version();
|
||||||
|
|
||||||
|
|||||||
@@ -4,23 +4,24 @@ use crate::{
|
|||||||
windows,
|
windows,
|
||||||
};
|
};
|
||||||
use velopack::bundle::BundleZip;
|
use velopack::bundle::BundleZip;
|
||||||
use velopack::constants;
|
|
||||||
use velopack::locator::*;
|
use velopack::locator::*;
|
||||||
|
use velopack::constants;
|
||||||
|
|
||||||
use ::windows::core::PCWSTR;
|
|
||||||
use ::windows::Win32::Storage::FileSystem::GetDiskFreeSpaceExW;
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use pretty_bytes_rust::pretty_bytes;
|
use pretty_bytes_rust::pretty_bytes;
|
||||||
use std::{
|
use std::{
|
||||||
fs::{self},
|
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
|
// find and parse nuspec
|
||||||
info!("Reading package manifest...");
|
info!("Reading package manifest...");
|
||||||
let app = pkg.read_manifest()?;
|
let app = pkg.read_manifest()?;
|
||||||
|
|
||||||
|
|
||||||
info!("Package manifest loaded successfully.");
|
info!("Package manifest loaded successfully.");
|
||||||
info!(" Package ID: {}", &app.id);
|
info!(" Package ID: {}", &app.id);
|
||||||
info!(" Package Version: {}", &app.version);
|
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 Machine Architecture: {}", &app.machine_architecture);
|
||||||
info!(" Package Runtime Dependencies: {}", &app.runtime_dependencies);
|
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)? {
|
if !windows::prerequisite::prompt_and_install_all_missing(&app, None)? {
|
||||||
info!("Cancelling setup. Pre-requisites not installed.");
|
info!("Cancelling setup. Pre-requisites not installed.");
|
||||||
return Ok(());
|
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)
|
// path needs to exist for future operations (disk space etc)
|
||||||
if !root_path.exists() {
|
if !root_path.exists() {
|
||||||
shared::retry_io(|| fs::create_dir_all(&root_path))?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_impl(
|
fn install_impl(pkg: &mut BundleZip, locator: &VelopackLocator, tx: &std::sync::mpsc::Sender<i16>, start_args: Option<Vec<&str>>) -> Result<()> {
|
||||||
pkg: &mut BundleZip,
|
|
||||||
locator: &VelopackLocator,
|
|
||||||
tx: &std::sync::mpsc::Sender<i16>,
|
|
||||||
start_args: Option<Vec<&str>>,
|
|
||||||
) -> Result<()> {
|
|
||||||
info!("Starting installation!");
|
info!("Starting installation!");
|
||||||
|
|
||||||
// all application paths
|
// all application paths
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::shared::{self};
|
|||||||
use velopack::{constants, locator::VelopackLocator};
|
use velopack::{constants, locator::VelopackLocator};
|
||||||
|
|
||||||
use crate::windows;
|
use crate::windows;
|
||||||
use anyhow::{bail, Result};
|
use anyhow::Result;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
||||||
pub fn uninstall(locator: &VelopackLocator, delete_self: bool) -> Result<()> {
|
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();
|
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 {
|
fn _uninstall_impl(locator: &VelopackLocator) -> bool {
|
||||||
let root_path = locator.get_root_dir();
|
let root_path = locator.get_root_dir();
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ use anyhow::{bail, Result};
|
|||||||
use clap::{arg, value_parser, Command};
|
use clap::{arg, value_parser, Command};
|
||||||
use memmap2::Mmap;
|
use memmap2::Mmap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::path::Path;
|
|
||||||
use std::{env, path::PathBuf};
|
use std::{env, path::PathBuf};
|
||||||
use velopack::bundle::BundleZip;
|
|
||||||
use velopack_bins::*;
|
use velopack_bins::*;
|
||||||
|
|
||||||
#[used]
|
#[used]
|
||||||
@@ -67,7 +65,6 @@ fn main_inner() -> Result<()> {
|
|||||||
.arg(arg!(-v --verbose "Print debug messages to console"))
|
.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!(-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!(-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..));
|
.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) {
|
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)));
|
.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 matches = arg_config.try_get_matches()?;
|
||||||
|
|
||||||
let silent = matches.get_flag("silent");
|
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.");
|
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
|
// in debug mode only, allow a nupkg to be passed in as the first argument
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
if let Some(pkg) = debug {
|
if let Some(pkg) = debug {
|
||||||
info!("Loading bundle from DEBUG nupkg file {:?}...", pkg);
|
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
|
// try to load the bundle from embedded zip
|
||||||
if offset > 0 && length > 0 {
|
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)? };
|
||||||
if bundle.is_none() {
|
let zip_range: &[u8] = &mmap[offset as usize..(offset + length) as usize];
|
||||||
bail!("Could not find embedded zip file. Please contact the application author.");
|
let mut bundle = velopack::bundle::load_bundle_from_memory(&zip_range)?;
|
||||||
}
|
commands::install(&mut bundle, install_to, exe_args)?;
|
||||||
|
|
||||||
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(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
commands::install(&mut bundle, (root_path, root_is_default), exe_args)?;
|
bail!("Could not find embedded zip file. Please contact the application author.");
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ extern crate log;
|
|||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use clap::{arg, value_parser, ArgMatches, Command};
|
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::locator::{auto_locate_app_manifest, LocationContext};
|
||||||
use velopack::{constants, logging::*};
|
use velopack::logging::*;
|
||||||
use velopack_bins::{shared::OperationWait, *};
|
use velopack_bins::{shared::OperationWait, *};
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[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!(-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!(--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!(-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..))
|
.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")
|
.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())
|
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 restart = !get_flag_or_false(&matches, "norestart");
|
||||||
let package = matches.get_one::<PathBuf>("package");
|
let package = matches.get_one::<PathBuf>("package");
|
||||||
let install_to = matches.get_one::<PathBuf>("installto");
|
|
||||||
let exe_args = get_exe_args(matches);
|
let exe_args = get_exe_args(matches);
|
||||||
let wait = get_op_wait(&matches);
|
let wait = get_op_wait(&matches);
|
||||||
(wait, restart, package, install_to, exe_args)
|
(wait, restart, package, exe_args)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply(matches: &ArgMatches) -> Result<()> {
|
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!("Command: Apply");
|
||||||
info!(" Restart: {:?}", restart);
|
info!(" Restart: {:?}", restart);
|
||||||
info!(" Wait: {:?}", wait);
|
info!(" Wait: {:?}", wait);
|
||||||
info!(" Package: {:?}", package);
|
info!(" Package: {:?}", package);
|
||||||
info!(" App Location: {:?}", install_to);
|
|
||||||
info!(" Exe Args: {:?}", exe_args);
|
info!(" Exe Args: {:?}", exe_args);
|
||||||
|
|
||||||
let locator = match install_to {
|
let locator = auto_locate_app_manifest(LocationContext::IAmUpdateExe)?;
|
||||||
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 _mutex = locator.try_get_exclusive_lock()?;
|
let _mutex = locator.try_get_exclusive_lock()?;
|
||||||
let _ = commands::apply(&locator, restart, wait, package, exe_args, true)?;
|
let _ = commands::apply(&locator, restart, wait, package, exe_args, true)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -297,7 +248,7 @@ fn uninstall(_matches: &ArgMatches) -> Result<()> {
|
|||||||
fn test_cli_parse_handles_equals_spaces() {
|
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 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 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!(wait, OperationWait::NoWait);
|
||||||
assert_eq!(restart, true);
|
assert_eq!(restart, true);
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ pub mod splash;
|
|||||||
pub mod known_path;
|
pub mod known_path;
|
||||||
pub mod strings;
|
pub mod strings;
|
||||||
pub mod registry;
|
pub mod registry;
|
||||||
pub mod process;
|
|
||||||
pub mod webview2;
|
pub mod webview2;
|
||||||
|
|
||||||
mod self_delete;
|
mod self_delete;
|
||||||
|
|||||||
@@ -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(())
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
os::windows::process::CommandExt,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
process::Command as Process,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -7,52 +9,48 @@ use velopack::locator::VelopackLocator;
|
|||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use normpath::PathExt;
|
use normpath::PathExt;
|
||||||
|
use wait_timeout::ChildExt;
|
||||||
use windows::core::PCWSTR;
|
use windows::core::PCWSTR;
|
||||||
use windows::Win32::Storage::FileSystem::GetLongPathNameW;
|
use windows::Win32::Storage::FileSystem::GetLongPathNameW;
|
||||||
use windows::Win32::System::SystemInformation::{VerSetConditionMask, VerifyVersionInfoW, OSVERSIONINFOEXW, VER_FLAGS};
|
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::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 {
|
pub fn run_hook(locator: &VelopackLocator, hook_name: &str, timeout_secs: u64) -> bool {
|
||||||
let sw = simple_stopwatch::Stopwatch::start_new();
|
let sw = simple_stopwatch::Stopwatch::start_new();
|
||||||
let root_dir = locator.get_root_dir();
|
let root_dir = locator.get_root_dir();
|
||||||
let current_path = locator.get_current_bin_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 ver_string = locator.get_manifest_version_full_string();
|
||||||
let args = vec![hook_name, &ver_string];
|
let args = vec![hook_name, &ver_string];
|
||||||
let mut success = false;
|
let mut success = false;
|
||||||
|
|
||||||
info!("Running {} hook...", hook_name);
|
info!("Running {} hook...", hook_name);
|
||||||
let cmd = process::run_process(
|
const CREATE_NO_WINDOW: u32 = 0x08000000;
|
||||||
main_exe_path,
|
let cmd = Process::new(&main_exe_path).args(args).current_dir(¤t_path).creation_flags(CREATE_NO_WINDOW).spawn();
|
||||||
args.iter().map(|f| f.to_string()).collect(),
|
|
||||||
Some(current_path.to_string_lossy().to_string()),
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Err(e) = cmd {
|
if let Err(e) = cmd {
|
||||||
warn!("Failed to start hook {}: {}", hook_name, e);
|
warn!("Failed to start hook {}: {}", hook_name, e);
|
||||||
return false;
|
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)) => {
|
Ok(Some(status)) => {
|
||||||
if status == 0 {
|
if status.success() {
|
||||||
info!("Hook executed successfully (took {}ms)", sw.ms());
|
info!("Hook executed successfully (took {}ms)", sw.ms());
|
||||||
success = true;
|
success = true;
|
||||||
} else {
|
} 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) => {
|
Ok(None) => {
|
||||||
let _ = process::kill_process(cmd.handle());
|
let _ = cmd.kill();
|
||||||
error!("Process timed out after {}s", timeout_secs);
|
error!("Process timed out after {}s", timeout_secs);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ use winsafe::{self as w, co};
|
|||||||
use velopack::bundle::load_bundle_from_file;
|
use velopack::bundle::load_bundle_from_file;
|
||||||
use velopack::locator::{auto_locate_app_manifest, LocationContext};
|
use velopack::locator::{auto_locate_app_manifest, LocationContext};
|
||||||
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_install_apply_uninstall() {
|
pub fn test_install_apply_uninstall() {
|
||||||
@@ -41,7 +40,7 @@ pub fn test_install_apply_uninstall() {
|
|||||||
let tmp_dir = tempdir().unwrap();
|
let tmp_dir = tempdir().unwrap();
|
||||||
let tmp_buf = tmp_dir.path().to_path_buf();
|
let tmp_buf = tmp_dir.path().to_path_buf();
|
||||||
let mut tmp_zip = load_bundle_from_file(nupkg).unwrap();
|
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_desktop_1.exists()); // desktop is created during update
|
||||||
assert!(lnk_start_1.exists());
|
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 tmp_buf = tmp_dir.path().to_path_buf();
|
||||||
let mut tmp_zip = load_bundle_from_file(nupkg).unwrap();
|
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("actual").join("file.txt").exists());
|
||||||
assert!(tmp_buf.join("current").join("other").join("syml").exists());
|
assert!(tmp_buf.join("current").join("other").join("syml").exists());
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using NuGet.Versioning;
|
using NuGet.Versioning;
|
||||||
using Velopack.Logging;
|
using Velopack.Logging;
|
||||||
@@ -31,9 +30,8 @@ namespace Velopack.Locators
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override SemanticVersion? CurrentlyInstalledVersion { get; }
|
public override SemanticVersion? CurrentlyInstalledVersion { get; }
|
||||||
|
|
||||||
private readonly Lazy<string?> _packagesDir;
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string? PackagesDir => _packagesDir.Value;
|
public override string? PackagesDir => CreateSubDirIfDoesNotExist(RootAppDir, "packages");
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override IVelopackLogger Log { get; }
|
public override IVelopackLogger Log { get; }
|
||||||
@@ -56,8 +54,6 @@ namespace Velopack.Locators
|
|||||||
if (!VelopackRuntimeInfo.IsWindows)
|
if (!VelopackRuntimeInfo.IsWindows)
|
||||||
throw new NotSupportedException($"Cannot instantiate {nameof(WindowsVelopackLocator)} on a non-Windows system.");
|
throw new NotSupportedException($"Cannot instantiate {nameof(WindowsVelopackLocator)} on a non-Windows system.");
|
||||||
|
|
||||||
_packagesDir = new(GetPackagesDir);
|
|
||||||
|
|
||||||
ProcessId = currentProcessId;
|
ProcessId = currentProcessId;
|
||||||
var ourPath = ProcessExePath = currentProcessPath;
|
var ourPath = ProcessExePath = currentProcessPath;
|
||||||
|
|
||||||
@@ -66,7 +62,7 @@ namespace Velopack.Locators
|
|||||||
Log = combinedLog;
|
Log = combinedLog;
|
||||||
|
|
||||||
using var initLog = new CachedVelopackLogger(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 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,
|
// 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 &&
|
bool fileLogCreated = false;
|
||||||
!PathUtil.IsDirectoryWritable(updateExeDirectory)) {
|
|
||||||
UpdateExePath = Path.Combine(TempAppRootDirectory, "Update.exe");
|
|
||||||
}
|
|
||||||
|
|
||||||
//bool fileLogCreated = false;
|
|
||||||
Exception? fileLogException = null;
|
|
||||||
if (!String.IsNullOrEmpty(AppId) && !String.IsNullOrEmpty(RootAppDir)) {
|
if (!String.IsNullOrEmpty(AppId) && !String.IsNullOrEmpty(RootAppDir)) {
|
||||||
try {
|
try {
|
||||||
var logFilePath = Path.Combine(RootAppDir, DefaultLoggingFileName);
|
var logFilePath = Path.Combine(RootAppDir, DefaultLoggingFileName);
|
||||||
var fileLog = new FileVelopackLogger(logFilePath, currentProcessId);
|
var fileLog = new FileVelopackLogger(logFilePath, currentProcessId);
|
||||||
combinedLog.Add(fileLog);
|
combinedLog.Add(fileLog);
|
||||||
//fileLogCreated = true;
|
fileLogCreated = true;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex2) {
|
||||||
fileLogException = ex;
|
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.
|
// 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 (!fileLogCreated) {
|
||||||
if (fileLogException is not null) {
|
|
||||||
try {
|
try {
|
||||||
var logFileName = String.IsNullOrEmpty(AppId) ? DefaultLoggingFileName : $"velopack_{AppId}.log";
|
var logFileName = String.IsNullOrEmpty(AppId) ? DefaultLoggingFileName : $"velopack_{AppId}.log";
|
||||||
var logFilePath = Path.Combine(Path.GetTempPath(), logFileName);
|
var logFilePath = Path.Combine(Path.GetTempPath(), logFileName);
|
||||||
var fileLog = new FileVelopackLogger(logFilePath, currentProcessId);
|
var fileLog = new FileVelopackLogger(logFilePath, currentProcessId);
|
||||||
combinedLog.Add(fileLog);
|
combinedLog.Add(fileLog);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex2) {
|
||||||
tempFileLogException = ex;
|
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) {
|
if (AppId == null) {
|
||||||
initLog.Warn(
|
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 {
|
} 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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,9 +91,6 @@ namespace Velopack
|
|||||||
if (silent) args.Add("--silent");
|
if (silent) args.Add("--silent");
|
||||||
args.Add("apply");
|
args.Add("apply");
|
||||||
|
|
||||||
args.Add("--installto");
|
|
||||||
args.Add(locator.RootAppDir!);
|
|
||||||
|
|
||||||
var entry = toApply ?? locator.GetLatestLocalFullPackage();
|
var entry = toApply ?? locator.GetLatestLocalFullPackage();
|
||||||
if (entry != null && locator.PackagesDir != null) {
|
if (entry != null && locator.PackagesDir != null) {
|
||||||
var pkg = Path.Combine(locator.PackagesDir, entry.FileName);
|
var pkg = Path.Combine(locator.PackagesDir, entry.FileName);
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
bundle::{self, Manifest},
|
bundle::{self, Manifest},
|
||||||
util::{self}, Error,
|
util, Error,
|
||||||
lockfile::LockFile
|
lockfile::LockFile
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,15 +132,7 @@ impl VelopackLocator {
|
|||||||
|
|
||||||
/// Returns the path to the current app's packages directory.
|
/// Returns the path to the current app's packages directory.
|
||||||
pub fn get_packages_dir(&self) -> PathBuf {
|
pub fn get_packages_dir(&self) -> PathBuf {
|
||||||
let path = self.paths.PackagesDir.clone();
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the path to the current app's packages directory as a string.
|
/// Returns the path to the current app's packages directory as a string.
|
||||||
@@ -305,15 +298,6 @@ impl VelopackLocator {
|
|||||||
Ok(lock_file)
|
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 {
|
fn path_as_string(path: &PathBuf) -> String {
|
||||||
path.to_string_lossy().to_string()
|
path.to_string_lossy().to_string()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user