diff --git a/src/Squirrel/Internal/HelperExe.cs b/src/Squirrel/Internal/HelperExe.cs index b22d542c9..ff929e714 100644 --- a/src/Squirrel/Internal/HelperExe.cs +++ b/src/Squirrel/Internal/HelperExe.cs @@ -94,10 +94,10 @@ public static async Task CompileWixTemplateToMsi(string wxsTarget, string output var candleParams = new string[] { "-nologo", "-ext", "WixNetFxExtension", "-out", objFile, wxsTarget }; var processResult = await Utility.InvokeProcessAsync(WixCandlePath, candleParams, CancellationToken.None, workingDir).ConfigureAwait(false); - if (processResult.Item1 != 0) { + if (processResult.ExitCode != 0) { var msg = String.Format( "Failed to compile WiX template, command invoked was: '{0} {1}'\n\nOutput was:\n{2}", - "candle.exe", Utility.ArgsToCommandLine(candleParams), processResult.Item2); + "candle.exe", Utility.ArgsToCommandLine(candleParams), processResult.StdOutput); throw new Exception(msg); } @@ -105,10 +105,10 @@ public static async Task CompileWixTemplateToMsi(string wxsTarget, string output var lightParams = new string[] { "-ext", "WixNetFxExtension", "-spdb", "-sval", "-out", outputFile, objFile }; processResult = await Utility.InvokeProcessAsync(WixLightPath, lightParams, CancellationToken.None, workingDir).ConfigureAwait(false); - if (processResult.Item1 != 0) { + if (processResult.ExitCode != 0) { var msg = String.Format( "Failed to link WiX template, command invoked was: '{0} {1}'\n\nOutput was:\n{2}", - "light.exe", Utility.ArgsToCommandLine(lightParams), processResult.Item2); + "light.exe", Utility.ArgsToCommandLine(lightParams), processResult.StdOutput); throw new Exception(msg); } } finally { diff --git a/src/Squirrel/Internal/StringFileInfo.cs b/src/Squirrel/Internal/StringFileInfo.cs index 3de395a16..70bc962ae 100644 --- a/src/Squirrel/Internal/StringFileInfo.cs +++ b/src/Squirrel/Internal/StringFileInfo.cs @@ -47,11 +47,25 @@ protected StringFileInfo(Version fileVersion, Version productVersion, uint fileF FileDate = fileDate; } + public class VersionInfoItem + { + public uint CodePage { get; set; } + public string Key { get; set; } + public string Value { get; set; } + + public VersionInfoItem(uint codePage, string key, string value) + { + CodePage = codePage; + Key = key; + Value = value; + } + } + // vi can be null on exit // Item1 = language | codepage // Item2 = Key // Item3 = Value - public static IEnumerable<(uint CodePage, string Key, string Value)> ReadVersionInfo(string fileName, out StringFileInfo vi) + public static IEnumerable ReadVersionInfo(string fileName, out StringFileInfo vi) { int num; int size = GetFileVersionInfoSize(fileName, out num); @@ -75,7 +89,7 @@ protected StringFileInfo(Version fileVersion, Version productVersion, uint fileF // Item1 = language | codepage // Item2 = Key // Item3 = Value - public static IEnumerable<(uint CodePage, string Key, string Value)> ReadVersionInfo(byte[] buffer, out StringFileInfo vi) + public static IEnumerable ReadVersionInfo(byte[] buffer, out StringFileInfo vi) { int offset; // The offset calculated here is unused @@ -118,7 +132,7 @@ protected StringFileInfo(Version fileVersion, Version productVersion, uint fileF return ReadVersionInfoInternal(buffer, fibs); } - protected static IEnumerable<(uint CodePage, string Key, string Value)> ReadVersionInfoInternal(byte[] buffer, FileInfoBaseStruct fibs) + protected static IEnumerable ReadVersionInfoInternal(byte[] buffer, FileInfoBaseStruct fibs) { int sfiOrValOffset = (fibs.ValueOffset + fibs.ValueLength + 3) & (~3); @@ -148,7 +162,7 @@ protected StringFileInfo(Version fileVersion, Version productVersion, uint fileF int len = FindLengthUnicodeSZ(buffer, stri.ValueOffset, stri.ValueOffset + (stri.ValueLength * 2)); string value = Encoding.Unicode.GetString(buffer, stri.ValueOffset, len * 2); - yield return (langCharset, stri.Key, value); + yield return new VersionInfoItem(langCharset, stri.Key, value); striOffset = nextStriOffset; } diff --git a/src/Squirrel/Internal/Utility.cs b/src/Squirrel/Internal/Utility.cs index 632f48b51..9c2d86c2f 100644 --- a/src/Squirrel/Internal/Utility.cs +++ b/src/Squirrel/Internal/Utility.cs @@ -280,11 +280,23 @@ public static string EscapeCmdExeMetachars(string command) return result.ToString(); } + public class ProcessResult + { + public int ExitCode { get; set; } + public string StdOutput { get; set; } + + public ProcessResult(int exitCode, string stdOutput) + { + ExitCode = exitCode; + StdOutput = stdOutput; + } + } + /// /// 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. /// - public static Task<(int ExitCode, string StdOutput)> InvokeProcessAsync(string fileName, IEnumerable args, CancellationToken ct, string workingDirectory = "") + public static Task InvokeProcessAsync(string fileName, IEnumerable 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); @@ -306,7 +318,7 @@ public static ProcessStartInfo CreateProcessStartInfo(string fileName, string ar return psi; } - public static async Task<(int ExitCode, string StdOutput)> InvokeProcessUnsafeAsync(ProcessStartInfo psi, CancellationToken ct) + public static async Task InvokeProcessUnsafeAsync(ProcessStartInfo psi, CancellationToken ct) { var pi = Process.Start(psi); await Task.Run(() => { @@ -329,7 +341,7 @@ await Task.Run(() => { } } - return (pi.ExitCode, textResult.Trim()); + return new ProcessResult(pi.ExitCode, textResult.Trim()); } public static Task ForEachAsync(this IEnumerable source, Action body, int degreeOfParallelism = 4) @@ -870,10 +882,22 @@ public static bool ByteArrayCompareFast(byte[] a1, byte[] a2) } #endif + public class ProcessInfo + { + public string ProcessExePath { get; set; } + public int ProcessId { get; set; } + + public ProcessInfo(string processExePath, int processId) + { + ProcessExePath = processExePath; + ProcessId = processId; + } + } + #if NET5_0_OR_GREATER [System.Runtime.Versioning.SupportedOSPlatform("windows")] #endif - public static List<(string ProcessExePath, int ProcessId)> EnumerateProcesses() + public static List EnumerateProcesses() { var pids = new int[2048]; var gch = GCHandle.Alloc(pids, GCHandleType.Pinned); @@ -884,7 +908,7 @@ public static bool ByteArrayCompareFast(byte[] a1, byte[] a2) if (bytesReturned < 1) throw new Exception("Failed to enumerate processes"); - List<(string ProcessExePath, int ProcessId)> ret = new(); + List ret = new(); for (int i = 0; i < bytesReturned / sizeof(int); i++) { IntPtr hProcess = IntPtr.Zero; @@ -902,7 +926,7 @@ public static bool ByteArrayCompareFast(byte[] a1, byte[] a2) if (String.IsNullOrWhiteSpace(exePath) || !File.Exists(exePath)) continue; - ret.Add((sb.ToString(), pids[i])); + ret.Add(new ProcessInfo(sb.ToString(), pids[i])); } catch (Exception) { // don't care } finally { diff --git a/src/Squirrel/ReleaseEntry.cs b/src/Squirrel/ReleaseEntry.cs index b09fa399b..145124450 100644 --- a/src/Squirrel/ReleaseEntry.cs +++ b/src/Squirrel/ReleaseEntry.cs @@ -386,15 +386,33 @@ internal static ReleasePackage GetPreviousRelease(IEnumerable rele static readonly Regex _suffixRegex = new Regex(@"(-full|-delta)?\.nupkg$", RegexOptions.Compiled | RegexOptions.IgnoreCase); static readonly Regex _versionStartRegex = new Regex(@"[\.-](0|[1-9]\d*)\.(0|[1-9]\d*)($|[^\d])", RegexOptions.Compiled); + internal class EntryNameInfo + { + public string PackageName { get; set; } + public SemanticVersion Version { get; set; } + public bool IsDelta { get; set; } + + public EntryNameInfo() + { + } + + public EntryNameInfo(string packageName, SemanticVersion version, bool isDelta) + { + PackageName = packageName; + Version = version; + IsDelta = isDelta; + } + } + /// /// Takes a filename such as 'My-Cool3-App-1.0.1-build.23-full.nupkg' and separates it into /// it's name and version (eg. 'My-Cool3-App', and '1.0.1-build.23'). Returns null values if /// the filename can not be parsed. /// - internal static (string PackageName, SemanticVersion Version, bool IsDelta) ParseEntryFileName(string fileName) + internal static EntryNameInfo ParseEntryFileName(string fileName) { if (!fileName.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)) - return (null, null, false); + return new EntryNameInfo(); bool delta = Path.GetFileNameWithoutExtension(fileName).EndsWith("-delta", StringComparison.OrdinalIgnoreCase); @@ -402,14 +420,14 @@ internal static (string PackageName, SemanticVersion Version, bool IsDelta) Pars var match = _versionStartRegex.Match(nameAndVer); if (!match.Success) - return (null, null, delta); + return new EntryNameInfo(null, null, delta); var verIdx = match.Index; var name = nameAndVer.Substring(0, verIdx); var version = nameAndVer.Substring(verIdx + 1); var semVer = new SemanticVersion(version); - return (name, semVer, delta); + return new EntryNameInfo(name, semVer, delta); } } } diff --git a/src/Squirrel/RuntimeInfo.cs b/src/Squirrel/RuntimeInfo.cs index 8ac88ff51..780dc8667 100644 --- a/src/Squirrel/RuntimeInfo.cs +++ b/src/Squirrel/RuntimeInfo.cs @@ -457,13 +457,25 @@ public override Task CheckIsSupported() const string UninstallRegSubKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall"; + private class VCVersion + { + public SemanticVersion Ver { get; set; } + public RuntimeCpu Cpu { get; set; } + + public VCVersion(SemanticVersion ver, RuntimeCpu cpu) + { + Ver = ver; + Cpu = cpu; + } + } + /// /// Returns the list of currently installed VC++ redistributables, as reported by the /// Windows Programs & Features dialog. /// - public static (SemanticVersion Ver, RuntimeCpu Cpu)[] GetInstalledVCVersions() + private static VCVersion[] GetInstalledVCVersions() { - List<(SemanticVersion Ver, RuntimeCpu Cpu)> results = new List<(SemanticVersion Ver, RuntimeCpu Cpu)>(); + List results = new(); void searchreg(RegistryKey view) { @@ -476,9 +488,9 @@ void searchreg(RegistryKey view) // these entries do not get added into the correct registry hive, so we need to determine // the cpu architecture from the name. I hate this but what can I do? if (name.Contains("x64") && Environment.Is64BitOperatingSystem) { - results.Add((v, RuntimeCpu.x64)); + results.Add(new VCVersion(v, RuntimeCpu.x64)); } else { - results.Add((v, RuntimeCpu.x86)); + results.Add(new VCVersion(v, RuntimeCpu.x86)); } } } diff --git a/src/Squirrel/Squirrel.csproj b/src/Squirrel/Squirrel.csproj index fb018507e..e32f7f28a 100644 --- a/src/Squirrel/Squirrel.csproj +++ b/src/Squirrel/Squirrel.csproj @@ -19,7 +19,6 @@ - diff --git a/src/Squirrel/UpdateManager.ApplyReleases.cs b/src/Squirrel/UpdateManager.ApplyReleases.cs index 84d790a3d..13a1f4ab7 100644 --- a/src/Squirrel/UpdateManager.ApplyReleases.cs +++ b/src/Squirrel/UpdateManager.ApplyReleases.cs @@ -98,7 +98,9 @@ public async Task FullUninstall() if (!releases.Any()) return; - var (currentRelease, currentVersion) = releases.OrderByDescending(x => x.Version).FirstOrDefault(); + var currentLocalRelease = releases.OrderByDescending(x => x.Version).FirstOrDefault(); + var currentRelease = currentLocalRelease.Directory; + var currentVersion = currentLocalRelease.Version; this.Log().Info("Starting full uninstall"); if (currentRelease.Exists) { @@ -630,7 +632,7 @@ await squirrelApps.ForEachAsync(async exe => { // Finally, clean up the app-X.Y.Z directories await toCleanup.ForEachAsync(x => { try { - if (runningProcesses.All(p => p.Item1 == null || !p.Item1.StartsWith(x.FullName, StringComparison.OrdinalIgnoreCase))) { + if (runningProcesses.All(p => p.ProcessExePath == null || !p.ProcessExePath.StartsWith(x.FullName, StringComparison.OrdinalIgnoreCase))) { Utility.DeleteFileOrDirectoryHardOrGiveUp(x.FullName); } @@ -681,15 +683,27 @@ internal async Task> updateLocalReleasesFile() return await Task.Run(() => ReleaseEntry.BuildReleasesFile(Utility.PackageDirectoryForAppDir(rootAppDirectory))).ConfigureAwait(false); } - IEnumerable<(DirectoryInfo Directory, SemanticVersion Version)> getReleases() + class LocalReleaseDirectory + { + public DirectoryInfo Directory { get; set; } + public SemanticVersion Version { get; set; } + + public LocalReleaseDirectory(DirectoryInfo directory, SemanticVersion version) + { + Directory = directory; + Version = version; + } + } + + IEnumerable getReleases() { var rootDirectory = new DirectoryInfo(rootAppDirectory); - if (!rootDirectory.Exists) return Enumerable.Empty<(DirectoryInfo Directory, SemanticVersion Version)>(); + if (!rootDirectory.Exists) return Enumerable.Empty(); return rootDirectory.GetDirectories() .Where(x => x.Name.StartsWith("app-", StringComparison.InvariantCultureIgnoreCase)) - .Select(x => (x, new SemanticVersion(x.Name.Substring(4)))); + .Select(x => new LocalReleaseDirectory(x, new SemanticVersion(x.Name.Substring(4)))); } DirectoryInfo getDirectoryForRelease(SemanticVersion releaseVersion) diff --git a/src/Squirrel/UpdateManager.InstallHelpers.cs b/src/Squirrel/UpdateManager.InstallHelpers.cs index ba3456812..f099da32c 100644 --- a/src/Squirrel/UpdateManager.InstallHelpers.cs +++ b/src/Squirrel/UpdateManager.InstallHelpers.cs @@ -108,22 +108,22 @@ public void KillAllProcessesBelongingToPackage() .Where(x => { // Processes we can't query will have an empty process name, we can't kill them // anyways - if (String.IsNullOrWhiteSpace(x.Item1)) return false; + if (String.IsNullOrWhiteSpace(x.ProcessExePath)) return false; // Files that aren't in our root app directory are untouched if (!Utility.IsFileInDirectory(x.ProcessExePath, rootAppDirectory)) return false; // Never kill our own EXE - if (ourExePath != null && x.Item1.Equals(ourExePath, StringComparison.OrdinalIgnoreCase)) return false; + if (ourExePath != null && x.ProcessExePath.Equals(ourExePath, StringComparison.OrdinalIgnoreCase)) return false; - var name = Path.GetFileName(x.Item1).ToLowerInvariant(); + var name = Path.GetFileName(x.ProcessExePath).ToLowerInvariant(); if (name == "squirrel.exe" || name == "update.exe") return false; return true; }) .ForEach(x => { try { - this.WarnIfThrows(() => Process.GetProcessById(x.Item2).Kill()); + this.WarnIfThrows(() => Process.GetProcessById(x.ProcessId).Kill()); } catch { } }); }