Improves Windows process handling and logging

Enhances process launching on Windows by using ShellExecuteExW to start processes as the user, ensuring environment variables are correctly inherited.

Relocates Update.exe and log files to writable user directories, addressing permission issues and improving application data isolation.

Adds logging for process ID and root directory for improved debugging.

Ensures existing environment variables are passed to child processes.

Cleans up temporary files in the local app data directory during uninstallation.
This commit is contained in:
Kevin Bost
2025-07-02 09:27:08 -07:00
parent 7c7c55753d
commit 7bf2e49339
19 changed files with 203 additions and 119 deletions

6
global.json Normal file
View File

@@ -0,0 +1,6 @@
{
"sdk": {
"version": "9.0.302",
"rollForward": "latestMinor"
}
}

View File

@@ -12,12 +12,7 @@ public class LogUpdatedEventArgs : EventArgs
public class MemoryLogger : IVelopackLogger
{
public event EventHandler<LogUpdatedEventArgs> LogUpdated;
private readonly StringBuilder _sb = new StringBuilder();
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
private readonly StringBuilder _sb = new();
public override string ToString()
{

View File

@@ -1,5 +1,4 @@
using System;
using System.Text;
using System.Text;
using Velopack.Logging;
namespace CSharpWpf;
@@ -12,12 +11,7 @@ public class LogUpdatedEventArgs : EventArgs
public class MemoryLogger : IVelopackLogger
{
public event EventHandler<LogUpdatedEventArgs> LogUpdated;
private readonly StringBuilder _sb = new StringBuilder();
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
private readonly StringBuilder _sb = new();
public override string ToString()
{

View File

@@ -57,7 +57,7 @@ pub fn apply_package_impl(old_locator: &VelopackLocator, package: &PathBuf, run_
let work_dir: Option<String> = None; // same as this process
let process_handle = process::run_process_as_admin(&exe_path, args, work_dir, false)?;
info!("Waiting (up to 10 minutes) for elevated process to exit...");
info!("Waiting (up to 10 minutes) for elevated process (pid: {}) to exit...", process_handle.pid());
let result = process::wait_for_process_to_exit(process_handle, Some(Duration::from_secs(10 * 60)))?;
match result {

View File

@@ -163,7 +163,8 @@ fn main() -> Result<()> {
info!(" CWD: {:?}", env::current_dir()?);
info!(" Verbose: {}", verbose);
info!(" Silent: {}", silent);
info!(" Log File: {:?}", log_file);
info!(" Root Directory: {:?}", root_dir);
info!(" Log File: {:?}", desired_log_file);
info!(" Context: {:?}", &location_context);
let (subcommand, subcommand_matches) =

View File

@@ -41,7 +41,8 @@ namespace Velopack.Locators
public void StartProcess(string exePath, IEnumerable<string> args, string? workDir, bool showWindow)
{
var psi = new ProcessStartInfo() {
var psi = new ProcessStartInfo()
{
CreateNoWindow = true,
FileName = exePath,
WorkingDirectory = workDir,
@@ -50,19 +51,28 @@ namespace Velopack.Locators
psi.AppendArgumentListSafe(args, out var debugArgs);
_logger.Debug($"Running: {psi.FileName} {debugArgs}");
var p = Process.Start(psi);
if (p == null) {
throw new Exception("Failed to launch process.");
}
var p = Process.Start(psi) ?? throw new Exception("Failed to launch process.");
if (VelopackRuntimeInfo.IsWindows) {
try {
_logger.Info($"Process started with ID: {p.Id}");
if (VelopackRuntimeInfo.IsWindows)
{
try
{
// this is an attempt to work around a bug where the restarted app fails to come to foreground.
if (!AllowSetForegroundWindow(p.Id))
throw new Win32Exception();
} catch (Exception ex) {
_logger.LogWarning(ex, "Failed to allow Update.exe to set foreground window.");
_logger.LogWarning("Failed to allow Update.exe to set foreground window.");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Error occurred while trying to allow Update.exe to set foreground window.");
}
}
if (p.HasExited)
{
_logger.Error($"Process {p.Id} has already exited.");
}
}
}

View File

@@ -1,14 +1,13 @@
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using System.Collections;
using System.Collections.Generic;
namespace Velopack.Locators
{
public interface IProcessImpl
{
public string GetCurrentProcessPath();
public uint GetCurrentProcessId();
public void StartProcess(string exePath, IEnumerable<string> args, string workDir, bool showWindow);
string GetCurrentProcessPath();
uint GetCurrentProcessId();
void StartProcess(string exePath, IEnumerable<string> args, string workDir, bool showWindow);
}
}

View File

@@ -17,5 +17,18 @@ namespace Velopack.Locators
return Path.Combine(locator.PackagesDir!, PathUtil.GetSafeFilename(velopackAsset.FileName));
}
public static string GetUpdateExePathForUpdate(this IVelopackLocator locator, bool checkExists = true)
{
string updateExePath = locator.UpdateExePath!;
if (Path.GetDirectoryName(updateExePath) is { } updateExeDirectory
&& !PathUtil.IsDirectoryWritable(updateExeDirectory) &&
locator.PackagesDir is { } packagesDir) {
string newPath = Path.Combine(Path.GetDirectoryName(packagesDir)!, "Update.exe");
if (!checkExists || File.Exists(newPath)) {
return newPath;
}
}
return updateExePath;
}
}
}

View File

@@ -51,7 +51,6 @@ namespace Velopack.Locators
if (!VelopackRuntimeInfo.IsWindows)
throw new NotSupportedException($"Cannot instantiate {nameof(WindowsVelopackLocator)} on a non-Windows system.");
_packagesDir = new(GetPackagesDir);
CombinedLogger = new CombinedVelopackLogger(customLog);
Process = processImpl ??= new DefaultProcessImpl(CombinedLogger);
@@ -116,24 +115,13 @@ namespace Velopack.Locators
}
}
if (UpdateExePath != null
&& Path.GetDirectoryName(UpdateExePath) is { } updateExeDirectory
&& !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);
File.Copy(UpdateExePath, tempTargetUpdateExe);
}
string? writableRootDir = GetWritableDirectory();
_packagesDir = new(() => GetPackagesDir(writableRootDir));
UpdateExePath = tempTargetUpdateExe;
}
//bool fileLogCreated = false;
Exception? fileLogException = null;
if (!String.IsNullOrEmpty(AppId) && !String.IsNullOrEmpty(RootAppDir)) {
if (!string.IsNullOrEmpty(AppId) && !string.IsNullOrEmpty(writableRootDir)) {
try {
var logFilePath = Path.Combine(RootAppDir, DefaultLoggingFileName);
var logFilePath = Path.Combine(writableRootDir, DefaultLoggingFileName);
var fileLog = new FileVelopackLogger(logFilePath, currentProcessId);
CombinedLogger.Add(fileLog);
//fileLogCreated = true;
@@ -142,11 +130,11 @@ namespace Velopack.Locators
}
}
// 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 PackagesDir 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) {
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 fileLog = new FileVelopackLogger(logFilePath, currentProcessId);
CombinedLogger.Add(fileLog);
@@ -171,21 +159,36 @@ namespace Velopack.Locators
}
}
private string? GetPackagesDir()
private string? GetPackagesDir(string? writableRootDir)
{
const string PackagesDirName = "packages";
if (writableRootDir is not null) {
// If we have a writable root directory, we can create the packages directory there.
return CreateSubDirIfDoesNotExist(writableRootDir, PackagesDirName);
}
Log.Warn("Unable to create packages directory");
return null;
}
private string? GetWritableDirectory()
{
if (string.IsNullOrWhiteSpace(AppId)) {
Log.Warn("AppId is not set, cannot determine writable directory.");
return null;
}
string? writableRootDir = PossibleDirectories()
.FirstOrDefault(IsWritable);
if (writableRootDir is null) {
Log.Warn("Unable to find a writable root directory for package.");
Log.Warn("Unable to find a writable directory for package.");
return null;
}
Log.Trace("Using writable root directory: " + writableRootDir);
Log.Trace("Using writable directory: " + writableRootDir);
return CreateSubDirIfDoesNotExist(writableRootDir, PackagesDirName);
return writableRootDir;
static bool IsWritable(string? directoryPath)
{

View File

@@ -1,8 +1,7 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using Velopack.Locators;
using Velopack.Logging;
using Velopack.Util;
namespace Velopack
{
@@ -91,7 +90,7 @@ namespace Velopack
}
}
var updatePath = locator.UpdateExePath!;
var updatePath = locator.GetUpdateExePathForUpdate();
var workingDir = Path.GetDirectoryName(updatePath)!;
locator.Process.StartProcess(updatePath, args, workingDir, false);
locator.Log.Info("Update.exe [apply] executed successfully.");

View File

@@ -4,6 +4,7 @@ using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NuGet.Versioning;
@@ -221,12 +222,12 @@ namespace Velopack
}
}
if (updates == null) {
if (updates is null) {
throw new ArgumentNullException(nameof(updates));
}
var targetRelease = updates.TargetFullRelease;
if (targetRelease == null) {
if (targetRelease is null) {
throw new ArgumentException($"Must pass a valid {nameof(UpdateInfo)} object with a non-null {nameof(UpdateInfo.TargetFullRelease)}", nameof(updates));
}
@@ -235,7 +236,7 @@ namespace Velopack
var completeFile = Locator.GetLocalPackagePath(targetRelease);
var incompleteFile = completeFile + ".partial";
// if the package already exists on disk, we can skip the download.
if (File.Exists(completeFile)) {
Log.Info($"Package already exists on disk: '{completeFile}', nothing to do.");
@@ -275,7 +276,7 @@ namespace Velopack
} finally {
if (VelopackRuntimeInfo.IsWindows && !cancelToken.IsCancellationRequested) {
try {
var updateExe = Locator.UpdateExePath!;
var updateExe = Locator.GetUpdateExePathForUpdate(checkExists: false);
Log.Info("Extracting new Update.exe to " + updateExe);
var zip = new ZipPackage(completeFile, loadUpdateExe: true);
@@ -310,7 +311,7 @@ namespace Velopack
CancellationToken cancelToken)
{
var releasesToDownload = updates.DeltasToTarget.OrderBy(d => d.Version).ToArray();
var updateExe = Locator.UpdateExePath!;
var updateExe = Locator.GetUpdateExePathForUpdate();
// downloading accounts for 0%-70% of progress
double current = 0;
@@ -348,15 +349,21 @@ namespace Velopack
"--old",
baseFile,
"--output",
targetFile,
targetFile
};
if (Locator.RootAppDir is { Length: > 0 }) {
args.Add("--root");
args.Add(Locator.RootAppDir);
}
foreach (var x in releasesToDownload) {
args.Add("--delta");
args.Add(Locator.GetLocalPackagePath(x));
}
var psi = new ProcessStartInfo(updateExe);
psi.UseShellExecute = true;
psi.AppendArgumentListSafe(args, out _);
psi.CreateNoWindow = true;
var p = psi.StartRedirectOutputToILogger(Log, VelopackLogLevel.Debug);
@@ -452,6 +459,9 @@ namespace Velopack
/// </summary>
protected virtual async Task<IDisposable> AcquireUpdateLock()
{
if (Locator.PackagesDir is null) {
throw new InvalidOperationException($"Cannot acquire update lock, {nameof(IVelopackLocator)}.{nameof(IVelopackLocator.PackagesDir)} is not set.");
}
var dir = Directory.CreateDirectory(Locator.PackagesDir!);
var lockPath = Path.Combine(dir.FullName, ".velopack_lock");
var fsLock = new LockFile(lockPath);
@@ -461,7 +471,7 @@ namespace Velopack
private static IUpdateSource CreateSimpleSource(string urlOrPath)
{
if (String.IsNullOrWhiteSpace(urlOrPath)) {
if (string.IsNullOrWhiteSpace(urlOrPath)) {
throw new ArgumentException("Must pass a valid URL or file path to UpdateManager", nameof(urlOrPath));
}

View File

@@ -12,6 +12,7 @@
<PropertyGroup>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsAotCompatible>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>

View File

@@ -196,11 +196,11 @@ namespace Velopack
new { Key = "--squirrel-obsolete", Value = defaultBlock },
new { Key = "--squirrel-uninstall", Value = defaultBlock },
}.ToDictionary(k => k.Key, v => v.Value, StringComparer.OrdinalIgnoreCase);
if (args.Length >= 2 && fastExitlookup.ContainsKey(args[0])) {
if (args.Length >= 2 && fastExitlookup.TryGetValue(args[0], out var hook)) {
try {
log.Info("Found fast exit hook: " + args[0]);
var version = SemanticVersion.Parse(args[1]);
fastExitlookup[args[0]](version);
hook(version);
log.Info("Completed hook, exiting...");
Exit(0);
return;

View File

@@ -114,10 +114,10 @@ impl VelopackLocator {
/// Creates a new VelopackLocator from the given paths, trying to auto-detect the manifest.
pub fn new(config: &VelopackLocatorConfig) -> Result<VelopackLocator, Error> {
if !config.UpdateExePath.exists() {
return Err(Error::NotInstalled("Update.exe does not exist in the expected path".to_owned()));
return Err(Error::NotInstalled(format!("Update.exe does not exist in the expected path ({})", config.UpdateExePath.display())));
}
if !config.ManifestPath.exists() {
return Err(Error::NotInstalled("Manifest file does not exist in the expected path".to_owned()));
return Err(Error::NotInstalled(format!("Manifest file does not exist in the expected path ({})", config.ManifestPath.display())));
}
let manifest = read_current_manifest(&config.ManifestPath)?;
@@ -130,16 +130,10 @@ impl VelopackLocator {
let root = paths.RootAppDir.clone();
if root.starts_with("C:\\Program Files") || !misc::is_directory_writable(&root) {
let velopack_package_root = get_local_app_data().unwrap().join("velopack").join(&manifest.id);
let orig_update_path = paths.UpdateExePath.clone();
paths.PackagesDir = velopack_package_root.join("packages");
if !paths.PackagesDir.exists() {
std::fs::create_dir_all(&paths.PackagesDir).unwrap();
}
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);
}
}
Self { paths, manifest }

View File

@@ -46,6 +46,10 @@ fn default_file_windows<L: TryInto<VelopackLocator>>(locator: L) -> PathBuf {
match locator.try_into() {
Ok(locator) => {
let mut log_dir = locator.get_root_dir();
if let Ok(appdata) = std::env::var("LOCALAPPDATA") {
let velopack_app_dir = PathBuf::from(appdata).join("Velopack").join(locator.get_manifest_id());
log_dir = velopack_app_dir;
}
log_dir.push(LOGGING_FILE_NAME);
match std::fs::OpenOptions::new().write(true).create(true).open(&log_dir) {
Ok(_) => log_dir, // the desired location is writable

View File

@@ -14,10 +14,9 @@ use windows::{
Foundation::{CloseHandle, FILETIME, HANDLE, WAIT_OBJECT_0, WAIT_TIMEOUT},
Security::{GetTokenInformation, TokenElevation, TOKEN_ELEVATION},
System::Threading::{
CreateProcessW, GetCurrentProcess, GetExitCodeProcess, GetProcessId, GetProcessTimes, OpenProcess, OpenProcessToken,
TerminateProcess, WaitForSingleObject, CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT, INFINITE, PROCESS_ACCESS_RIGHTS,
PROCESS_BASIC_INFORMATION, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_SYNCHRONIZE, PROCESS_TERMINATE, STARTUPINFOW,
STARTUPINFOW_FLAGS,
CreateProcessW, GetCurrentProcess, GetExitCodeProcess, GetProcessId, GetProcessTimes, OpenProcess, OpenProcessToken, TerminateProcess,
WaitForSingleObject, CREATE_NO_WINDOW, CREATE_UNICODE_ENVIRONMENT, INFINITE, PROCESS_ACCESS_RIGHTS, PROCESS_BASIC_INFORMATION,
PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_SYNCHRONIZE, PROCESS_TERMINATE, STARTUPINFOW, STARTUPINFOW_FLAGS,
},
UI::{
Shell::{ShellExecuteExW, SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW},
@@ -122,8 +121,10 @@ fn make_command_line(argv0: Option<&OsStr>, args: &[Arg], force_quotes: bool) ->
append_arg(&mut cmd, arg, force_quotes)?;
cmd.push(' ' as u16);
}
if !cmd.is_empty() {
cmd.pop();
}
cmd.push(0);
Ok(cmd.into())
}
@@ -131,15 +132,26 @@ fn make_envp(maybe_env: Option<HashMap<String, String>>) -> IoResult<Option<Wide
// 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();
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);
// Copy current process environment variables
for (key, value) in std::env::vars_os() {
if key.is_empty() || value.is_empty() {
continue; // Skip empty keys or values
}
let key_str = key.to_string_lossy();
if key_str.starts_with("=") {
continue;
}
blk.extend(ensure_no_nuls(key)?.encode_wide());
blk.push('=' as u16);
blk.extend(ensure_no_nuls(value)?.encode_wide());
blk.push(0);
}
if let Some(env) = maybe_env {
for (k, v) in env {
let os_key = OsString::from(k);
let os_value = OsString::from(v);
@@ -148,10 +160,13 @@ fn make_envp(maybe_env: Option<HashMap<String, String>>) -> IoResult<Option<Wide
blk.extend(ensure_no_nuls(os_value)?.encode_wide());
blk.push(0);
}
}
if blk.len() == 0 {
Ok(None)
} else {
blk.push(0);
Ok(Some(blk.into()))
} else {
Ok(None)
}
}
@@ -172,9 +187,7 @@ pub fn is_current_process_elevated() -> bool {
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()
{
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;
@@ -272,6 +285,49 @@ pub fn run_process_as_admin<P1: AsRef<Path>, P2: AsRef<Path>>(
}
}
pub fn start_process<P1: AsRef<Path>, P2: AsRef<Path>>(
exe_path: P1,
args: Vec<OsString>,
work_dir: Option<P2>,
show_window: bool,
) -> IoResult<SafeProcessHandle> {
let exe = string_to_wide(exe_path.as_ref());
let wrapped_args: Vec<Arg> = args.iter().map(|a| Arg::Regular(a.into())).collect();
let params = if args.len() > 0 {
PCWSTR(make_command_line(Some(exe.as_os_str()), &wrapped_args, false)?.as_ptr())
} else {
PCWSTR::null()
};
let work_dir = string_to_wide_opt(work_dir.map(|w| w.as_ref().to_path_buf()));
let n_show = if show_window {
windows::Win32::UI::WindowsAndMessaging::SW_NORMAL.0
} else {
windows::Win32::UI::WindowsAndMessaging::SW_HIDE.0
};
let mut exe_info: SHELLEXECUTEINFOW = SHELLEXECUTEINFOW {
cbSize: std::mem::size_of::<SHELLEXECUTEINFOW>() as u32,
fMask: SEE_MASK_NOCLOSEPROCESS,
//lpVerb: PCWSTR::null(),
lpFile: exe.as_pcwstr(),
lpParameters: params,
lpDirectory: work_dir.as_ref().map(|d| d.as_pcwstr()).unwrap_or(PCWSTR::default()),
nShow: n_show,
..Default::default()
};
unsafe {
info!("About to launch: '{:?}' in dir '{:?}' with arguments: {:?}", exe, work_dir, args);
ShellExecuteExW(&mut exe_info as *mut SHELLEXECUTEINFOW)?;
let process_id = GetProcessId(exe_info.hProcess);
if show_window {
let _ = AllowSetForegroundWindow(process_id);
}
Ok(SafeProcessHandle { handle: exe_info.hProcess, pid: process_id })
}
}
pub fn run_process<P1: AsRef<Path>, P2: AsRef<Path>>(
exe_path: P1,
args: Vec<OsString>,
@@ -283,7 +339,7 @@ pub fn run_process<P1: AsRef<Path>, P2: AsRef<Path>>(
let dirp = string_to_wide_opt(work_dir.map(|w| w.as_ref().to_path_buf()));
let envp = make_envp(set_env)?;
let wrapped_args: Vec<Arg> = args.iter().map(|a| Arg::Regular(a.into())).collect();
let mut params = make_command_line(Some(exe_path.as_os_str()), &wrapped_args, false)?;
let mut params: WideString = make_command_line(Some(exe_path.as_os_str()), &wrapped_args, false)?;
let mut pi = windows::Win32::System::Threading::PROCESS_INFORMATION::default();
@@ -314,21 +370,17 @@ pub fn run_process<P1: AsRef<Path>, P2: AsRef<Path>>(
CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT
};
// Keep environment block alive for the duration of the CreateProcessW call
let env_ptr = envp.as_ref().map(|e| e.as_cvoid());
let dir_ptr = dirp.as_ref().map(|d| d.as_pcwstr()).unwrap_or(PCWSTR::default());
unsafe {
info!("About to launch: '{:?}' in dir '{:?}' with arguments: {:?}", exe_path, dirp, args);
CreateProcessW(
exe_path.as_pcwstr(),
Some(params.as_pwstr()),
None,
None,
false,
flags,
envp.map(|e| e.as_cvoid()),
dirp.map(|d| d.as_pcwstr()).unwrap_or(PCWSTR::default()),
&si,
&mut pi,
)?;
let _ = AllowSetForegroundWindow(pi.dwProcessId);
info!("About to launch: '{:?}' in dir '{:?}' with arguments: {:?}", exe_path, dirp, params);
info!("Environment block present: {}, flags: {:?}", envp.is_some(), flags);
CreateProcessW(None, Some(params.as_pwstr()), None, None, false, flags, env_ptr, dir_ptr, &si, &mut pi)?;
if show_window {
let _ = AllowSetForegroundWindow(pi.dwProcessId);
}
let _ = CloseHandle(pi.hThread);
}
@@ -359,11 +411,7 @@ pub fn kill_process<T: AsRef<HANDLE>>(process: T) -> IoResult<()> {
Ok(())
}
pub fn open_process(
dwdesiredaccess: PROCESS_ACCESS_RIGHTS,
binherithandle: bool,
dwprocessid: u32,
) -> windows::core::Result<SafeProcessHandle> {
pub fn open_process(dwdesiredaccess: PROCESS_ACCESS_RIGHTS, binherithandle: bool, dwprocessid: u32) -> windows::core::Result<SafeProcessHandle> {
let handle = unsafe { OpenProcess(dwdesiredaccess, binherithandle, dwprocessid)? };
return Ok(SafeProcessHandle { handle, pid: dwprocessid });
}
@@ -485,10 +533,10 @@ pub fn wait_for_parent_to_exit(dur: Option<Duration>) -> IoResult<WaitResult> {
#[test]
fn test_kill_process() {
let cmd =
std::process::Command::new("cmd.exe").arg("/C").arg("ping").arg("8.8.8.8").arg("-t").spawn().expect("failed to start process");
let cmd = std::process::Command::new("cmd.exe").arg("/C").arg("ping").arg("8.8.8.8").arg("-t").spawn().expect("failed to start process");
let pid = cmd.id();
kill_pid(pid).expect("failed to kill process");
}

View File

@@ -136,7 +136,7 @@ namespace Velopack.Core
public string EntryAsString {
get {
if (StagingPercentage != null) {
return String.Format("{0} {1}{2} {3} # {4}", SHA1, BaseUrl, OriginalFilename, Filesize, stagingPercentageAsString(StagingPercentage.Value));
return String.Format("{0} {1}{2} {3} # {4}", SHA1, BaseUrl, OriginalFilename, Filesize, StagingPercentageAsString(StagingPercentage.Value));
} else {
return String.Format("{0} {1}{2} {3}", SHA1, BaseUrl, OriginalFilename, Filesize);
}
@@ -378,9 +378,8 @@ namespace Velopack.Core
// Generate release entries for all of the local packages
var entriesQueue = new ConcurrentQueue<ReleaseEntry>();
Parallel.ForEach(packagesDir.GetFiles("*.nupkg"), x => {
using (var file = x.OpenRead()) {
entriesQueue.Enqueue(GenerateFromFile(file, x.Name));
}
using var file = x.OpenRead();
entriesQueue.Enqueue(GenerateFromFile(file, x.Name));
});
// Write the new RELEASES file to a temp file then move it into
@@ -403,7 +402,7 @@ namespace Velopack.Core
return entries;
}
static string stagingPercentageAsString(float percentage)
private static string StagingPercentageAsString(float percentage)
{
return String.Format("{0:F0}%", percentage * 100.0);
}

View File

@@ -20,7 +20,7 @@ public static class MsiBuilder
{
public static (string mainTemplate, string enLocale) GenerateWixTemplate(MsiTemplateData data)
{
if (data == null)
if (data is null)
throw new ArgumentNullException(nameof(data));
var templateContent = GetResourceContent("MsiTemplate.hbs");

View File

@@ -61,6 +61,13 @@ pub extern "system" fn CleanupDeferred(h_install: MSIHANDLE) -> c_uint {
}
if let Some(app_id) = app_id {
if let Ok(appdata) = std::env::var("LOCALAPPDATA") {
let velopack_app_dir = PathBuf::from(appdata).join("Velopack").join(app_id);
if let Err(e) = remove_dir_all::remove_dir_all(&velopack_app_dir) {
show_debug_message("CleanupDeferred", format!("Failed to remove local app data directory: {:?} {}", velopack_app_dir, e));
}
}
if let Some(temp_dir) = temp_dir {
let temp_dir = PathBuf::from(temp_dir);
let temp_dir = temp_dir.join(format!("velopack_{}", app_id));
@@ -81,12 +88,13 @@ pub extern "system" fn LaunchApplication(h_install: MSIHANDLE) -> c_uint {
let install_dir = msi_get_property(h_install, "INSTALLFOLDER");
let stub_file = msi_get_property(h_install, "RustStubFileName");
show_debug_message("LaunchApplication", format!("INSTALLFOLDER={:?}, RustStubFileName={:?}", install_dir, stub_file));
if let Some(install_dir) = install_dir {
if let Some(stub_file) = stub_file {
let stub_path = PathBuf::from(&install_dir).join(stub_file);
if let Err(e) = process::run_process(stub_path, vec![], Some(&install_dir), false, None) {
show_debug_message("LaunchApplication", format!("INSTALLFOLDER={:?}, RustStubFileName={:?}", install_dir, stub_path));
//NB: Need to start the process because the MSI starting a child process won't have any environment variables set.
if let Err(e) = process::start_process(stub_path, vec![], Some(&install_dir), false) {
show_debug_message("LaunchApplication", format!("Failed to launch application: {}", e));
}
}