mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Warning hunting; refactor process invoke
This commit is contained in:
@@ -267,17 +267,18 @@ void extractSingleFile(void* zipBuf, size_t cZipBuf, wstring fileLocation, std::
|
||||
throwLastMzError(&zip_archive, L"Unable to extract selected file from archive (DEFLATE).");
|
||||
}
|
||||
else if (file_stat.m_method == MZ_LZMA) {
|
||||
|
||||
// miniz does not support LZMA, so we will extract as compressed data
|
||||
// using MZ_ZIP_FLAG_COMPRESSED_DATA and then decode after using LZMA SDK
|
||||
auto dataCompr = std::vector<Byte>(file_stat.m_comp_size);
|
||||
if (!mz_zip_reader_extract_to_mem(&zip_archive, file_stat.m_file_index, &dataCompr[0], file_stat.m_comp_size, MZ_ZIP_FLAG_COMPRESSED_DATA))
|
||||
auto dataCompr = std::vector<Byte>((size_t)file_stat.m_comp_size);
|
||||
if (!mz_zip_reader_extract_to_mem(&zip_archive, file_stat.m_file_index, &dataCompr[0], (size_t)file_stat.m_comp_size, MZ_ZIP_FLAG_COMPRESSED_DATA))
|
||||
throwLastMzError(&zip_archive, L"Unable to extract selected file from archive (LZMA).");
|
||||
|
||||
// LZMA stream in zip container starts with 4 bytes: 0x09, 0x14, 0x05, 0x00
|
||||
// after that, there are 5 bytes that make up the LZMA decode properties
|
||||
size_t szCompr = file_stat.m_comp_size - LZMA_PROPS_SIZE - 4;
|
||||
size_t szDecompr = file_stat.m_uncomp_size;
|
||||
auto dataDecompr = std::vector<Byte>(file_stat.m_uncomp_size);
|
||||
size_t szCompr = (size_t)file_stat.m_comp_size - LZMA_PROPS_SIZE - 4;
|
||||
size_t szDecompr = (size_t)file_stat.m_uncomp_size;
|
||||
auto dataDecompr = std::vector<Byte>((size_t)file_stat.m_uncomp_size);
|
||||
|
||||
ELzmaStatus status;
|
||||
SRes lzr = LzmaDecode(&dataDecompr[0], &szDecompr, &dataCompr[LZMA_PROPS_SIZE + 4],
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<PathMap>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)'))=./</PathMap>
|
||||
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
|
||||
<SuppressTrimAnalysisWarnings>true</SuppressTrimAnalysisWarnings>
|
||||
<NoWarn>CA2007</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Squirrel.CommandLine
|
||||
return HostWriter.IsBundle(peFile, out var offset) && offset > 0;
|
||||
}
|
||||
|
||||
public static async Task UpdateSingleFileBundleIcon(string rootTempDir, string sourceFile, string destinationFile, string iconPath)
|
||||
public static void UpdateSingleFileBundleIcon(string rootTempDir, string sourceFile, string destinationFile, string iconPath)
|
||||
{
|
||||
using var _ = Utility.GetTempDir(rootTempDir, out var tmpdir);
|
||||
var sourceName = Path.GetFileNameWithoutExtension(sourceFile);
|
||||
@@ -136,7 +136,7 @@ namespace Squirrel.CommandLine
|
||||
|
||||
// set new icon
|
||||
Log.Info("Patching Update.exe icon");
|
||||
await HelperExe.SetExeIcon(newAppHost, iconPath);
|
||||
HelperExe.SetExeIcon(newAppHost, iconPath);
|
||||
|
||||
// create new bundle
|
||||
var bundlerOutput = Path.Combine(tmpdir, "output");
|
||||
@@ -153,7 +153,7 @@ namespace Squirrel.CommandLine
|
||||
);
|
||||
|
||||
Log.Info("Re-packing Update.exe bundle");
|
||||
var singleFile = DotnetUtil.GenerateBundle(bundler, tmpdir, bundlerOutput);
|
||||
var singleFile = GenerateBundle(bundler, tmpdir, bundlerOutput);
|
||||
|
||||
// copy to requested location
|
||||
File.Copy(singleFile, destinationFile);
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Squirrel.Lib;
|
||||
|
||||
namespace Squirrel.CommandLine
|
||||
{
|
||||
@@ -11,19 +15,81 @@ namespace Squirrel.CommandLine
|
||||
internal class HelperExe : HelperFile
|
||||
{
|
||||
public static string SetupPath => FindHelperFile("Setup.exe");
|
||||
public static string UpdatePath
|
||||
public static string UpdatePath
|
||||
=> FindHelperFile("Update.exe", p => Microsoft.NET.HostModel.AppHost.HostWriter.IsBundle(p, out var _));
|
||||
public static string StubExecutablePath => FindHelperFile("StubExecutable.exe");
|
||||
public static string SingleFileHostPath => FindHelperFile("singlefilehost.exe");
|
||||
public static string SignToolPath => FindHelperFile("signtool.exe");
|
||||
|
||||
// private so we don't expose paths to internal tools. these should be exposed as a helper function
|
||||
private static string SignToolPath => FindHelperFile("signtool.exe");
|
||||
private static string WixTemplatePath => FindHelperFile("template.wxs");
|
||||
private static string RceditPath => FindHelperFile("rcedit.exe");
|
||||
private static string WixCandlePath => FindHelperFile("candle.exe");
|
||||
private static string WixLightPath => FindHelperFile("light.exe");
|
||||
|
||||
public static async Task<string> CompileWixTemplateToMsi(Dictionary<string, string> templateData, string workingDir, string appId)
|
||||
private static bool CheckIsAlreadySigned(string filePath)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(filePath)) return true;
|
||||
|
||||
if (!File.Exists(filePath)) {
|
||||
Log.Warn($"Cannot sign '{filePath}', file does not exist.");
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
if (AuthenticodeTools.IsTrusted(filePath)) {
|
||||
Log.Debug("'{0}' is already signed, skipping...", filePath);
|
||||
return true;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.ErrorException("Failed to determine signing status for " + filePath, ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void SignPEFilesWithSignTool(string filePath, string signArguments)
|
||||
{
|
||||
if (CheckIsAlreadySigned(filePath)) return;
|
||||
|
||||
List<string> args = new List<string>();
|
||||
args.Add("sign");
|
||||
args.AddRange(NativeMethods.CommandLineToArgvW(signArguments));
|
||||
args.Add(filePath);
|
||||
|
||||
var result = ProcessUtil.InvokeProcess(SignToolPath, args, null, CancellationToken.None);
|
||||
if (result.ExitCode != 0) {
|
||||
var cmdWithPasswordHidden = new Regex(@"\/p\s+?[^\s]+").Replace(result.Command, "/p ********");
|
||||
throw new Exception(
|
||||
$"Command failed:\n{cmdWithPasswordHidden}\n\n" +
|
||||
$"Output was:\n" + result.StdOutput);
|
||||
} else {
|
||||
Log.Info("Sign successful: " + result.StdOutput);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SignPEFilesWithTemplate(string filePath, string signTemplate)
|
||||
{
|
||||
if (CheckIsAlreadySigned(filePath)) return;
|
||||
|
||||
var command = signTemplate.Replace("\"{{file}}\"", "{{file}}").Replace("{{file}}", $"\"{filePath}\"");
|
||||
var args = NativeMethods.CommandLineToArgvW(command);
|
||||
|
||||
if (args.Length < 2)
|
||||
throw new OptionValidationException("Invalid signing template");
|
||||
|
||||
var result = ProcessUtil.InvokeProcess(args[0], args.Skip(1), null, CancellationToken.None);
|
||||
if (result.ExitCode != 0) {
|
||||
var cmdWithPasswordHidden = new Regex(@"\/p\s+?[^\s]+").Replace(result.Command, "/p ********");
|
||||
throw new Exception(
|
||||
$"Command failed:\n{cmdWithPasswordHidden}\n\n" +
|
||||
$"Output was:\n" + result.StdOutput);
|
||||
} else {
|
||||
Log.Info("Sign successful: " + result.StdOutput);
|
||||
}
|
||||
}
|
||||
|
||||
public static string CompileWixTemplateToMsi(Dictionary<string, string> templateData, string workingDir, string appId)
|
||||
{
|
||||
var wxsFile = Path.Combine(workingDir, appId + ".wxs");
|
||||
var objFile = Path.Combine(workingDir, appId + ".wixobj");
|
||||
@@ -38,12 +104,12 @@ namespace Squirrel.CommandLine
|
||||
// Candle reprocesses and compiles WiX source files into object files (.wixobj).
|
||||
Log.Info("Compiling WiX Template (candle.exe)");
|
||||
var candleParams = new string[] { "-nologo", "-ext", "WixNetFxExtension", "-out", objFile, wxsFile };
|
||||
await InvokeAndThrowIfNonZero(WixCandlePath, candleParams, workingDir).ConfigureAwait(false);
|
||||
InvokeAndThrowIfNonZero(WixCandlePath, candleParams, workingDir);
|
||||
|
||||
// Light links and binds one or more .wixobj files and creates a Windows Installer database (.msi or .msm).
|
||||
Log.Info("Linking WiX Template (light.exe)");
|
||||
var lightParams = new string[] { "-ext", "WixNetFxExtension", "-spdb", "-sval", "-out", msiFile, objFile };
|
||||
await InvokeAndThrowIfNonZero(WixLightPath, lightParams, workingDir).ConfigureAwait(false);
|
||||
InvokeAndThrowIfNonZero(WixLightPath, lightParams, workingDir);
|
||||
return msiFile;
|
||||
} finally {
|
||||
Utility.DeleteFileOrDirectoryHard(wxsFile, throwOnFailure: false);
|
||||
@@ -51,14 +117,14 @@ namespace Squirrel.CommandLine
|
||||
}
|
||||
}
|
||||
|
||||
public static Task SetExeIcon(string exePath, string iconPath)
|
||||
public static void SetExeIcon(string exePath, string iconPath)
|
||||
{
|
||||
Log.Info("Updating PE icon for: " + exePath);
|
||||
var args = new[] { Path.GetFullPath(exePath), "--set-icon", iconPath };
|
||||
return InvokeAndThrowIfNonZero(RceditPath, args);
|
||||
Utility.Retry(() => InvokeAndThrowIfNonZero(RceditPath, args, null));
|
||||
}
|
||||
|
||||
public static Task SetPEVersionBlockFromPackageInfo(string exePath, NuGet.IPackage package, string iconPath = null)
|
||||
public static void SetPEVersionBlockFromPackageInfo(string exePath, NuGet.IPackage package, string iconPath = null)
|
||||
{
|
||||
Log.Info("Updating StringTable resources for: " + exePath);
|
||||
var realExePath = Path.GetFullPath(exePath);
|
||||
@@ -78,7 +144,7 @@ namespace Squirrel.CommandLine
|
||||
args.Add(Path.GetFullPath(iconPath));
|
||||
}
|
||||
|
||||
return InvokeAndThrowIfNonZero(RceditPath, args);
|
||||
Utility.Retry(() => InvokeAndThrowIfNonZero(RceditPath, args, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,17 @@ namespace Squirrel.CommandLine
|
||||
v => signTemplate = v);
|
||||
}
|
||||
|
||||
public void SignPEFile(string filePath)
|
||||
{
|
||||
if (!String.IsNullOrEmpty(signParams)) {
|
||||
HelperExe.SignPEFilesWithSignTool(filePath, signParams);
|
||||
} else if (!String.IsNullOrEmpty(signTemplate)) {
|
||||
HelperExe.SignPEFilesWithTemplate(filePath, signTemplate);
|
||||
} else {
|
||||
Log.Debug($"No signing paramaters, file will not be signed: '{filePath}'.");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Validate()
|
||||
{
|
||||
if (!String.IsNullOrEmpty(signParams) && !String.IsNullOrEmpty(signTemplate)) {
|
||||
@@ -29,44 +40,6 @@ namespace Squirrel.CommandLine
|
||||
throw new OptionValidationException($"Argument 'signTemplate': Must contain '{{{{file}}}}' in template string (replaced with the file to sign). Current value is '{signTemplate}'");
|
||||
}
|
||||
}
|
||||
|
||||
public void SignPEFile(string filePath)
|
||||
{
|
||||
try {
|
||||
if (AuthenticodeTools.IsTrusted(filePath)) {
|
||||
Log.Debug("'{0}' is already signed, skipping...", filePath);
|
||||
return;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.ErrorException("Failed to determine signing status for " + filePath, ex);
|
||||
}
|
||||
|
||||
string cmd;
|
||||
ProcessStartInfo psi;
|
||||
if (!String.IsNullOrEmpty(signParams)) {
|
||||
// use embedded signtool.exe with provided parameters
|
||||
cmd = $"sign {signParams} \"{filePath}\"";
|
||||
psi = Utility.CreateProcessStartInfo(HelperExe.SignToolPath, cmd);
|
||||
cmd = "signtool.exe " + cmd;
|
||||
} else if (!String.IsNullOrEmpty(signTemplate)) {
|
||||
// escape custom sign command and pass it to cmd.exe
|
||||
cmd = signTemplate.Replace("\"{{file}}\"", "{{file}}").Replace("{{file}}", $"\"{filePath}\"");
|
||||
psi = Utility.CreateProcessStartInfo("cmd", $"/c {Utility.EscapeCmdExeMetachars(cmd)}");
|
||||
} else {
|
||||
Log.Debug("{0} was not signed. (skipped; no signing parameters)", filePath);
|
||||
return;
|
||||
}
|
||||
|
||||
var processResult = Utility.InvokeProcessUnsafeAsync(psi, CancellationToken.None)
|
||||
.ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
|
||||
if (processResult.ExitCode != 0) {
|
||||
var cmdWithPasswordHidden = new Regex(@"/p\s+\w+").Replace(cmd, "/p ********");
|
||||
throw new Exception("Signing command failed: \n > " + cmdWithPasswordHidden + "\n" + processResult.StdOutput);
|
||||
} else {
|
||||
Log.Info("Sign successful: " + processResult.StdOutput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class ReleasifyOptions : SigningOptions
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace Squirrel.CommandLine
|
||||
var bundledUpdatePath = HelperExe.UpdatePath;
|
||||
var updatePath = Path.Combine(tempDir, "Update.exe");
|
||||
if (setupIcon != null) {
|
||||
DotnetUtil.UpdateSingleFileBundleIcon(TempDir, bundledUpdatePath, updatePath, setupIcon).Wait();
|
||||
DotnetUtil.UpdateSingleFileBundleIcon(TempDir, bundledUpdatePath, updatePath, setupIcon);
|
||||
} else {
|
||||
File.Copy(bundledUpdatePath, updatePath, true);
|
||||
}
|
||||
@@ -262,7 +262,7 @@ namespace Squirrel.CommandLine
|
||||
var bundledzp = new ZipPackage(package);
|
||||
var targetSetupExe = Path.Combine(di.FullName, $"{bundledzp.Id}Setup.exe");
|
||||
File.Copy(options.debugSetupExe ?? HelperExe.SetupPath, targetSetupExe, true);
|
||||
Utility.Retry(() => HelperExe.SetPEVersionBlockFromPackageInfo(targetSetupExe, bundledzp, setupIcon).Wait());
|
||||
HelperExe.SetPEVersionBlockFromPackageInfo(targetSetupExe, bundledzp, setupIcon);
|
||||
|
||||
var newestFullRelease = Squirrel.EnumerableExtensions.MaxBy(releaseEntries, x => x.Version).Where(x => !x.IsDelta).First();
|
||||
var newestReleasePath = Path.Combine(di.FullName, newestFullRelease.Filename);
|
||||
@@ -282,14 +282,14 @@ namespace Squirrel.CommandLine
|
||||
|
||||
if (!String.IsNullOrEmpty(options.msi)) {
|
||||
bool x64 = options.msi.Equals("x64");
|
||||
var msiPath = createMsiPackage(targetSetupExe, bundledzp, x64).Result;
|
||||
var msiPath = createMsiPackage(targetSetupExe, bundledzp, x64);
|
||||
options.SignPEFile(msiPath);
|
||||
}
|
||||
|
||||
Log.Info("Done");
|
||||
}
|
||||
|
||||
static Task<string> createMsiPackage(string setupExe, IPackage package, bool packageAs64Bit)
|
||||
static string createMsiPackage(string setupExe, IPackage package, bool packageAs64Bit)
|
||||
{
|
||||
Log.Info($"Compiling machine-wide msi deployment tool in {(packageAs64Bit ? "64-bit" : "32-bit")} mode");
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ namespace Squirrel.CommandLine
|
||||
printProcessed(newLibFiles.Length, baseLibFiles.Count);
|
||||
|
||||
ReleasePackageBuilder.addDeltaFilesToContentTypes(tempInfo.FullName);
|
||||
HelperFile.CompressLzma7z(outputFile, tempInfo.FullName).GetAwaiterResult();
|
||||
HelperFile.CompressLzma7z(outputFile, tempInfo.FullName);
|
||||
|
||||
this.Log().Info(
|
||||
$"Successfully created delta package for {basePackage.Version} -> {newPackage.Version}" +
|
||||
|
||||
@@ -50,8 +50,7 @@ namespace Squirrel.CommandLine
|
||||
if (helper != null)
|
||||
return helper;
|
||||
|
||||
var psi = Utility.CreateProcessStartInfo(findCommand, n);
|
||||
var result = Utility.InvokeProcessUnsafeAsync(psi, CancellationToken.None).GetAwaiterResult();
|
||||
var result = ProcessUtil.InvokeProcess(findCommand, new[] { n }, null, CancellationToken.None);
|
||||
if (result.ExitCode == 0) {
|
||||
return n;
|
||||
}
|
||||
@@ -87,19 +86,19 @@ namespace Squirrel.CommandLine
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async Task CompressLzma7z(string zipFilePath, string inFolder)
|
||||
public static void CompressLzma7z(string zipFilePath, string inFolder)
|
||||
{
|
||||
Log.Info($"Compressing '{inFolder}' to '{zipFilePath}' using 7z (LZMA)...");
|
||||
var args = new string[] { "a", zipFilePath, "-tzip", "-m0=LZMA", "-aoa", "-y", "*" };
|
||||
await InvokeAndThrowIfNonZero(SevenZipPath, args, inFolder).ConfigureAwait(false);
|
||||
InvokeAndThrowIfNonZero(SevenZipPath, args, inFolder);
|
||||
}
|
||||
|
||||
protected static async Task InvokeAndThrowIfNonZero(string exePath, IEnumerable<string> args, string workingDir = null)
|
||||
protected static void InvokeAndThrowIfNonZero(string exePath, IEnumerable<string> args, string workingDir)
|
||||
{
|
||||
var result = await Utility.InvokeProcessAsync(exePath, args, CancellationToken.None, workingDir).ConfigureAwait(false);
|
||||
var result = ProcessUtil.InvokeProcess(exePath, args, workingDir, CancellationToken.None);
|
||||
if (result.ExitCode != 0) {
|
||||
throw new Exception(
|
||||
$"Command failed: \n{Path.GetFileName(exePath)} {Utility.ArgsToCommandLine(args)}\n\n" +
|
||||
$"Command failed:\n{result.Command}\n\n" +
|
||||
$"Output was:\n" + result.StdOutput);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System;
|
||||
@@ -70,7 +70,7 @@ namespace Microsoft.NET.HostModel.AppHost
|
||||
{
|
||||
if (assemblyToCopyResorcesFrom != null && appHostIsPEImage)
|
||||
{
|
||||
if (ResourceUpdater.IsSupportedOS())
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && ResourceUpdater.IsSupportedOS())
|
||||
{
|
||||
// Copy resources from managed dll to the apphost
|
||||
new ResourceUpdater(appHostDestinationFilePath)
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Squirrel.CommandLine
|
||||
|
||||
contentsPostProcessHook?.Invoke(tempPath, package);
|
||||
|
||||
HelperFile.CompressLzma7z(outputFile, tempPath).GetAwaiterResult();
|
||||
HelperFile.CompressLzma7z(outputFile, tempPath);
|
||||
|
||||
ReleasePackageFile = outputFile;
|
||||
return ReleasePackageFile;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<NoWarn>CA2007</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -144,17 +144,15 @@ namespace Squirrel
|
||||
bool IsInstalledApp { get; }
|
||||
|
||||
/// <summary>The directory the app is (or will be) installed in.</summary>
|
||||
//string AppDirectory { get; }
|
||||
string AppDirectory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently installed version of the given executable, or if
|
||||
/// not given, the currently running assembly
|
||||
/// </summary>
|
||||
/// <param name="executable">The executable to check, or null for this
|
||||
/// executable</param>
|
||||
/// <returns>The running version, or null if this is not a Squirrel
|
||||
/// installed app (i.e. you're running from VS)</returns>
|
||||
//SemanticVersion CurrentlyInstalledVersion(string executable = null);
|
||||
SemanticVersion CurrentlyInstalledVersion();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an entry in Programs and Features based on the currently
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
@@ -30,6 +31,35 @@ namespace Squirrel
|
||||
return (int) pbi.InheritedFromUniqueProcessId;
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", EntryPoint = "LocalFree", SetLastError = true)]
|
||||
private static extern IntPtr _LocalFree(IntPtr hMem);
|
||||
|
||||
[DllImport("shell32.dll", EntryPoint = "CommandLineToArgvW", CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr _CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string cmdLine, out int numArgs);
|
||||
|
||||
public static string[] CommandLineToArgvW(string cmdLine)
|
||||
{
|
||||
IntPtr argv = IntPtr.Zero;
|
||||
try {
|
||||
int numArgs = 0;
|
||||
|
||||
argv = _CommandLineToArgvW(cmdLine, out numArgs);
|
||||
if (argv == IntPtr.Zero) {
|
||||
throw new Win32Exception();
|
||||
}
|
||||
var result = new string[numArgs];
|
||||
|
||||
for (int i = 0; i < numArgs; i++) {
|
||||
IntPtr currArg = Marshal.ReadIntPtr(argv, i * Marshal.SizeOf(typeof(IntPtr)));
|
||||
result[i] = Marshal.PtrToStringUni(currArg);
|
||||
}
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
_LocalFree(argv);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("shell32.dll", SetLastError = true)]
|
||||
public static extern void SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID);
|
||||
|
||||
|
||||
197
src/Squirrel/Internal/ProcessUtil.cs
Normal file
197
src/Squirrel/Internal/ProcessUtil.cs
Normal file
@@ -0,0 +1,197 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Squirrel
|
||||
{
|
||||
internal static class ProcessUtil
|
||||
{
|
||||
/*
|
||||
* caesay — 09/12/2021 at 12:10 PM
|
||||
* yeah
|
||||
* can I steal this for squirrel?
|
||||
* Roman — 09/12/2021 at 12:10 PM
|
||||
* sure :)
|
||||
* reference CommandRunner.cs on the github url as source? :)
|
||||
* https://github.com/RT-Projects/RT.Util/blob/ef660cd693f66bc946da3aaa368893b03b74eed7/RT.Util.Core/CommandRunner.cs#L327
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Given a number of argument strings, constructs a single command line string with all the arguments escaped
|
||||
/// correctly so that a process using standard Windows API for parsing the command line will receive exactly the
|
||||
/// strings passed in here. See Remarks.</summary>
|
||||
/// <remarks>
|
||||
/// The string is only valid for passing directly to a process. If the target process is invoked by passing the
|
||||
/// process name + arguments to cmd.exe then further escaping is required, to counteract cmd.exe's interpretation
|
||||
/// of additional special characters. See <see cref="EscapeCmdExeMetachars"/>.</remarks>
|
||||
[SupportedOSPlatform("windows")]
|
||||
private static string ArgsToCommandLine(IEnumerable<string> args)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var arg in args) {
|
||||
if (arg == null)
|
||||
continue;
|
||||
if (sb.Length != 0)
|
||||
sb.Append(' ');
|
||||
// For details, see https://web.archive.org/web/20150318010344/http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
|
||||
// or https://devblogs.microsoft.com/oldnewthing/?p=12833
|
||||
if (arg.Length != 0 && arg.IndexOfAny(_cmdChars) < 0)
|
||||
sb.Append(arg);
|
||||
else {
|
||||
sb.Append('"');
|
||||
for (int c = 0; c < arg.Length; c++) {
|
||||
int backslashes = 0;
|
||||
while (c < arg.Length && arg[c] == '\\') {
|
||||
c++;
|
||||
backslashes++;
|
||||
}
|
||||
if (c == arg.Length) {
|
||||
sb.Append('\\', backslashes * 2);
|
||||
break;
|
||||
} else if (arg[c] == '"') {
|
||||
sb.Append('\\', backslashes * 2 + 1);
|
||||
sb.Append('"');
|
||||
} else {
|
||||
sb.Append('\\', backslashes);
|
||||
sb.Append(arg[c]);
|
||||
}
|
||||
}
|
||||
sb.Append('"');
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
private static readonly char[] _cmdChars = new[] { ' ', '"', '\n', '\t', '\v' };
|
||||
|
||||
/// <summary>
|
||||
/// Escapes all cmd.exe meta-characters by prefixing them with a ^. See <see cref="ArgsToCommandLine"/> for more
|
||||
/// information.</summary>
|
||||
[SupportedOSPlatform("windows")]
|
||||
private static string EscapeCmdExeMetachars(string command)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
foreach (var ch in command) {
|
||||
switch (ch) {
|
||||
case '(':
|
||||
case ')':
|
||||
case '%':
|
||||
case '!':
|
||||
case '^':
|
||||
case '"':
|
||||
case '<':
|
||||
case '>':
|
||||
case '&':
|
||||
case '|':
|
||||
result.Append('^');
|
||||
break;
|
||||
}
|
||||
result.Append(ch);
|
||||
}
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
private static string ArgsToCommandLineUnix(IEnumerable<string> args)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var arg in args) {
|
||||
if (arg == null)
|
||||
continue;
|
||||
if (sb.Length != 0)
|
||||
sb.Append(' ');
|
||||
|
||||
// there are just too many 'command chars' in unix, so we play it
|
||||
// super safe here and escape the string if there are any non-alpha-numeric
|
||||
if (System.Text.RegularExpressions.Regex.IsMatch(arg, @"$[\w]^")) {
|
||||
sb.Append(arg);
|
||||
} else {
|
||||
// https://stackoverflow.com/a/33949338/184746
|
||||
// single quotes are 'strong quotes' and can contain everything
|
||||
// except never other single quotes.
|
||||
sb.Append("'");
|
||||
sb.Append(arg.Replace("'", @"'\''"));
|
||||
sb.Append("'");
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static ProcessStartInfo CreateProcessStartInfo(string fileName, string workingDirectory)
|
||||
{
|
||||
var psi = new ProcessStartInfo(fileName);
|
||||
psi.UseShellExecute = false;
|
||||
psi.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
psi.ErrorDialog = false;
|
||||
psi.CreateNoWindow = true;
|
||||
psi.RedirectStandardOutput = true;
|
||||
psi.RedirectStandardError = true;
|
||||
psi.WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory;
|
||||
return psi;
|
||||
}
|
||||
|
||||
private static (ProcessStartInfo StartInfo, string CommandDisplayString) CreateProcessStartInfo(string fileName, IEnumerable<string> args, string workingDirectory)
|
||||
{
|
||||
var psi = CreateProcessStartInfo(fileName, workingDirectory);
|
||||
|
||||
string displayArgs;
|
||||
|
||||
#if NET5_0_OR_GREATER
|
||||
foreach (var a in args) psi.ArgumentList.Add(a);
|
||||
displayArgs = $"['{String.Join("', '", args)}']";
|
||||
#else
|
||||
psi.Arguments = displayArgs = SquirrelRuntimeInfo.IsWindows ? ArgsToCommandLine(args) : ArgsToCommandLineUnix(args);
|
||||
#endif
|
||||
|
||||
return (psi, fileName + " " + displayArgs);
|
||||
}
|
||||
|
||||
private static (int ExitCode, string StdOutput) InvokeProcess(ProcessStartInfo psi, CancellationToken ct)
|
||||
{
|
||||
var pi = Process.Start(psi);
|
||||
while (!ct.IsCancellationRequested) {
|
||||
if (pi.WaitForExit(500)) break;
|
||||
}
|
||||
|
||||
if (ct.IsCancellationRequested && !pi.HasExited) {
|
||||
pi.Kill();
|
||||
ct.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
string output = pi.StandardOutput.ReadToEnd();
|
||||
string error = pi.StandardError.ReadToEnd();
|
||||
var all = (output ?? "") + Environment.NewLine + (error ?? "");
|
||||
|
||||
return (pi.ExitCode, all.Trim());
|
||||
}
|
||||
|
||||
public static Process StartNonBlocking(string fileName, IEnumerable<string> args, string workingDirectory)
|
||||
{
|
||||
var (psi, cmd) = CreateProcessStartInfo(fileName, args, workingDirectory);
|
||||
return Process.Start(psi);
|
||||
}
|
||||
|
||||
public static (int ExitCode, string StdOutput, string Command) InvokeProcess(string fileName, IEnumerable<string> args, string workingDirectory, CancellationToken ct)
|
||||
{
|
||||
var (psi, cmd) = CreateProcessStartInfo(fileName, args, workingDirectory);
|
||||
var p = InvokeProcess(psi, ct);
|
||||
return (p.ExitCode, p.StdOutput, cmd);
|
||||
}
|
||||
|
||||
//public static (int ExitCode, string StdOutput, string Command) InvokeProcess(string fileName, string args, string workingDirectory, CancellationToken ct)
|
||||
//{
|
||||
// var psi = CreateProcessStartInfo(fileName, workingDirectory);
|
||||
// psi.Arguments = args;
|
||||
// var p = InvokeProcess(psi, ct);
|
||||
// return (p.ExitCode, p.StdOutput, fileName + " " + args);
|
||||
//}
|
||||
|
||||
public static Task<(int ExitCode, string StdOutput, string Command)> InvokeProcessAsync(string fileName, IEnumerable<string> args, string workingDirectory, CancellationToken ct)
|
||||
{
|
||||
return Task.Run(() => InvokeProcess(fileName, args, workingDirectory, ct));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,103 +226,7 @@ namespace Squirrel
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* caesay — 09/12/2021 at 12:10 PM
|
||||
* yeah
|
||||
* can I steal this for squirrel?
|
||||
* Roman — 09/12/2021 at 12:10 PM
|
||||
* sure :)
|
||||
* reference CommandRunner.cs on the github url as source? :)
|
||||
* https://github.com/RT-Projects/RT.Util/blob/ef660cd693f66bc946da3aaa368893b03b74eed7/RT.Util.Core/CommandRunner.cs#L327
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Given a number of argument strings, constructs a single command line string with all the arguments escaped
|
||||
/// correctly so that a process using standard Windows API for parsing the command line will receive exactly the
|
||||
/// strings passed in here. See Remarks.</summary>
|
||||
/// <remarks>
|
||||
/// The string is only valid for passing directly to a process. If the target process is invoked by passing the
|
||||
/// process name + arguments to cmd.exe then further escaping is required, to counteract cmd.exe's interpretation
|
||||
/// of additional special characters. See <see cref="EscapeCmdExeMetachars"/>.</remarks>
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static string ArgsToCommandLine(IEnumerable<string> args)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var arg in args) {
|
||||
if (arg == null)
|
||||
continue;
|
||||
if (sb.Length != 0)
|
||||
sb.Append(' ');
|
||||
// For details, see https://web.archive.org/web/20150318010344/http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
|
||||
// or https://devblogs.microsoft.com/oldnewthing/?p=12833
|
||||
if (arg.Length != 0 && arg.IndexOfAny(_cmdChars) < 0)
|
||||
sb.Append(arg);
|
||||
else {
|
||||
sb.Append('"');
|
||||
for (int c = 0; c < arg.Length; c++) {
|
||||
int backslashes = 0;
|
||||
while (c < arg.Length && arg[c] == '\\') {
|
||||
c++;
|
||||
backslashes++;
|
||||
}
|
||||
if (c == arg.Length) {
|
||||
sb.Append('\\', backslashes * 2);
|
||||
break;
|
||||
} else if (arg[c] == '"') {
|
||||
sb.Append('\\', backslashes * 2 + 1);
|
||||
sb.Append('"');
|
||||
} else {
|
||||
sb.Append('\\', backslashes);
|
||||
sb.Append(arg[c]);
|
||||
}
|
||||
}
|
||||
sb.Append('"');
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
private static readonly char[] _cmdChars = new[] { ' ', '"', '\n', '\t', '\v' };
|
||||
|
||||
/// <summary>
|
||||
/// Escapes all cmd.exe meta-characters by prefixing them with a ^. See <see cref="ArgsToCommandLine"/> for more
|
||||
/// information.</summary>
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static string EscapeCmdExeMetachars(string command)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
foreach (var ch in command) {
|
||||
switch (ch) {
|
||||
case '(':
|
||||
case ')':
|
||||
case '%':
|
||||
case '!':
|
||||
case '^':
|
||||
case '"':
|
||||
case '<':
|
||||
case '>':
|
||||
case '&':
|
||||
case '|':
|
||||
result.Append('^');
|
||||
break;
|
||||
}
|
||||
result.Append(ch);
|
||||
}
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This function will escape command line arguments such that CommandLineToArgvW is guarenteed to produce the same output as the 'args' parameter.
|
||||
/// It also will automatically execute wine if trying to run an exe while not on windows.
|
||||
/// </summary>
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static Task<(int ExitCode, string StdOutput)> InvokeProcessAsync(string fileName, IEnumerable<string> args, CancellationToken ct, string workingDirectory = "")
|
||||
{
|
||||
if (Environment.OSVersion.Platform != PlatformID.Win32NT && fileName.EndsWith(".exe", StringComparison.OrdinalIgnoreCase)) {
|
||||
return InvokeProcessUnsafeAsync(CreateProcessStartInfo("wine", ArgsToCommandLine(new string[] { fileName }.Concat(args)), workingDirectory), ct);
|
||||
} else {
|
||||
return InvokeProcessUnsafeAsync(CreateProcessStartInfo(fileName, ArgsToCommandLine(args), workingDirectory), ct);
|
||||
}
|
||||
}
|
||||
|
||||
public static T GetAwaiterResult<T>(this Task<T> task)
|
||||
{
|
||||
@@ -334,45 +238,6 @@ namespace Squirrel
|
||||
task.ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public static ProcessStartInfo CreateProcessStartInfo(string fileName, string arguments, string workingDirectory = "")
|
||||
{
|
||||
var psi = new ProcessStartInfo(fileName, arguments);
|
||||
psi.UseShellExecute = false;
|
||||
psi.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
psi.ErrorDialog = false;
|
||||
psi.CreateNoWindow = true;
|
||||
psi.RedirectStandardOutput = true;
|
||||
psi.RedirectStandardError = true;
|
||||
psi.WorkingDirectory = workingDirectory;
|
||||
return psi;
|
||||
}
|
||||
|
||||
public static async Task<(int ExitCode, string StdOutput)> InvokeProcessUnsafeAsync(ProcessStartInfo psi, CancellationToken ct)
|
||||
{
|
||||
var pi = Process.Start(psi);
|
||||
await Task.Run(() => {
|
||||
while (!ct.IsCancellationRequested) {
|
||||
if (pi.WaitForExit(2000)) return;
|
||||
}
|
||||
|
||||
if (ct.IsCancellationRequested) {
|
||||
pi.Kill();
|
||||
ct.ThrowIfCancellationRequested();
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
string textResult = await pi.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
|
||||
if (String.IsNullOrWhiteSpace(textResult) || pi.ExitCode != 0) {
|
||||
textResult = (textResult ?? "") + "\n" + await pi.StandardError.ReadToEndAsync().ConfigureAwait(false);
|
||||
|
||||
if (String.IsNullOrWhiteSpace(textResult)) {
|
||||
textResult = String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
return (pi.ExitCode, textResult.Trim());
|
||||
}
|
||||
|
||||
public static Task ForEachAsync<T>(this IEnumerable<T> source, Action<T> body, int degreeOfParallelism = 4)
|
||||
{
|
||||
return ForEachAsync(source, x => Task.Run(() => body(x)), degreeOfParallelism);
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
// File generated by dotnet-combine at 2022-05-07__11_02_05
|
||||
#pragma warning disable CS1574 // XML comment has cref attribute that could not be resolved
|
||||
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
|
||||
#pragma warning disable CS0162 // Unreachable code detected
|
||||
|
||||
// File generated by dotnet-combine at 2022-05-07__11_02_05
|
||||
|
||||
// plist-cil - An open source library to parse and generate property lists for .NET
|
||||
// Copyright (C) 2015 Natalia Portillo
|
||||
@@ -938,8 +942,8 @@ namespace Squirrel.PropertyList
|
||||
|
||||
/// <summary>Parses a binary property list from a byte array.</summary>
|
||||
/// <param name="data">The binary property list's data.</param>
|
||||
/// <param name="offset">The length of the property list.</param>
|
||||
/// <param name="count">The offset at which to start reading the property list.</param>
|
||||
/// <param name="offset">The offset at which to start reading the property list.</param>
|
||||
/// <param name="length">The length of the property list.</param>
|
||||
/// <returns>The root object of the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
|
||||
/// <exception cref="PropertyListFormatException">When the property list's format could not be parsed.</exception>
|
||||
public static NSObject Parse(byte[] data, int offset, int length) => Parse(data.AsSpan(offset, length));
|
||||
@@ -1467,6 +1471,7 @@ namespace Squirrel.PropertyList
|
||||
{
|
||||
internal partial class BinaryPropertyListWriter
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The equality comparer which is used when adding an object to the <see cref="BinaryPropertyListWriter.idMap" />
|
||||
/// . In most cases, objects are always added. The only exception are very specific strings, which are only added once.
|
||||
@@ -4943,8 +4948,8 @@ namespace Squirrel.PropertyList
|
||||
|
||||
/// <summary>Parses a property list from a byte array.</summary>
|
||||
/// <param name="bytes">The property list data as a byte array.</param>
|
||||
/// <param name="offset">The length of the property list.</param>
|
||||
/// <param name="count">The offset at which to start reading the property list.</param>
|
||||
/// <param name="length">The length of the property list.</param>
|
||||
/// <param name="offset">The offset at which to start reading the property list.</param>
|
||||
/// <returns>The root object in the property list. This is usually a NSDictionary but can also be a NSArray.</returns>
|
||||
public static NSObject Parse(byte[] bytes, int offset, int length) => Parse(bytes.AsSpan(offset, length));
|
||||
|
||||
@@ -5184,7 +5189,6 @@ namespace Squirrel.PropertyList
|
||||
/// Initializes a new instance of the <see cref="Squirrel.PropertyList.UID" /> class using an unsigned 16-bit
|
||||
/// number.
|
||||
/// </summary>
|
||||
/// <param name="name">Name.</param>
|
||||
/// <param name="number">Unsigned 16-bit number.</param>
|
||||
public UID(ushort number) => value = number;
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace Squirrel
|
||||
var args = new string[] { "/passive", "/norestart", "/showrmui" };
|
||||
var quietArgs = new string[] { "/q", "/norestart" };
|
||||
Log.Info($"Running {Id} installer '{pathToInstaller} {string.Join(" ", args)}'");
|
||||
var p = await Utility.InvokeProcessAsync(pathToInstaller, isQuiet ? quietArgs : args, CancellationToken.None).ConfigureAwait(false);
|
||||
var p = await ProcessUtil.InvokeProcessAsync(pathToInstaller, isQuiet ? quietArgs : args, null, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
// https://johnkoerner.com/install/windows-installer-error-codes/
|
||||
|
||||
|
||||
@@ -14,26 +14,42 @@ namespace Squirrel
|
||||
/// <summary> The unique application Id. This is used in various app paths. </summary>
|
||||
public virtual string AppId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The root directory of the application. On Windows, this folder contains all
|
||||
/// the application files, but that may not be the case on other operating systems.
|
||||
/// </summary>
|
||||
public virtual string RootAppDir { get; }
|
||||
|
||||
/// <summary> The directory in which nupkg files are stored for this application. </summary>
|
||||
public virtual string PackagesDir { get; }
|
||||
|
||||
/// <summary> The temporary directory for this application. </summary>
|
||||
public virtual string TempDir { get; }
|
||||
|
||||
/// <summary> The directory where new versions are stored, before they are applied. </summary>
|
||||
public virtual string VersionStagingDir { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The directory where the current version of the application is stored.
|
||||
/// This directory will be swapped out for a new version in <see cref="VersionStagingDir"/>.
|
||||
/// </summary>
|
||||
public virtual string CurrentVersionDir { get; }
|
||||
|
||||
/// <summary> The path to the current Update.exe or similar on other operating systems. </summary>
|
||||
public virtual string UpdateExePath { get; }
|
||||
|
||||
/// <summary> The path to the RELEASES index detailing the local packages. </summary>
|
||||
public virtual string ReleasesFilePath => Path.Combine(PackagesDir, "RELEASES");
|
||||
|
||||
/// <summary> The path to the .betaId file which contains a unique GUID for this user. </summary>
|
||||
public virtual string BetaIdFilePath => Path.Combine(PackagesDir, ".betaId");
|
||||
|
||||
/// <summary> The currently installed version of the application. </summary>
|
||||
public virtual SemanticVersion CurrentlyInstalledVersion => GetCurrentlyInstalledVersion();
|
||||
|
||||
private static IFullLogger Log() => SquirrelLocator.Current.GetService<ILogManager>().GetLogger(typeof(UpdateConfig));
|
||||
|
||||
/// <summary> Creates a new instance of UpdateConfig. </summary>
|
||||
public UpdateConfig(string applicationIdOverride, string localAppDataDirOverride)
|
||||
{
|
||||
UpdateExePath = GetUpdateExe();
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace Squirrel
|
||||
|
||||
// Progress range: 60 -> 80
|
||||
// extracts the new package to a version dir (app-{ver}) inside VersionStagingDir
|
||||
var newVersionStagingDir = await this.ErrorIfThrows(() => installPackageToStagingDir(updateInfo, release, x => progress(CalculateProgress(x, 60, 80))),
|
||||
var newVersionDir = await this.ErrorIfThrows(() => installPackageToStagingDir(updateInfo, release, x => progress(CalculateProgress(x, 60, 80))),
|
||||
"Failed to install package to app dir").ConfigureAwait(false);
|
||||
|
||||
progress(80);
|
||||
@@ -50,14 +50,16 @@ namespace Squirrel
|
||||
this.Log().Info("Updating local release file");
|
||||
var currentReleases = await Task.Run(() => ReleaseEntry.BuildReleasesFile(_config.PackagesDir)).ConfigureAwait(false);
|
||||
|
||||
progress(85);
|
||||
if (SquirrelRuntimeInfo.IsWindows) {
|
||||
progress(85);
|
||||
|
||||
this.Log().Info("Running post-install hooks");
|
||||
var currentVersionDir = await invokePostInstall(newVersionStagingDir, attemptingFullInstall, false, silentInstall).ConfigureAwait(false);
|
||||
this.Log().Info("Running post-install hooks");
|
||||
newVersionDir = await invokePostInstall(newVersionDir, attemptingFullInstall, false, silentInstall).ConfigureAwait(false);
|
||||
|
||||
progress(90);
|
||||
progress(90);
|
||||
|
||||
executeSelfUpdate(currentVersionDir);
|
||||
executeSelfUpdate(newVersionDir);
|
||||
}
|
||||
|
||||
progress(95);
|
||||
|
||||
@@ -69,7 +71,7 @@ namespace Squirrel
|
||||
|
||||
progress(100);
|
||||
|
||||
return currentVersionDir;
|
||||
return newVersionDir;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -96,7 +98,8 @@ namespace Squirrel
|
||||
using (var cts = new CancellationTokenSource()) {
|
||||
cts.CancelAfter(10 * 1000);
|
||||
try {
|
||||
await Utility.InvokeProcessAsync(exe, new string[] { "--squirrel-uninstall", currentVersion.ToString() }, cts.Token).ConfigureAwait(false);
|
||||
var args = new string[] { "--squirrel-uninstall", currentVersion.ToString() };
|
||||
await ProcessUtil.InvokeProcessAsync(exe, args, Path.GetDirectoryName(exe), cts.Token).ConfigureAwait(false);
|
||||
} catch (Exception ex) {
|
||||
this.Log().ErrorException("Failed to run cleanup hook, continuing: " + exe, ex);
|
||||
}
|
||||
@@ -208,6 +211,7 @@ namespace Squirrel
|
||||
return await createFullPackagesFromDeltas(releasesToApply.Skip(1), entry, progress).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
void executeSelfUpdate(string newVersionDir)
|
||||
{
|
||||
var newSquirrel = Path.Combine(newVersionDir, "Squirrel.exe");
|
||||
@@ -228,6 +232,7 @@ namespace Squirrel
|
||||
Utility.Retry(() => File.Copy(newSquirrel, _config.UpdateExePath, true));
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
async Task<string> invokePostInstall(string targetDir, bool isInitialInstall, bool firstRunOnly, bool silentInstall)
|
||||
{
|
||||
var versionInfo = _config.GetVersionInfoFromDirectory(targetDir);
|
||||
@@ -245,7 +250,7 @@ namespace Squirrel
|
||||
cts.CancelAfter(30 * 1000);
|
||||
|
||||
try {
|
||||
await Utility.InvokeProcessAsync(exe, args, cts.Token, Path.GetDirectoryName(exe)).ConfigureAwait(false);
|
||||
await ProcessUtil.InvokeProcessAsync(exe, args, Path.GetDirectoryName(exe), cts.Token).ConfigureAwait(false);
|
||||
} catch (Exception ex) {
|
||||
this.Log().ErrorException("Couldn't run Squirrel hook, continuing: " + exe, ex);
|
||||
}
|
||||
@@ -298,18 +303,20 @@ namespace Squirrel
|
||||
// don't run hooks if the folder is already dead.
|
||||
if (isAppFolderDead(v.DirectoryPath)) continue;
|
||||
|
||||
var squirrelApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(v.DirectoryPath);
|
||||
var args = new string[] { "--squirrel-obsolete", v.Version.ToString() };
|
||||
if (SquirrelRuntimeInfo.IsWindows) {
|
||||
var squirrelApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(v.DirectoryPath);
|
||||
var args = new string[] { "--squirrel-obsolete", v.Version.ToString() };
|
||||
|
||||
if (squirrelApps.Count > 0) {
|
||||
// For each app, run the install command in-order and wait
|
||||
foreach (var exe in squirrelApps) {
|
||||
using (var cts = new CancellationTokenSource()) {
|
||||
cts.CancelAfter(10 * 1000);
|
||||
try {
|
||||
await Utility.InvokeProcessAsync(exe, args, cts.Token).ConfigureAwait(false);
|
||||
} catch (Exception ex) {
|
||||
this.Log().ErrorException("Coudln't run Squirrel hook, continuing: " + exe, ex);
|
||||
if (squirrelApps.Count > 0) {
|
||||
// For each app, run the install command in-order and wait
|
||||
foreach (var exe in squirrelApps) {
|
||||
using (var cts = new CancellationTokenSource()) {
|
||||
cts.CancelAfter(10 * 1000);
|
||||
try {
|
||||
await ProcessUtil.InvokeProcessAsync(exe, args, Path.GetDirectoryName(exe), cts.Token).ConfigureAwait(false);
|
||||
} catch (Exception ex) {
|
||||
this.Log().ErrorException("Coudln't run Squirrel hook, continuing: " + exe, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,12 @@ namespace Squirrel
|
||||
/// <inheritdoc/>
|
||||
public bool IsInstalledApp => CurrentlyInstalledVersion() != null;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual SemanticVersion CurrentlyInstalledVersion() => _config.CurrentlyInstalledVersion;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual string AppDirectory => _config.RootAppDir;
|
||||
|
||||
/// <summary>The <see cref="UpdateConfig"/> describes the structure of the application on disk (eg. file/folder locations).</summary>
|
||||
public UpdateConfig Config => _config;
|
||||
|
||||
@@ -126,12 +132,6 @@ namespace Squirrel
|
||||
await ApplyReleases(updateInfo, silentInstall, true, progress).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SemanticVersion CurrentlyInstalledVersion()
|
||||
{
|
||||
return _config.CurrentlyInstalledVersion;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ReleaseEntry> UpdateApp(Action<int> progress = null)
|
||||
{
|
||||
@@ -173,9 +173,10 @@ namespace Squirrel
|
||||
ApplyReleases(updateInfo, x => progress(x / 3 + 66)),
|
||||
"Failed to apply updates").ConfigureAwait(false);
|
||||
|
||||
await this.ErrorIfThrows(() =>
|
||||
CreateUninstallerRegistryEntry(),
|
||||
"Failed to set up uninstaller").ConfigureAwait(false);
|
||||
if (SquirrelRuntimeInfo.IsWindows) {
|
||||
await CreateUninstallerRegistryEntry().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
} catch {
|
||||
if (ignoreDeltaUpdates == false) {
|
||||
ignoreDeltaUpdates = true;
|
||||
@@ -259,6 +260,7 @@ namespace Squirrel
|
||||
return process;
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
private Process restartProcess(string exeToStart = null, string arguments = null)
|
||||
{
|
||||
// NB: Here's how this method works:
|
||||
@@ -285,7 +287,7 @@ namespace Squirrel
|
||||
args.Add(arguments);
|
||||
}
|
||||
|
||||
return Process.Start(_config.UpdateExePath, Utility.ArgsToCommandLine(args));
|
||||
return ProcessUtil.StartNonBlocking(_config.UpdateExePath, args, Path.GetDirectoryName(_config.UpdateExePath));
|
||||
}
|
||||
|
||||
private static string GetLocalAppDataDirectory(string assemblyLocation = null)
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\src\SquirrelCli\NugetConsole.cs" Link="TestHelpers\NugetConsole.cs" />
|
||||
<Compile Include="..\src\Squirrel.CommandLine\NugetConsole.cs" Link="TestHelpers\NugetConsole.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user