mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
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:
6
global.json
Normal file
6
global.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "9.0.302",
|
||||
"rollForward": "latestMinor"
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) =
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<PropertyGroup>
|
||||
<CheckEolTargetFramework>false</CheckEolTargetFramework>
|
||||
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsAotCompatible>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user