Some utility code clean up

This commit is contained in:
Caelan Sayler
2023-12-26 13:28:55 +00:00
parent 7a482a6f28
commit ad4fdb4208
14 changed files with 167 additions and 549 deletions

View File

@@ -1,4 +1,6 @@
using System.Runtime.Versioning;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
@@ -98,7 +100,7 @@ public class HelperExe : HelperFile
// https://stackoverflow.com/questions/35619036/open-app-after-installation-from-pkg-file-in-mac
var postinstall = Path.Combine(tmpScripts, "postinstall");
File.WriteAllText(postinstall, $"#!/bin/sh\nsudo -u \"$USER\" open \"$2/{bundleName}/\"\nexit 0");
PlatformUtil.ChmodFileAsExecutable(postinstall);
ChmodFileAsExecutable(postinstall);
// generate non-relocatable component pkg. this will be included into a product archive
var pkgPlistPath = Path.Combine(tmp, "tmp.plist");
@@ -172,7 +174,7 @@ public class HelperExe : HelperFile
filePath
};
var ntresultjson = PlatformUtil.InvokeProcess("xcrun", args, null, CancellationToken.None);
var ntresultjson = InvokeProcess("xcrun", args, null);
Log.Info(ntresultjson.StdOutput);
// try to catch any notarization errors. if we have a submission id, retrieve notary logs.
@@ -187,7 +189,7 @@ public class HelperExe : HelperFile
"--keychain-profile", keychainProfileName,
};
var result = PlatformUtil.InvokeProcess("xcrun", logargs, null, CancellationToken.None);
var result = InvokeProcess("xcrun", logargs, null);
Log.Warn(result.StdOutput);
}
@@ -232,4 +234,36 @@ public class HelperExe : HelperFile
Log.Info($"Creating ditto bundle '{outputZip}'");
InvokeAndThrowIfNonZero("ditto", args, null);
}
private const string OSX_CSTD_LIB = "libSystem.dylib";
private const string NIX_CSTD_LIB = "libc";
[SupportedOSPlatform("osx")]
[DllImport(OSX_CSTD_LIB, EntryPoint = "chmod", SetLastError = true)]
private static extern int osx_chmod(string pathname, int mode);
[SupportedOSPlatform("linux")]
[DllImport(NIX_CSTD_LIB, EntryPoint = "chmod", SetLastError = true)]
private static extern int nix_chmod(string pathname, int mode);
protected static void ChmodFileAsExecutable(string filePath)
{
Func<string, int, int> chmod;
if (SquirrelRuntimeInfo.IsOSX) chmod = osx_chmod;
else if (SquirrelRuntimeInfo.IsLinux) chmod = nix_chmod;
else return; // no-op on windows, all .exe files can be executed.
var filePermissionOctal = Convert.ToInt32("777", 8);
const int EINTR = 4;
int chmodReturnCode;
do {
chmodReturnCode = chmod(filePath, filePermissionOctal);
} while (chmodReturnCode == -1 && Marshal.GetLastWin32Error() == EINTR);
if (chmodReturnCode == -1) {
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Could not set file permission {filePermissionOctal} for {filePath}.");
}
}
}

View File

@@ -1,4 +1,6 @@
using System.Runtime.Versioning;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
@@ -68,7 +70,7 @@ public class HelperExe : HelperFile
}
var totalToSign = pendingSign.Count;
var baseSignArgs = PlatformUtil.CommandLineToArgvW(signArguments);
var baseSignArgs = CommandLineToArgvW(signArguments);
do {
List<string> args = new List<string>();
@@ -78,7 +80,7 @@ public class HelperExe : HelperFile
args.Add(pendingSign.Dequeue());
}
var result = PlatformUtil.InvokeProcess(SignToolPath, args, rootDir, CancellationToken.None);
var result = InvokeProcess(SignToolPath, args, rootDir);
if (result.ExitCode != 0) {
var cmdWithPasswordHidden = new Regex(@"\/p\s+?[^\s]+").Replace(result.Command, "/p ********");
Log.Debug($"Signing command failed: {cmdWithPasswordHidden}");
@@ -101,7 +103,7 @@ public class HelperExe : HelperFile
var command = signTemplate.Replace("\"{{file}}\"", "{{file}}").Replace("{{file}}", $"\"{filePath}\"");
var result = PlatformUtil.InvokeProcess(command, null, null, CancellationToken.None);
var result = InvokeProcess(command, null, null);
if (result.ExitCode != 0) {
var cmdWithPasswordHidden = new Regex(@"\/p\s+?[^\s]+").Replace(result.Command, "/p ********");
Log.Debug($"Signing command failed: {cmdWithPasswordHidden}");
@@ -144,4 +146,37 @@ public class HelperExe : HelperFile
Utility.Retry(() => InvokeAndThrowIfNonZero(RceditPath, args, null));
}
private const string WIN_KERNEL32 = "kernel32.dll";
private const string WIN_SHELL32 = "shell32.dll";
[SupportedOSPlatform("windows")]
[DllImport(WIN_KERNEL32, EntryPoint = "LocalFree", SetLastError = true)]
private static extern IntPtr _LocalFree(IntPtr hMem);
[SupportedOSPlatform("windows")]
[DllImport(WIN_SHELL32, EntryPoint = "CommandLineToArgvW", CharSet = CharSet.Unicode)]
private static extern IntPtr _CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string cmdLine, out int numArgs);
[SupportedOSPlatform("windows")]
protected static string[] CommandLineToArgvW(string cmdLine)
{
IntPtr argv = IntPtr.Zero;
try {
argv = _CommandLineToArgvW(cmdLine, out var 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);
}
}
}

View File

@@ -1,4 +1,8 @@
using System.Reflection;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using Microsoft.Extensions.Logging;
namespace Squirrel.Packaging;
@@ -88,8 +92,48 @@ public class HelperFile
protected static string InvokeAndThrowIfNonZero(string exePath, IEnumerable<string> args, string workingDir)
{
var result = PlatformUtil.InvokeProcess(exePath, args, workingDir, CancellationToken.None);
var result = InvokeProcess(exePath, args, workingDir);
ProcessFailedException.ThrowIfNonZero(result);
return result.StdOutput;
}
protected 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());
}
protected static (int ExitCode, string StdOutput, string Command) InvokeProcess(string fileName, IEnumerable<string> args, string workingDirectory, CancellationToken ct = default)
{
var psi = CreateProcessStartInfo(fileName, workingDirectory);
psi.AppendArgumentListSafe(args, out var argString);
var p = InvokeProcess(psi, ct);
return (p.ExitCode, p.StdOutput, $"{fileName} {argString}");
}
protected 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;
}
}

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using System;
using System;
using System.IO;
using System.IO.Compression;

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using System;
using System;
using System.IO;
using System.IO.Compression;
using System.Threading;

View File

@@ -1,8 +1,10 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
namespace Squirrel
{
[ExcludeFromCodeCoverage]
internal static class Disposable
{
public static IDisposable Create(Action action)

View File

@@ -1,5 +1,4 @@
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using System;
using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Logging;

View File

@@ -1,461 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Squirrel
{
internal static class PlatformUtil
{
private const string OSX_CSTD_LIB = "libSystem.dylib";
private const string NIX_CSTD_LIB = "libc";
private const string WIN_KERNEL32 = "kernel32.dll";
private const string WIN_SHELL32 = "shell32.dll";
private const string WIN_NTDLL = "NTDLL.DLL";
private const string WIN_PSAPI = "psapi.dll";
[SupportedOSPlatform("osx")]
[DllImport(OSX_CSTD_LIB, EntryPoint = "chmod", SetLastError = true)]
private static extern int osx_chmod(string pathname, int mode);
[SupportedOSPlatform("linux")]
[DllImport(NIX_CSTD_LIB, EntryPoint = "chmod", SetLastError = true)]
private static extern int nix_chmod(string pathname, int mode);
public static void ChmodFileAsExecutable(string filePath)
{
Func<string, int, int> chmod;
if (SquirrelRuntimeInfo.IsOSX) chmod = osx_chmod;
else if (SquirrelRuntimeInfo.IsLinux) chmod = nix_chmod;
else return; // no-op on windows, all .exe files can be executed.
var filePermissionOctal = Convert.ToInt32("777", 8);
const int EINTR = 4;
int chmodReturnCode;
do {
chmodReturnCode = chmod(filePath, filePermissionOctal);
} while (chmodReturnCode == -1 && Marshal.GetLastWin32Error() == EINTR);
if (chmodReturnCode == -1) {
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Could not set file permission {filePermissionOctal} for {filePath}.");
}
}
private enum MagicMachO : uint
{
MH_MAGIC = 0xfeedface,
MH_CIGAM = 0xcefaedfe,
MH_MAGIC_64 = 0xfeedfacf,
MH_CIGAM_64 = 0xcffaedfe
}
public static bool IsMachOImage(string filePath)
{
using (BinaryReader reader = new BinaryReader(File.OpenRead(filePath))) {
if (reader.BaseStream.Length < 256) // Header size
return false;
uint magic = reader.ReadUInt32();
return Enum.IsDefined(typeof(MagicMachO), magic);
}
}
[SupportedOSPlatform("windows")]
[DllImport(WIN_PSAPI, SetLastError = true)]
private static extern bool EnumProcesses(
IntPtr pProcessIds, // pointer to allocated DWORD array
int cb,
out int pBytesReturned);
[SupportedOSPlatform("windows")]
[DllImport(WIN_KERNEL32, SetLastError = true)]
private static extern bool QueryFullProcessImageName(
IntPtr hProcess,
[In] int justPassZeroHere,
[Out] StringBuilder lpImageFileName,
[In] [MarshalAs(UnmanagedType.U4)] ref int nSize);
[Flags]
private enum ProcessAccess : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VirtualMemoryOperation = 0x00000008,
VirtualMemoryRead = 0x00000010,
VirtualMemoryWrite = 0x00000020,
DuplicateHandle = 0x00000040,
CreateProcess = 0x000000080,
SetQuota = 0x00000100,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
QueryLimitedInformation = 0x00001000,
Synchronize = 0x00100000
}
[SupportedOSPlatform("windows")]
[DllImport(WIN_KERNEL32, SetLastError = true)]
private static extern IntPtr OpenProcess(
ProcessAccess processAccess,
bool bInheritHandle,
int processId);
[SupportedOSPlatform("windows")]
[DllImport(WIN_KERNEL32, SetLastError = true)]
private static extern bool CloseHandle(IntPtr hHandle);
[SupportedOSPlatform("windows")]
private static List<(string ProcessExePath, int ProcessId)> GetRunningProcessesWindows()
{
var pids = new int[2048];
var gch = GCHandle.Alloc(pids, GCHandleType.Pinned);
try {
if (!EnumProcesses(gch.AddrOfPinnedObject(), sizeof(int) * pids.Length, out var bytesReturned))
throw new Win32Exception("Failed to enumerate processes");
if (bytesReturned < 1)
throw new Exception("Failed to enumerate processes");
List<(string ProcessExePath, int ProcessId)> ret = new();
for (int i = 0; i < bytesReturned / sizeof(int); i++) {
IntPtr hProcess = IntPtr.Zero;
try {
hProcess = OpenProcess(ProcessAccess.QueryLimitedInformation, false, pids[i]);
if (hProcess == IntPtr.Zero)
continue;
var sb = new StringBuilder(256);
var capacity = sb.Capacity;
if (!QueryFullProcessImageName(hProcess, 0, sb, ref capacity))
continue;
var exePath = sb.ToString();
if (String.IsNullOrWhiteSpace(exePath) || !File.Exists(exePath))
continue;
ret.Add((sb.ToString(), pids[i]));
} catch (Exception) {
// don't care
} finally {
if (hProcess != IntPtr.Zero)
CloseHandle(hProcess);
}
}
return ret;
} finally {
gch.Free();
}
}
public static List<(string ProcessExePath, int ProcessId)> GetRunningProcesses()
{
IEnumerable<(string ProcessExePath, int ProcessId)> processes = SquirrelRuntimeInfo.IsWindows
? GetRunningProcessesWindows()
: Process.GetProcesses().Select(p => (p.MainModule?.FileName, p.Id));
return processes
.Where(x => !String.IsNullOrWhiteSpace(x.ProcessExePath)) // Processes we can't query will have an empty process name
.ToList();
}
public static List<(string ProcessExePath, int ProcessId)> GetRunningProcessesInDirectory(string directory)
{
return GetRunningProcesses()
.Where(x => Utility.IsFileInDirectory(x.ProcessExePath, directory))
.ToList();
}
public static void KillProcessesInDirectory(ILogger logger, string directoryToKill)
{
logger.Info("Killing all processes in " + directoryToKill);
var myPid = Process.GetCurrentProcess().Id;
int c = 0;
foreach (var x in GetRunningProcessesInDirectory(directoryToKill)) {
if (myPid == x.ProcessId) {
logger.Info($"Skipping '{x.ProcessExePath}' (is current process)");
continue;
}
try {
Process.GetProcessById(x.ProcessId).Kill();
c++;
} catch (Exception ex) {
logger.Warn(ex, $"Unable to terminate process (pid.{x.ProcessId})");
}
}
logger.Info($"Terminated {c} processes successfully.");
}
[SupportedOSPlatform("windows")]
[DllImport(WIN_KERNEL32, EntryPoint = "LocalFree", SetLastError = true)]
private static extern IntPtr _LocalFree(IntPtr hMem);
[SupportedOSPlatform("windows")]
[DllImport(WIN_SHELL32, EntryPoint = "CommandLineToArgvW", CharSet = CharSet.Unicode)]
private static extern IntPtr _CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string cmdLine, out int numArgs);
[SupportedOSPlatform("windows")]
public static string[] CommandLineToArgvW(string cmdLine)
{
IntPtr argv = IntPtr.Zero;
try {
argv = _CommandLineToArgvW(cmdLine, out var 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);
}
}
/*
* 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 (args != null) {
#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 StartProcessNonBlocking(string fileName, IEnumerable<string> args, string workingDirectory)
{
var (psi, cmd) = CreateProcessStartInfo(fileName, args, workingDirectory);
return Process.Start(psi);
}
public static Process StartProcessNonBlocking(string fileName, string args, string workingDirectory)
{
var psi = CreateProcessStartInfo(fileName, workingDirectory);
psi.Arguments = args;
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));
}
private enum StandardHandles : int
{
STD_INPUT_HANDLE = -10,
STD_OUTPUT_HANDLE = -11,
STD_ERROR_HANDLE = -12,
}
[SupportedOSPlatform("windows")]
[DllImport(WIN_KERNEL32, EntryPoint = "GetStdHandle")]
private static extern IntPtr GetStdHandle(StandardHandles nStdHandle);
[SupportedOSPlatform("windows")]
[DllImport(WIN_KERNEL32, EntryPoint = "AllocConsole")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool AllocConsole();
[SupportedOSPlatform("windows")]
[DllImport(WIN_KERNEL32)]
private static extern bool AttachConsole(int pid);
static int consoleCreated = 0;
[SupportedOSPlatform("windows")]
public static void AttachConsole()
{
if (Environment.OSVersion.Platform != PlatformID.Win32NT) return;
if (Interlocked.CompareExchange(ref consoleCreated, 1, 0) == 1) return;
if (!AttachConsole(-1)) {
AllocConsole();
}
GetStdHandle(StandardHandles.STD_ERROR_HANDLE);
GetStdHandle(StandardHandles.STD_OUTPUT_HANDLE);
}
}
}

View File

@@ -72,6 +72,7 @@ using Squirrel.Json.Reflection;
#endif
#if NET5_0_OR_GREATER
namespace Squirrel.Json
{
[ExcludeFromCodeCoverage]
@@ -83,9 +84,8 @@ namespace Squirrel.Json
}
}
}
#endif
#if !NET5_0_OR_GREATER
#else
// ReSharper disable LoopCanBeConvertedToQuery
// ReSharper disable RedundantExplicitArrayCreation

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
@@ -15,6 +16,8 @@ namespace Squirrel
{
internal static class Utility
{
public const string SpecVersionFileName = "sq.version";
/// <summary>
/// Calculates the total percentage of a specific step that should report within a specific range.
/// <para />
@@ -508,6 +511,28 @@ namespace Squirrel
return AppendPathToUri(uri, "");
}
public static async Task<int> GetExitCodeAsync(this Process p)
{
#if NET5_0_OR_GREATER
await p.WaitForExitAsync().ConfigureAwait(false);
return p.ExitCode;
#else
var tcs = new TaskCompletionSource<int>();
var thread = new Thread(() => {
try {
p.WaitForExit();
tcs.SetResult(p.ExitCode);
} catch (Exception ex) {
tcs.SetException(ex);
}
});
thread.IsBackground = true;
thread.Start();
await tcs.Task.ConfigureAwait(false);
return p.ExitCode;
#endif
}
public static Uri AddQueryParamsToUri(Uri uri, IEnumerable<KeyValuePair<string, string>> newQuery)
{
var query = System.Web.HttpUtility.ParseQueryString(uri.Query);
@@ -530,24 +555,6 @@ namespace Squirrel
return peExtensions.Any(x => ext.Equals(x, StringComparison.OrdinalIgnoreCase));
}
public static bool IsFileTopLevelInPackage(string fullName, string pkgPath)
{
var fn = fullName.ToLowerInvariant();
var pkg = pkgPath.ToLowerInvariant();
var relativePath = fn.Replace(pkg, "");
// NB: We want to match things like `/lib/net45/foo.exe` but not `/lib/net45/bar/foo.exe`
return relativePath.Split(Path.DirectorySeparatorChar).Length == 4;
}
public static void ConsoleWriteWithColor(string text, ConsoleColor color)
{
var fc = Console.ForegroundColor;
Console.ForegroundColor = color;
Console.Write(text);
Console.ForegroundColor = fc;
}
public static Guid CreateGuidFromHash(string text)
{
return CreateGuidFromHash(text, Utility.IsoOidNamespace);
@@ -629,32 +636,6 @@ namespace Squirrel
guid[right] = temp;
}
public const string SpecVersionFileName = "sq.version";
public static NuspecManifest ReadManifestFromVersionDir(string appVersionDir)
{
NuspecManifest manifest;
string nuspec;
nuspec = Path.Combine(appVersionDir, SpecVersionFileName);
if (File.Exists(nuspec) && NuspecManifest.TryParseFromFile(nuspec, out manifest))
return manifest;
nuspec = Path.Combine(appVersionDir, "Contents", SpecVersionFileName);
if (File.Exists(nuspec) && NuspecManifest.TryParseFromFile(nuspec, out manifest))
return manifest;
nuspec = Path.Combine(appVersionDir, "mysqver");
if (File.Exists(nuspec) && NuspecManifest.TryParseFromFile(nuspec, out manifest))
return manifest;
nuspec = Path.Combine(appVersionDir, "current.version");
if (File.Exists(nuspec) && NuspecManifest.TryParseFromFile(nuspec, out manifest))
return manifest;
return null;
}
public static void CopyFiles(string source, string target)
{
CopyFiles(new DirectoryInfo(source), new DirectoryInfo(target));

View File

@@ -58,7 +58,7 @@ namespace Squirrel.Locators
var appPath = ourPath.Substring(0, ix + 4);
var contentsDir = Path.Combine(appPath, "Contents");
var updateExe = Path.Combine(contentsDir, "UpdateMac");
var metadataPath = Path.Combine(contentsDir, "sq.version");
var metadataPath = Path.Combine(contentsDir, Utility.SpecVersionFileName);
if (File.Exists(updateExe) && NuspecManifest.TryParseFromFile(metadataPath, out var manifest)) {
Log.Info("Located valid manifest file at: " + metadataPath);

View File

@@ -66,7 +66,7 @@ namespace Squirrel.Locators
if (File.Exists(possibleUpdateExe)) {
Log.Info("Update.exe found in parent directory");
// we're running in a directory with an Update.exe in the parent directory
var manifestFile = Path.Combine(myDirPath, "sq.version");
var manifestFile = Path.Combine(myDirPath, Utility.SpecVersionFileName);
if (NuspecManifest.TryParseFromFile(manifestFile, out var manifest)) {
// ideal, the info we need is in a manifest file.
Log.Info("Located valid manifest file at: " + manifestFile);
@@ -88,7 +88,7 @@ namespace Squirrel.Locators
// this is an attempt to handle the case where we are running in a nested current directory.
var rootDir = ourExePath.Substring(0, ixCurrent);
var currentDir = Path.Combine(rootDir, "current");
var manifestFile = Path.Combine(currentDir, "sq.version");
var manifestFile = Path.Combine(currentDir, Utility.SpecVersionFileName);
possibleUpdateExe = Path.GetFullPath(Path.Combine(rootDir, "Update.exe"));
// we only support parsing a manifest when we're in a nested current directory. no legacy fallback.
if (File.Exists(possibleUpdateExe) && NuspecManifest.TryParseFromFile(manifestFile, out var manifest)) {

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
@@ -87,17 +88,21 @@ namespace Squirrel.Windows
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 PlatformUtil.InvokeProcessAsync(pathToInstaller, isQuiet ? quietArgs : args, null, CancellationToken.None).ConfigureAwait(false);
var psi = new ProcessStartInfo(pathToInstaller);
psi.AppendArgumentListSafe(isQuiet ? quietArgs : args, out var _);
var p = Process.Start(psi);
var code = await p.GetExitCodeAsync().ConfigureAwait(false);
// https://johnkoerner.com/install/windows-installer-error-codes/
if (p.ExitCode == 1638) // a newer compatible version is already installed
if (code == 1638) // a newer compatible version is already installed
return RuntimeInstallResult.InstallSuccess;
if (p.ExitCode == 1641) // installer initiated a restart
if (code == 1641) // installer initiated a restart
return RuntimeInstallResult.RestartRequired;
return (RuntimeInstallResult) p.ExitCode;
return (RuntimeInstallResult) code;
}
/// <summary> The unique string representation of this runtime </summary>

View File

@@ -174,25 +174,6 @@ namespace Squirrel.Tests
Assert.Equal(result, Utility.FileIsLikelyPEImage(input));
}
[SkippableTheory]
[InlineData("C:\\Users\\bob\\temp\\pkgPath\\lib\\net45\\foo.exe", "C:\\Users\\bob\\temp\\pkgPath", true)]
[InlineData("C:\\Users\\bob\\temp\\pkgPath\\lib\\net45\\node_modules\\foo.exe", "C:\\Users\\bob\\temp\\pkgPath", false)]
[InlineData("C:\\Users\\bob\\temp\\pkgPath\\lib\\net45\\node_modules\\foo\\foo.exe", "C:\\Users\\bob\\temp\\pkgPath", false)]
[InlineData("foo.png", "C:\\Users\\bob\\temp\\pkgPath", false)]
public void IsFileTopLevelInPackageTest(string input, string packagePath, bool result)
{
Skip.IfNot(SquirrelRuntimeInfo.IsWindows);
Assert.Equal(result, Utility.IsFileTopLevelInPackage(input, packagePath));
}
[Fact]
public void WeCanFetchAllProcesses()
{
var result = PlatformUtil.GetRunningProcesses();
Assert.True(result.Count > 1);
Assert.True(result.Count != 2048);
}
[Fact(Skip = "Only really need to run this test after changes to FileDownloader")]
public async Task DownloaderReportsProgress()
{