diff --git a/build.ps1 b/build.ps1 index 364b50c7..9f47e073 100644 --- a/build.ps1 +++ b/build.ps1 @@ -37,7 +37,7 @@ Copy-Item "$In\Win32\WriteZipToSetup.exe" -Destination "$Out" Copy-Item "$In\Win32\WriteZipToSetup.pdb" -Destination "$Out" Copy-Item -Path "$PSScriptRoot\vendor\7zip\*" -Destination "$Out" -Recurse -Copy-Item -Path "$PSScriptRoot\vendor\wix\*" -Destination "$Out" -Recurse +# Copy-Item -Path "$PSScriptRoot\vendor\wix\*" -Destination "$Out" -Recurse Copy-Item "$PSScriptRoot\vendor\NuGet.exe" -Destination "$Out" Copy-Item "$PSScriptRoot\vendor\rcedit.exe" -Destination "$Out" Copy-Item "$PSScriptRoot\vendor\signtool.exe" -Destination "$Out" diff --git a/src/SquirrelCli/squirrel.ico b/squirrel.ico similarity index 100% rename from src/SquirrelCli/squirrel.ico rename to squirrel.ico diff --git a/src/Squirrel/Squirrel.csproj b/src/Squirrel/Squirrel.csproj index 620bdb9c..660e9444 100644 --- a/src/Squirrel/Squirrel.csproj +++ b/src/Squirrel/Squirrel.csproj @@ -7,8 +7,7 @@ Squirrel Squirrel true - 9 - Squirrel.Lib + SquirrelLib diff --git a/src/SquirrelCli/SquirrelCli.csproj b/src/SquirrelCli/SquirrelCli.csproj index a21e0244..2094c921 100644 --- a/src/SquirrelCli/SquirrelCli.csproj +++ b/src/SquirrelCli/SquirrelCli.csproj @@ -1,22 +1,21 @@  - Exe net6.0 + Exe + 9 + Squirrel + ..\..\squirrel.ico + en + + true true true - true win-x86 - squirrel.ico - Squirrel - en + true - - - - diff --git a/src/Squirrel/Lib/ValidatedOptionSet.cs b/src/SquirrelCli/ValidatedOptionSet.cs similarity index 100% rename from src/Squirrel/Lib/ValidatedOptionSet.cs rename to src/SquirrelCli/ValidatedOptionSet.cs diff --git a/src/SquirrelCli/app.manifest b/src/SquirrelCli/app.manifest deleted file mode 100644 index 532ca1ee..00000000 --- a/src/SquirrelCli/app.manifest +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Update/CopStache.cs b/src/Update/CopStache.cs deleted file mode 100644 index 82e1b5e7..00000000 --- a/src/Update/CopStache.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security; -using System.Text; -using System.Threading.Tasks; - -namespace Squirrel.Update -{ - public static class CopStache - { - public static string Render(string template, Dictionary identifiers) - { - var buf = new StringBuilder(); - - foreach (var line in template.Split('\n')) { - identifiers["RandomGuid"] = (Guid.NewGuid()).ToString(); - - foreach (var key in identifiers.Keys) { - buf.Replace("{{" + key + "}}", SecurityElement.Escape(identifiers[key])); - } - - buf.AppendLine(line); - } - - return buf.ToString(); - } - } -} diff --git a/src/Update/Program.cs b/src/Update/Program.cs index 7cf0deeb..8e131040 100644 --- a/src/Update/Program.cs +++ b/src/Update/Program.cs @@ -17,21 +17,15 @@ using Squirrel.Lib; namespace Squirrel.Update { - enum UpdateAction - { - Unset = 0, Install, Uninstall, Download, Update, Releasify, Shortcut, - Deshortcut, ProcessStart, UpdateSelf, CheckForUpdate - } - class Program : IEnableLogger { static StartupOption opt; + static IFullLogger Log => SquirrelLocator.Current.GetService().GetLogger(typeof(Program)); public static int Main(string[] args) { - var pg = new Program(); try { - return pg.main(args); + return main(args); } catch (Exception ex) { // NB: Normally this is a terrible idea but we want to make // sure Setup.exe above us gets the nonzero error code @@ -40,7 +34,7 @@ namespace Squirrel.Update } } - int main(string[] args) + static int main(string[] args) { try { opt = new StartupOption(args); @@ -68,9 +62,9 @@ namespace Squirrel.Update } } - int executeCommandLine(string[] args) + static int executeCommandLine(string[] args) { - this.Log().Info("Starting Squirrel Updater: " + String.Join(" ", args)); + Log.Info("Starting Squirrel Updater: " + String.Join(" ", args)); if (args.Any(x => x.StartsWith("/squirrel", StringComparison.OrdinalIgnoreCase))) { // NB: We're marked as Squirrel-aware, but we don't want to do @@ -84,7 +78,6 @@ namespace Squirrel.Update } switch (opt.updateAction) { -#if !MONO case UpdateAction.Install: var progressSource = new ProgressSource(); Install(opt.silentInstall, progressSource, Path.GetFullPath(opt.target)).Wait(); @@ -105,7 +98,7 @@ namespace Squirrel.Update UpdateSelf().Wait(); break; case UpdateAction.Shortcut: - Shortcut(opt.target, opt.shortcutArgs, opt.processStartArgs, opt.setupIcon, opt.onlyUpdateShortcuts); + Shortcut(opt.target, opt.shortcutArgs, opt.processStartArgs, opt.icon, opt.onlyUpdateShortcuts); break; case UpdateAction.Deshortcut: Deshortcut(opt.target, opt.shortcutArgs); @@ -113,22 +106,18 @@ namespace Squirrel.Update case UpdateAction.ProcessStart: ProcessStart(opt.processStart, opt.processStartArgs, opt.shouldWait); break; -#endif - case UpdateAction.Releasify: - Releasify(opt.target, opt.releaseDir, opt.bootstrapperExe, opt.backgroundGif, opt.signingParameters, opt.baseUrl, opt.setupIcon, !opt.noMsi, opt.packageAs64Bit, opt.frameworkVersion, !opt.noDelta); - break; } - this.Log().Info("Finished Squirrel Updater"); + Log.Info("Finished Squirrel Updater"); return 0; } - public async Task Install(bool silentInstall, ProgressSource progressSource, string sourceDirectory = null) + static async Task Install(bool silentInstall, ProgressSource progressSource, string sourceDirectory = null) { sourceDirectory = sourceDirectory ?? AssemblyRuntimeInfo.BaseDirectory; var releasesPath = Path.Combine(sourceDirectory, "RELEASES"); - this.Log().Info("Starting install, writing to {0}", sourceDirectory); + Log.Info("Starting install, writing to {0}", sourceDirectory); if (!AssemblyRuntimeInfo.IsSingleFile) { // when doing a standard build, our executable has multiple files/dependencies. @@ -138,7 +127,7 @@ namespace Squirrel.Update } if (!File.Exists(releasesPath)) { - this.Log().Info("RELEASES doesn't exist, creating it at " + releasesPath); + Log.Info("RELEASES doesn't exist, creating it at " + releasesPath); var nupkgs = (new DirectoryInfo(sourceDirectory)).GetFiles() .Where(x => x.Name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)) .Select(x => ReleaseEntry.GenerateFromFile(x.FullName)); @@ -150,42 +139,42 @@ namespace Squirrel.Update .First().PackageName; using (var mgr = new UpdateManager(sourceDirectory, ourAppName)) { - this.Log().Info("About to install to: " + mgr.RootAppDirectory); + Log.Info("About to install to: " + mgr.RootAppDirectory); if (Directory.Exists(mgr.RootAppDirectory)) { - this.Log().Warn("Install path {0} already exists, burning it to the ground", mgr.RootAppDirectory); + Log.Warn("Install path {0} already exists, burning it to the ground", mgr.RootAppDirectory); mgr.KillAllExecutablesBelongingToPackage(); await Task.Delay(500); - await this.ErrorIfThrows(() => Utility.DeleteDirectory(mgr.RootAppDirectory), + await Log.ErrorIfThrows(() => Utility.DeleteDirectory(mgr.RootAppDirectory), "Failed to remove existing directory on full install, is the app still running???"); - this.ErrorIfThrows(() => Utility.Retry(() => Directory.CreateDirectory(mgr.RootAppDirectory), 3), + Log.ErrorIfThrows(() => Utility.Retry(() => Directory.CreateDirectory(mgr.RootAppDirectory), 3), "Couldn't recreate app directory, perhaps Antivirus is blocking it"); } Directory.CreateDirectory(mgr.RootAppDirectory); var updateTarget = Path.Combine(mgr.RootAppDirectory, "Update.exe"); - this.ErrorIfThrows(() => File.Copy(AssemblyRuntimeInfo.EntryExePath, updateTarget, true), + Log.ErrorIfThrows(() => File.Copy(AssemblyRuntimeInfo.EntryExePath, updateTarget, true), "Failed to copy Update.exe to " + updateTarget); await mgr.FullInstall(silentInstall, progressSource.Raise); - await this.ErrorIfThrows(() => mgr.CreateUninstallerRegistryEntry(), + await Log.ErrorIfThrows(() => mgr.CreateUninstallerRegistryEntry(), "Failed to create uninstaller registry entry"); } } - public async Task Update(string updateUrl, string appName = null) + static async Task Update(string updateUrl, string appName = null) { appName = appName ?? getAppNameFromDirectory(); - this.Log().Info("Starting update, downloading from " + updateUrl); + Log.Info("Starting update, downloading from " + updateUrl); using (var mgr = new UpdateManager(updateUrl, appName)) { bool ignoreDeltaUpdates = false; - this.Log().Info("About to update to: " + mgr.RootAppDirectory); + Log.Info("About to update to: " + mgr.RootAppDirectory); retry: try { @@ -199,24 +188,24 @@ namespace Squirrel.Update await mgr.ApplyReleases(updateInfo, x => Console.WriteLine(UpdateManager.CalculateProgress(x, 30, 100))); } catch (Exception ex) { if (ignoreDeltaUpdates) { - this.Log().ErrorException("Really couldn't apply updates!", ex); + Log.ErrorException("Really couldn't apply updates!", ex); throw; } - this.Log().WarnException("Failed to apply updates, falling back to full updates", ex); + Log.WarnException("Failed to apply updates, falling back to full updates", ex); ignoreDeltaUpdates = true; goto retry; } var updateTarget = Path.Combine(mgr.RootAppDirectory, "Update.exe"); - await this.ErrorIfThrows(() => + await Log.ErrorIfThrows(() => mgr.CreateUninstallerRegistryEntry(), "Failed to create uninstaller registry entry"); } } - public async Task UpdateSelf() + static async Task UpdateSelf() { waitForParentToExit(); var src = AssemblyRuntimeInfo.EntryExePath; @@ -229,11 +218,11 @@ namespace Squirrel.Update }); } - public async Task Download(string updateUrl, string appName = null) + static async Task Download(string updateUrl, string appName = null) { appName = appName ?? getAppNameFromDirectory(); - this.Log().Info("Fetching update information, downloading from " + updateUrl); + Log.Info("Fetching update information, downloading from " + updateUrl); using (var mgr = new UpdateManager(updateUrl, appName)) { var updateInfo = await mgr.CheckForUpdate(intention: UpdaterIntention.Update, progress: x => Console.WriteLine(x / 3)); await mgr.DownloadReleases(updateInfo.ReleasesToApply, x => Console.WriteLine(33 + x / 3)); @@ -253,11 +242,11 @@ namespace Squirrel.Update } } - public async Task CheckForUpdate(string updateUrl, string appName = null) + static async Task CheckForUpdate(string updateUrl, string appName = null) { appName = appName ?? getAppNameFromDirectory(); - this.Log().Info("Fetching update information, downloading from " + updateUrl); + Log.Info("Fetching update information, downloading from " + updateUrl); using (var mgr = new UpdateManager(updateUrl, appName)) { var updateInfo = await mgr.CheckForUpdate(intention: UpdaterIntention.Update, progress: x => Console.WriteLine(x)); var releaseNotes = updateInfo.FetchReleaseNotes(); @@ -275,9 +264,9 @@ namespace Squirrel.Update } } - public async Task Uninstall(string appName = null) + static async Task Uninstall(string appName = null) { - this.Log().Info("Starting uninstall for app: " + appName); + Log.Info("Starting uninstall for app: " + appName); appName = appName ?? getAppNameFromDirectory(); using (var mgr = new UpdateManager("", appName)) { @@ -294,172 +283,7 @@ namespace Squirrel.Update } } - public void Releasify(string package, string targetDir = null, string bootstrapperExe = null, string backgroundGif = null, string signingOpts = null, string baseUrl = null, string setupIcon = null, bool generateMsi = true, bool packageAs64Bit = false, string frameworkVersion = null, bool generateDeltas = true) - { - ensureConsole(); - - if (baseUrl != null) { - if (!Utility.IsHttpUrl(baseUrl)) { - throw new Exception(string.Format("Invalid --baseUrl '{0}'. A base URL must start with http or https and be a valid URI.", baseUrl)); - } - - if (!baseUrl.EndsWith("/")) { - baseUrl += "/"; - } - } - - targetDir = targetDir ?? Path.Combine(".", "Releases"); - bootstrapperExe = bootstrapperExe ?? Path.Combine(".", "Setup.exe"); - - if (!Directory.Exists(targetDir)) { - Directory.CreateDirectory(targetDir); - } - - if (!File.Exists(bootstrapperExe)) { - bootstrapperExe = Path.Combine( - AssemblyRuntimeInfo.BaseDirectory, - "Setup.exe"); - } - - if (!File.Exists(bootstrapperExe)) { - this.Log().Error("Could not find Bootstrapper EXE. Please place it in the directory next to me, or use the bootstrapperExe command line option."); - throw new ArgumentException(); - } - - this.Log().Info("Bootstrapper EXE found at:" + bootstrapperExe); - - // validate that the provided "frameworkVersion" is supported by Setup.exe - if (!String.IsNullOrWhiteSpace(frameworkVersion)) { - var chkFrameworkResult = Utility.InvokeProcessAsync(bootstrapperExe, "--checkFramework " + frameworkVersion, CancellationToken.None).Result; - if (chkFrameworkResult.Item1 != 0) { - this.Log().Error($"Unsupported FrameworkVersion: '{frameworkVersion}'. {chkFrameworkResult.Item2}"); - throw new ArgumentException(); - } - } - - // copy input package to target output directory - var di = new DirectoryInfo(targetDir); - File.Copy(package, Path.Combine(di.FullName, Path.GetFileName(package)), true); - - var allNuGetFiles = di.EnumerateFiles() - .Where(x => x.Name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)); - - var toProcess = allNuGetFiles.Where(x => !x.Name.Contains("-delta") && !x.Name.Contains("-full")); - var processed = new List(); - - var releaseFilePath = Path.Combine(di.FullName, "RELEASES"); - var previousReleases = new List(); - if (File.Exists(releaseFilePath)) { - previousReleases.AddRange(ReleaseEntry.ParseReleaseFile(File.ReadAllText(releaseFilePath, Encoding.UTF8))); - } - - foreach (var file in toProcess) { - this.Log().Info("Creating release package: " + file.FullName); - - var rp = new ReleasePackage(file.FullName); - rp.CreateReleasePackage(Path.Combine(di.FullName, rp.SuggestedReleaseFileName), contentsPostProcessHook: pkgPath => { - - // create sub executable for all exe's in this package (except Squirrel!) - new DirectoryInfo(pkgPath).GetAllFilesRecursively() - .Where(x => x.Name.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase)) - .Where(x => !x.Name.Contains("squirrel.exe", StringComparison.InvariantCultureIgnoreCase)) - .Where(x => Utility.IsFileTopLevelInPackage(x.FullName, pkgPath)) - .Where(x => Utility.ExecutableUsesWin32Subsystem(x.FullName)) - .ForEachAsync(x => createExecutableStubForExe(x.FullName)) - .Wait(); - - // copy myself into the package so Squirrel can also be updated - // how we find the lib dir is a huge hack here, but 'ReleasePackage' verifies there can only be one of these so it should be fine. - var re = new Regex(@"lib[\\\/][^\\\/]*[\\\/]?", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); - var libDir = Directory - .EnumerateDirectories(pkgPath, "*", SearchOption.AllDirectories) - .Where(d => re.IsMatch(d)) - .OrderBy(d => d.Length) - .FirstOrDefault(); - File.Copy(getUpdateExePath(), Path.Combine(libDir, "Squirrel.exe")); - - // sign all exe's in this package - if (signingOpts == null) return; - new DirectoryInfo(pkgPath).GetAllFilesRecursively() - .Where(x => Utility.FileIsLikelyPEImage(x.Name)) - .ForEachAsync(async x => { - if (isPEFileSigned(x.FullName)) { - this.Log().Info("{0} is already signed, skipping", x.FullName); - return; - } - - this.Log().Info("About to sign {0}", x.FullName); - await signPEFile(x.FullName, signingOpts); - }, 1) - .Wait(); - }); - - processed.Add(rp.ReleasePackageFile); - - var prev = ReleaseEntry.GetPreviousRelease(previousReleases, rp, targetDir); - if (prev != null && generateDeltas) { - var deltaBuilder = new DeltaPackageBuilder(null); - - var dp = deltaBuilder.CreateDeltaPackage(prev, rp, - Path.Combine(di.FullName, rp.SuggestedReleaseFileName.Replace("full", "delta"))); - processed.Insert(0, dp.InputPackageFile); - } - } - - foreach (var file in toProcess) { File.Delete(file.FullName); } - - var newReleaseEntries = processed - .Select(packageFilename => ReleaseEntry.GenerateFromFile(packageFilename, baseUrl)) - .ToList(); - var distinctPreviousReleases = previousReleases - .Where(x => !newReleaseEntries.Select(e => e.Version).Contains(x.Version)); - var releaseEntries = distinctPreviousReleases.Concat(newReleaseEntries).ToList(); - - ReleaseEntry.WriteReleaseFile(releaseEntries, releaseFilePath); - - var targetSetupExe = Path.Combine(di.FullName, "Setup.exe"); - var newestFullRelease = releaseEntries.MaxBy(x => x.Version).Where(x => !x.IsDelta).First(); - - File.Copy(bootstrapperExe, targetSetupExe, true); - var zipPath = createSetupEmbeddedZip(Path.Combine(di.FullName, newestFullRelease.Filename), di.FullName, signingOpts, setupIcon).Result; - - var writeZipToSetup = Utility.FindHelperExecutable("WriteZipToSetup.exe"); - - try { - string arguments = $"\"{targetSetupExe}\" \"{zipPath}\""; - if (!String.IsNullOrWhiteSpace(frameworkVersion)) { - arguments += $" --set-required-framework \"{frameworkVersion}\""; - } - if (!String.IsNullOrWhiteSpace(backgroundGif)) { - arguments += $" --set-splash \"{Path.GetFullPath(backgroundGif)}\""; - } - - var result = Utility.InvokeProcessAsync(writeZipToSetup, arguments, CancellationToken.None).Result; - if (result.Item1 != 0) throw new Exception("Failed to write Zip to Setup.exe!\n\n" + result.Item2); - } catch (Exception ex) { - this.Log().ErrorException("Failed to update Setup.exe with new Zip file", ex); - throw; - } finally { - File.Delete(zipPath); - } - - Utility.Retry(() => - setPEVersionInfoAndIcon(targetSetupExe, new ZipPackage(package), setupIcon).Wait()); - - if (signingOpts != null) { - signPEFile(targetSetupExe, signingOpts).Wait(); - } - - if (generateMsi) { - createMsiPackage(targetSetupExe, new ZipPackage(package), packageAs64Bit).Wait(); - - if (signingOpts != null) { - signPEFile(targetSetupExe.Replace(".exe", ".msi"), signingOpts).Wait(); - } - } - } - - public void Shortcut(string exeName, string shortcutArgs, string processStartArgs, string icon, bool onlyUpdate) + static void Shortcut(string exeName, string shortcutArgs, string processStartArgs, string icon, bool onlyUpdate) { if (String.IsNullOrWhiteSpace(exeName)) { ShowHelp(); @@ -475,7 +299,7 @@ namespace Squirrel.Update } } - public void Deshortcut(string exeName, string shortcutArgs) + static void Deshortcut(string exeName, string shortcutArgs) { if (String.IsNullOrWhiteSpace(exeName)) { ShowHelp(); @@ -491,7 +315,7 @@ namespace Squirrel.Update } } - public void ProcessStart(string exeName, string arguments, bool shouldWait) + static void ProcessStart(string exeName, string arguments, bool shouldWait) { if (String.IsNullOrWhiteSpace(exeName)) { ShowHelp(); @@ -519,7 +343,7 @@ namespace Squirrel.Update // Check for the EXE name they want var targetExe = new FileInfo(Path.Combine(latestAppDir, exeName.Replace("%20", " "))); - this.Log().Info("Want to launch '{0}'", targetExe); + Log.Info("Want to launch '{0}'", targetExe); // Check for path canonicalization attacks if (!targetExe.FullName.StartsWith(latestAppDir, StringComparison.Ordinal)) { @@ -527,27 +351,27 @@ namespace Squirrel.Update } if (!targetExe.Exists) { - this.Log().Error("File {0} doesn't exist in current release", targetExe); + Log.Error("File {0} doesn't exist in current release", targetExe); throw new ArgumentException(); } if (shouldWait) waitForParentToExit(); try { - this.Log().Info("About to launch: '{0}': {1}", targetExe.FullName, arguments ?? ""); + Log.Info("About to launch: '{0}': {1}", targetExe.FullName, arguments ?? ""); Process.Start(new ProcessStartInfo(targetExe.FullName, arguments ?? "") { WorkingDirectory = Path.GetDirectoryName(targetExe.FullName) }); } catch (Exception ex) { - this.Log().ErrorException("Failed to start process", ex); + Log.ErrorException("Failed to start process", ex); } } - public void ShowHelp() + static void ShowHelp() { ensureConsole(); opt.WriteOptionDescriptions(); } - void waitForParentToExit() + static void waitForParentToExit() { // Grab a handle the parent process var parentPid = NativeMethods.GetParentProcessId(); @@ -557,242 +381,16 @@ namespace Squirrel.Update try { handle = NativeMethods.OpenProcess(ProcessAccess.Synchronize, false, parentPid); if (handle != IntPtr.Zero) { - this.Log().Info("About to wait for parent PID {0}", parentPid); + Log.Info("About to wait for parent PID {0}", parentPid); NativeMethods.WaitForSingleObject(handle, 0xFFFFFFFF /*INFINITE*/); } else { - this.Log().Info("Parent PID {0} no longer valid - ignoring", parentPid); + Log.Info("Parent PID {0} no longer valid - ignoring", parentPid); } } finally { if (handle != IntPtr.Zero) NativeMethods.CloseHandle(handle); } } - string getUpdateExePath() - { - //if (selfContained) { - // var selfPath = Path.Combine(AssemblyRuntimeInfo.BaseDirectory, "UpdateSelfContained.exe"); - // if (!File.Exists(selfPath)) { - // this.Log().Error("Could not find UpdateSelfContained.exe in base directory. Am I published?"); - // } - // return selfPath; - //} - return AssemblyRuntimeInfo.EntryExePath; - } - - async Task createSetupEmbeddedZip(string fullPackage, string releasesDir, string signingOpts, string setupIcon) - { - string tempPath; - - this.Log().Info("Building embedded zip file for Setup.exe"); - using (Utility.WithTempDirectory(out tempPath, null)) { - this.ErrorIfThrows(() => { - File.Copy(getUpdateExePath(), Path.Combine(tempPath, "Update.exe")); - File.Copy(fullPackage, Path.Combine(tempPath, Path.GetFileName(fullPackage))); - }, "Failed to write package files to temp dir: " + tempPath); - - if (!String.IsNullOrWhiteSpace(setupIcon)) { - this.ErrorIfThrows(() => { - File.Copy(setupIcon, Path.Combine(tempPath, "setupIcon.ico")); - }, "Failed to write icon to temp dir: " + tempPath); - } - - var releases = new[] { ReleaseEntry.GenerateFromFile(fullPackage) }; - ReleaseEntry.WriteReleaseFile(releases, Path.Combine(tempPath, "RELEASES")); - - var target = Path.GetTempFileName(); - File.Delete(target); - - // Sign Update.exe so that virus scanners don't think we're - // pulling one over on them - if (signingOpts != null) { - var di = new DirectoryInfo(tempPath); - - var files = di.EnumerateFiles() - .Where(x => x.Name.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase)) - .Select(x => x.FullName); - - await files.ForEachAsync(x => signPEFile(x, signingOpts)); - } - - this.ErrorIfThrows(() => - ZipFile.CreateFromDirectory(tempPath, target, CompressionLevel.Optimal, false), - "Failed to create Zip file from directory: " + tempPath); - - return target; - } - } - - static async Task signPEFile(string exePath, string signingOpts) - { - // Try to find SignTool.exe - var exe = @".\signtool.exe"; - if (!File.Exists(exe)) { - exe = Path.Combine(AssemblyRuntimeInfo.BaseDirectory, "signtool.exe"); - - // Run down PATH and hope for the best - if (!File.Exists(exe)) exe = "signtool.exe"; - } - - var processResult = await Utility.InvokeProcessAsync(exe, - String.Format("sign {0} \"{1}\"", signingOpts, exePath), CancellationToken.None); - - if (processResult.Item1 != 0) { - var optsWithPasswordHidden = new Regex(@"/p\s+\w+").Replace(signingOpts, "/p ********"); - var msg = String.Format("Failed to sign, command invoked was: '{0} sign {1} {2}'", - exe, optsWithPasswordHidden, exePath); - - throw new Exception(msg); - } else { - Console.WriteLine(processResult.Item2); - } - } - bool isPEFileSigned(string path) - { -#if MONO - return Path.GetExtension(path).Equals(".exe", StringComparison.OrdinalIgnoreCase); -#else - try { - return AuthenticodeTools.IsTrusted(path); - } catch (Exception ex) { - this.Log().ErrorException("Failed to determine signing status for " + path, ex); - return false; - } -#endif - } - - async Task createExecutableStubForExe(string fullName) - { - var exe = Utility.FindHelperExecutable(@"StubExecutable.exe"); - - var target = Path.Combine( - Path.GetDirectoryName(fullName), - Path.GetFileNameWithoutExtension(fullName) + "_ExecutionStub.exe"); - - await Utility.CopyToAsync(exe, target); - - await Utility.InvokeProcessAsync( - Utility.FindHelperExecutable("WriteZipToSetup.exe"), - String.Format("--copy-stub-resources \"{0}\" \"{1}\"", fullName, target), - CancellationToken.None); - } - - static async Task setPEVersionInfoAndIcon(string exePath, IPackage package, string iconPath = null) - { - var realExePath = Path.GetFullPath(exePath); - var company = String.Join(",", package.Authors); - var verStrings = new Dictionary() { - { "CompanyName", company }, - { "LegalCopyright", package.Copyright ?? "Copyright © " + DateTime.Now.Year.ToString() + " " + company }, - { "FileDescription", package.Summary ?? package.Description ?? "Installer for " + package.Id }, - { "ProductName", package.Description ?? package.Summary ?? package.Id }, - }; - - var args = verStrings.Aggregate(new StringBuilder("\"" + realExePath + "\""), (acc, x) => { acc.AppendFormat(" --set-version-string \"{0}\" \"{1}\"", x.Key, x.Value); return acc; }); - args.AppendFormat(" --set-file-version {0} --set-product-version {0}", package.Version.ToString()); - if (iconPath != null) { - args.AppendFormat(" --set-icon \"{0}\"", Path.GetFullPath(iconPath)); - } - - // Try to find rcedit.exe - string exe = Utility.FindHelperExecutable("rcedit.exe"); - - var processResult = await Utility.InvokeProcessAsync(exe, args.ToString(), CancellationToken.None); - - if (processResult.Item1 != 0) { - var msg = String.Format( - "Failed to modify resources, command invoked was: '{0} {1}'\n\nOutput was:\n{2}", - exe, args, processResult.Item2); - - throw new Exception(msg); - } else { - Console.WriteLine(processResult.Item2); - } - } - - static async Task createMsiPackage(string setupExe, IPackage package, bool packageAs64Bit) - { - var pathToWix = pathToWixTools(); - var setupExeDir = Path.GetDirectoryName(setupExe); - var company = String.Join(",", package.Authors); - - var culture = CultureInfo.GetCultureInfo(package.Language ?? "").TextInfo.ANSICodePage; - - - var templateText = File.ReadAllText(Path.Combine(pathToWix, "template.wxs")); - var templateData = new Dictionary { - { "Id", package.Id }, - { "Title", package.Title }, - { "Author", company }, - { "Version", Regex.Replace(package.Version.ToString(), @"-.*$", "") }, - { "Summary", package.Summary ?? package.Description ?? package.Id }, - { "Codepage", $"{culture}" }, - { "Platform", packageAs64Bit ? "x64" : "x86" }, - { "ProgramFilesFolder", packageAs64Bit ? "ProgramFiles64Folder" : "ProgramFilesFolder" }, - { "Win64YesNo", packageAs64Bit ? "yes" : "no" } - }; - - // NB: We need some GUIDs that are based on the package ID, but unique (i.e. - // "Unique but consistent"). - for (int i = 1; i <= 10; i++) { - templateData[String.Format("IdAsGuid{0}", i)] = Utility.CreateGuidFromHash(String.Format("{0}:{1}", package.Id, i)).ToString(); - } - - var templateResult = CopStache.Render(templateText, templateData); - - var wxsTarget = Path.Combine(setupExeDir, "Setup.wxs"); - File.WriteAllText(wxsTarget, templateResult, Encoding.UTF8); - - var candleParams = String.Format("-nologo -ext WixNetFxExtension -out \"{0}\" \"{1}\"", wxsTarget.Replace(".wxs", ".wixobj"), wxsTarget); - var processResult = await Utility.InvokeProcessAsync( - Path.Combine(pathToWix, "candle.exe"), candleParams, CancellationToken.None, setupExeDir); - - if (processResult.Item1 != 0) { - var msg = String.Format( - "Failed to compile WiX template, command invoked was: '{0} {1}'\n\nOutput was:\n{2}", - "candle.exe", candleParams, processResult.Item2); - - throw new Exception(msg); - } - - var lightParams = String.Format("-ext WixNetFxExtension -sval -out \"{0}\" \"{1}\"", wxsTarget.Replace(".wxs", ".msi"), wxsTarget.Replace(".wxs", ".wixobj")); - processResult = await Utility.InvokeProcessAsync( - Path.Combine(pathToWix, "light.exe"), lightParams, CancellationToken.None, setupExeDir); - - if (processResult.Item1 != 0) { - var msg = String.Format( - "Failed to link WiX template, command invoked was: '{0} {1}'\n\nOutput was:\n{2}", - "light.exe", lightParams, processResult.Item2); - - throw new Exception(msg); - } - - var toDelete = new[] { - wxsTarget, - wxsTarget.Replace(".wxs", ".wixobj"), - wxsTarget.Replace(".wxs", ".wixpdb"), - }; - - await Utility.ForEachAsync(toDelete, x => Utility.DeleteFileHarder(x)); - } - - static string pathToWixTools() - { - var ourPath = AssemblyRuntimeInfo.BaseDirectory; - - // Same Directory? (i.e. released) - if (File.Exists(Path.Combine(ourPath, "candle.exe"))) { - return ourPath; - } - - // Debug Mode (i.e. in vendor) - var debugPath = Path.Combine(ourPath, "..", "..", "..", "vendor", "wix", "candle.exe"); - if (File.Exists(debugPath)) { - return Path.GetFullPath(debugPath); - } - - throw new Exception("WiX tools can't be found"); - } - static string getAppNameFromDirectory(string path = null) { path = path ?? AssemblyRuntimeInfo.BaseDirectory; diff --git a/src/Update/StartupOption.cs b/src/Update/StartupOption.cs index 0fe90b68..5cba0b01 100644 --- a/src/Update/StartupOption.cs +++ b/src/Update/StartupOption.cs @@ -3,28 +3,24 @@ using System; namespace Squirrel.Update { + enum UpdateAction + { + Unset = 0, Install, Uninstall, Download, Update, Shortcut, + Deshortcut, ProcessStart, UpdateSelf, CheckForUpdate + } + internal class StartupOption { private readonly OptionSet optionSet; - internal bool silentInstall { get; private set; } = false; internal UpdateAction updateAction { get; private set; } = default(UpdateAction); internal string target { get; private set; } = default(string); - internal string releaseDir { get; private set; } = default(string); - internal string bootstrapperExe { get; private set; } = default(string); - internal string backgroundGif { get; private set; } = default(string); - internal string signingParameters { get; private set; } = default(string); - internal string baseUrl { get; private set; } = default(string); + //internal string baseUrl { get; private set; } = default(string); internal string processStart { get; private set; } = default(string); internal string processStartArgs { get; private set; } = default(string); - internal string setupIcon { get; private set; } = default(string); internal string icon { get; private set; } = default(string); internal string shortcutArgs { get; private set; } = default(string); - internal string frameworkVersion { get; private set; } internal bool shouldWait { get; private set; } = false; - internal bool noMsi { get; private set; } = (Environment.OSVersion.Platform != PlatformID.Win32NT); // NB: WiX doesn't work under Mono / Wine - internal bool packageAs64Bit { get; private set; } = false; - internal bool noDelta { get; private set; } = false; internal bool onlyUpdateShortcuts { get; private set; } = false; public StartupOption(string[] args) @@ -44,7 +40,6 @@ namespace Squirrel.Update { "download=", "Download the releases specified by the URL and write new results to stdout as JSON", v => { updateAction = UpdateAction.Download; target = v; } }, { "checkForUpdate=", "Check for one available update and writes new results to stdout as JSON", v => { updateAction = UpdateAction.CheckForUpdate; target = v; } }, { "update=", "Update the application to the latest remote version specified by URL", v => { updateAction = UpdateAction.Update; target = v; } }, - { "releasify=", "Update or generate a releases directory with a given NuGet package", v => { updateAction = UpdateAction.Releasify; target = v; } }, { "createShortcut=", "Create a shortcut for the given executable name", v => { updateAction = UpdateAction.Shortcut; target = v; } }, { "removeShortcut=", "Remove a shortcut for the given executable name", v => { updateAction = UpdateAction.Deshortcut; target = v; } }, { "updateSelf=", "Copy the currently executing Update.exe into the default location", v => { updateAction = UpdateAction.UpdateSelf; target = v; } }, @@ -53,29 +48,16 @@ namespace Squirrel.Update "", "Options:", { "h|?|help", "Display Help and exit", _ => {} }, - { "r=|releaseDir=", "Path to a release directory to use with releasify", v => releaseDir = v}, - { "bootstrapperExe=", "Path to the Setup.exe to use as a template", v => bootstrapperExe = v}, - { "g=|loadingGif=|splashImage=", "Path to an animated GIF to be displayed during installation", v => backgroundGif = v}, { "i=|icon", "Path to an ICO file that will be used for icon shortcuts", v => icon = v}, - { "setupIcon=", "Path to an ICO file that will be used for the Setup executable's icon", v => setupIcon = v}, - { "n=|signWithParams=", "Sign the installer via SignTool.exe with the parameters given", v => signingParameters = v}, { "s|silent", "Silent install", _ => silentInstall = true}, - { "b=|baseUrl=", "Provides a base URL to prefix the RELEASES file packages with", v => baseUrl = v, true}, + //{ "b=|baseUrl=", "Provides a base URL to prefix the RELEASES file packages with", v => baseUrl = v, true}, { "a=|process-start-args=", "Arguments that will be used when starting executable", v => processStartArgs = v, true}, { "l=|shortcut-locations=", "Comma-separated string of shortcut locations, e.g. 'Desktop,StartMenu'", v => shortcutArgs = v}, - { "no-msi", "Don't generate an MSI package", v => noMsi = true}, - { "no-delta", "Don't generate delta packages to save time", v => noDelta = true}, - { "framework-version=", "Set the required .NET framework version, e.g. net461", v => frameworkVersion = v }, - { "msi-win64", "Mark the MSI as 64-bit, which is useful in Enterprise deployment scenarios", _ => packageAs64Bit = true}, { "updateOnly", "Update shortcuts that already exist, rather than creating new ones", _ => onlyUpdateShortcuts = true}, }; opts.Parse(args); - // NB: setupIcon and icon are just aliases for compatibility - // reasons, because of a dumb breaking rename I made in 1.0.1 - setupIcon = setupIcon ?? icon; - return opts; } diff --git a/src/Update/Update.csproj b/src/Update/Update.csproj index 8743edc6..10566f38 100644 --- a/src/Update/Update.csproj +++ b/src/Update/Update.csproj @@ -1,29 +1,30 @@ - - + - - net6.0 - WinExe - 9 - Squirrel.Update - app.manifest - squirrel.ico - CA1416 - en - true - true - true - win-x86 - true - + + net6.0 + WinExe + 9 + Squirrel.Update + app.manifest + ..\..\squirrel.ico + CA1416 + en + + + true + true + true + win-x86 + true + - - - update.ico - + + + update.ico + - - - + + + \ No newline at end of file diff --git a/src/Update/squirrel.ico b/src/Update/squirrel.ico deleted file mode 100644 index d9bd2a75..00000000 Binary files a/src/Update/squirrel.ico and /dev/null differ