Relocates Update.exe to writable location

Moves the Update.exe file to the local app data directory if the application directory is not writable.
This ensures that the application can update itself even when installed in a protected location like Program Files.
Adds anyhow dependency to rust library.
This commit is contained in:
Kevin Bost
2025-06-30 00:19:43 -07:00
parent da9064fc54
commit 7c7c55753d
6 changed files with 52 additions and 16 deletions

1
Cargo.lock generated
View File

@@ -2342,6 +2342,7 @@ checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5"
name = "velopack"
version = "0.0.0-local"
dependencies = [
"anyhow",
"async-std",
"bitflags 2.9.1",
"derivative",

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
@@ -119,12 +118,11 @@ namespace Velopack.Locators
if (UpdateExePath != null
&& Path.GetDirectoryName(UpdateExePath) is { } updateExeDirectory
&& !PathUtil.IsDirectoryWritable(updateExeDirectory)) {
var tempTargetUpdateExe = Path.Combine(TempAppRootDirectory, "Update.exe");
&& !PathUtil.IsDirectoryWritable(updateExeDirectory) &&
PackagesDir is { } packagesDir) {
var tempTargetUpdateExe = Path.Combine(packagesDir, "Update.exe");
if (File.Exists(UpdateExePath) && !File.Exists(tempTargetUpdateExe)) {
initLog.Warn("Application directory is not writable. Copying Update.exe to temp location: " + tempTargetUpdateExe);
// Debugger.Launch();
Directory.CreateDirectory(TempAppRootDirectory);
File.Copy(UpdateExePath, tempTargetUpdateExe);
}
@@ -165,7 +163,7 @@ namespace Velopack.Locators
initLog.Trace($"File logger exception: {fileLogException}");
}
if (AppId == null) {
if (AppId is null) {
initLog.Warn(
$"Failed to initialize {nameof(WindowsVelopackLocator)}. This could be because the program is not installed or packaged properly.");
} else {
@@ -180,7 +178,7 @@ namespace Velopack.Locators
string? writableRootDir = PossibleDirectories()
.FirstOrDefault(IsWritable);
if (writableRootDir == null) {
if (writableRootDir is null) {
Log.Warn("Unable to find a writable root directory for package.");
return null;
}
@@ -191,7 +189,7 @@ namespace Velopack.Locators
static bool IsWritable(string? directoryPath)
{
if (directoryPath == null) return false;
if (directoryPath is null) return false;
try {
if (!Directory.Exists(directoryPath)) {
@@ -206,11 +204,11 @@ namespace Velopack.Locators
IEnumerable<string?> PossibleDirectories()
{
if (!string.IsNullOrWhiteSpace(AppId)) {
yield return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "velopack", AppId);
}
yield return RootAppDir;
yield return TempAppRootDirectory;
}
}
private string TempAppRootDirectory => Path.Combine(Path.GetTempPath(), "velopack_" + AppId);
}
}

View File

@@ -31,6 +31,7 @@ tempfile.workspace = true
[dependencies]
log.workspace = true
anyhow.workspace = true
ureq.workspace = true
url.workspace = true
semver.workspace = true

View File

@@ -0,0 +1,32 @@
use anyhow::{bail, Result};
use std::path::PathBuf;
use crate::wide_strings::wide_to_os_string;
use windows::{
core::GUID,
Win32::UI::Shell::{
FOLDERID_LocalAppData, SHGetKnownFolderPath
},
};
#[cfg(windows)]
fn get_known_folder(rfid: *const GUID) -> Result<PathBuf> {
unsafe {
let flag = windows::Win32::UI::Shell::KNOWN_FOLDER_FLAG(0);
let result = SHGetKnownFolderPath(rfid, flag, None)?;
if result.is_null() {
bail!("Failed to get known folder path (SHGetKnownFolderPath returned null)");
}
let str = wide_to_os_string(result);
let path = PathBuf::from(str);
Ok(path)
}
}
#[cfg(windows)]
pub fn get_local_app_data() -> Result<PathBuf> {
get_known_folder(&FOLDERID_LocalAppData)
}

View File

@@ -134,8 +134,7 @@ pub mod locator;
pub mod sources;
#[cfg(target_os = "windows")]
maybe_pub!(wide_strings);
maybe_pub!(known_path, wide_strings);
maybe_pub!(download, bundle, constants, lockfile, logging, misc);
maybe_pub_os!(process, "process_win.rs", "process_unix.rs");

View File

@@ -7,6 +7,11 @@ use semver::Version;
use std::path::PathBuf;
use uuid::Uuid;
#[cfg(windows)]
use crate:: {
known_path::get_local_app_data
};
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
/// ShortcutLocationFlags is a bitflags enumeration of system shortcut locations.
@@ -124,13 +129,13 @@ impl VelopackLocator {
pub fn new_with_manifest(mut paths: VelopackLocatorConfig, manifest: Manifest) -> Self {
let root = paths.RootAppDir.clone();
if root.starts_with("C:\\Program Files") || !misc::is_directory_writable(&root) {
let temp_root = std::env::temp_dir().join(format!("velopack_{}", manifest.id));
let velopack_package_root = get_local_app_data().unwrap().join("velopack").join(&manifest.id);
let orig_update_path = paths.UpdateExePath.clone();
paths.PackagesDir = temp_root.join("packages");
paths.PackagesDir = velopack_package_root.join("packages");
if !paths.PackagesDir.exists() {
std::fs::create_dir_all(&paths.PackagesDir).unwrap();
}
paths.UpdateExePath = temp_root.join("Update.exe");
paths.UpdateExePath = velopack_package_root.join("Update.exe");
if !paths.UpdateExePath.exists() && orig_update_path.exists() {
std::fs::copy(orig_update_path, &paths.UpdateExePath).unwrap();
warn!("Application directory is not writable. Copying Update.exe to temp location: {:?}", paths.UpdateExePath);