From 1516a3ab52105b245ff3727dd005e155d506fb77 Mon Sep 17 00:00:00 2001 From: Jan Hannemann Date: Tue, 12 Mar 2019 13:57:44 -0700 Subject: [PATCH] fix multi-proc logging, log naming, no excpetions in happy path --- src/Squirrel/IUpdateManager.cs | 9 +- src/Squirrel/UpdateManager.ApplyReleases.cs | 26 ++-- src/Squirrel/UpdateManager.CheckForUpdates.cs | 32 +++-- src/Squirrel/UpdateManager.cs | 6 +- src/Update/Program.cs | 134 ++++++------------ src/Update/StartupOption.cs | 85 +++++++++++ src/Update/Update-Mono.csproj | 1 + src/Update/Update.csproj | 3 +- 8 files changed, 173 insertions(+), 123 deletions(-) create mode 100644 src/Update/StartupOption.cs diff --git a/src/Squirrel/IUpdateManager.cs b/src/Squirrel/IUpdateManager.cs index 1edc11e4e..8ca122164 100644 --- a/src/Squirrel/IUpdateManager.cs +++ b/src/Squirrel/IUpdateManager.cs @@ -23,12 +23,19 @@ public enum ShortcutLocation { AppRoot = 1 << 3 } + public enum UpdaterIntention { + Install, + Update + } + public interface IUpdateManager : IDisposable, IEnableLogger { /// /// Fetch the remote store for updates and compare against the current /// version to determine what updates to download. /// + /// Indicates whether the UpdateManager is used + /// in a Install or Update scenario. /// Set this flag if applying a release /// fails to fall back to a full release, which takes longer to download /// but is less error-prone. @@ -36,7 +43,7 @@ public interface IUpdateManager : IDisposable, IEnableLogger /// will return values from 0-100 and Complete, or Throw /// An UpdateInfo object representing the updates to install. /// - Task CheckForUpdate(bool ignoreDeltaUpdates = false, Action progress = null); + Task CheckForUpdate(bool ignoreDeltaUpdates = false, Action progress = null, UpdaterIntention intention = UpdaterIntention.Update); /// /// Download a list of releases into the local package directory. diff --git a/src/Squirrel/UpdateManager.ApplyReleases.cs b/src/Squirrel/UpdateManager.ApplyReleases.cs index 1dbd7c5f4..09af8c54b 100644 --- a/src/Squirrel/UpdateManager.ApplyReleases.cs +++ b/src/Squirrel/UpdateManager.ApplyReleases.cs @@ -536,25 +536,25 @@ internal void unshimOurselves() // directory are "dead" (i.e. already uninstalled, but not deleted), and // we blow them away. This is to make sure that we don't attempt to run // an uninstaller on an already-uninstalled version. - async Task cleanDeadVersions(SemanticVersion originalVersion, SemanticVersion currentVersion, bool forceUninstall = false) + async Task cleanDeadVersions(SemanticVersion currentVersion, SemanticVersion newVersion, bool forceUninstall = false) { - if (currentVersion == null) return; + if (newVersion == null) return; var di = new DirectoryInfo(rootAppDirectory); if (!di.Exists) return; - this.Log().Info("cleanDeadVersions: for version {0}", currentVersion); - - string originalVersionFolder = null; - if (originalVersion != null) { - originalVersionFolder = getDirectoryForRelease(originalVersion).Name; - this.Log().Info("cleanDeadVersions: exclude folder {0}", originalVersionFolder); - } + this.Log().Info("cleanDeadVersions: checking for version {0}", newVersion); string currentVersionFolder = null; if (currentVersion != null) { currentVersionFolder = getDirectoryForRelease(currentVersion).Name; - this.Log().Info("cleanDeadVersions: exclude folder {0}", currentVersionFolder); + this.Log().Info("cleanDeadVersions: exclude current version folder {0}", currentVersionFolder); + } + + string newVersionFolder = null; + if (newVersion != null) { + newVersionFolder = getDirectoryForRelease(newVersion).Name; + this.Log().Info("cleanDeadVersions: exclude new version folder {0}", newVersionFolder); } // NB: If we try to access a directory that has already been @@ -563,7 +563,7 @@ async Task cleanDeadVersions(SemanticVersion originalVersion, SemanticVersion cu // come from here. var toCleanup = di.GetDirectories() .Where(x => x.Name.ToLowerInvariant().Contains("app-")) - .Where(x => x.Name != currentVersionFolder && x.Name != originalVersionFolder) + .Where(x => x.Name != newVersionFolder && x.Name != currentVersionFolder) .Where(x => !isAppFolderDead(x.FullName)); if (forceUninstall == false) { @@ -591,7 +591,7 @@ await squirrelApps.ForEachAsync(async exe => { // Include dead folders in folders to :fire: toCleanup = di.GetDirectories() .Where(x => x.Name.ToLowerInvariant().Contains("app-")) - .Where(x => x.Name != currentVersionFolder && x.Name != originalVersionFolder); + .Where(x => x.Name != newVersionFolder && x.Name != currentVersionFolder); // Get the current process list in an attempt to not burn // directories which have running processes @@ -625,7 +625,7 @@ await toCleanup.ForEachAsync(async x => { var releaseEntry = default(ReleaseEntry); foreach (var entry in entries) { - if (entry.Version == currentVersion) { + if (entry.Version == newVersion) { releaseEntry = ReleaseEntry.GenerateFromFile(Path.Combine(pkgDir, entry.Filename)); continue; } diff --git a/src/Squirrel/UpdateManager.CheckForUpdates.cs b/src/Squirrel/UpdateManager.CheckForUpdates.cs index 226494d30..9ba91a736 100644 --- a/src/Squirrel/UpdateManager.CheckForUpdates.cs +++ b/src/Squirrel/UpdateManager.CheckForUpdates.cs @@ -21,6 +21,7 @@ public CheckForUpdateImpl(string rootAppDirectory) } public async Task CheckForUpdate( + UpdaterIntention intention, string localReleaseFile, string updateUrlOrPath, bool ignoreDeltaUpdates = false, @@ -30,15 +31,19 @@ public async Task CheckForUpdate( progress = progress ?? (_ => { }); var localReleases = Enumerable.Empty(); - var stagingId = getOrCreateStagedUserId(); + var stagingId = intention == UpdaterIntention.Install ? null : getOrCreateStagedUserId(); - bool shouldInitialize = false; - try { - localReleases = Utility.LoadLocalReleases(localReleaseFile); - } catch (Exception ex) { - // Something has gone pear-shaped, let's start from scratch - this.Log().WarnException("Failed to load local releases, starting from scratch", ex); - shouldInitialize = true; + bool shouldInitialize = intention == UpdaterIntention.Install; + + if (intention != UpdaterIntention.Install) { + try { + localReleases = Utility.LoadLocalReleases(localReleaseFile); + } + catch (Exception ex) { + // Something has gone pear-shaped, let's start from scratch + this.Log().WarnException("Failed to load local releases, starting from scratch", ex); + shouldInitialize = true; + } } if (shouldInitialize) await initializeClientAppDirectory(); @@ -125,7 +130,7 @@ public async Task CheckForUpdate( throw new Exception("Remote release File is empty or corrupted"); } - ret = determineUpdateInfo(localReleases, remoteReleases, ignoreDeltaUpdates); + ret = determineUpdateInfo(intention, localReleases, remoteReleases, ignoreDeltaUpdates); progress(100); return ret; @@ -142,7 +147,7 @@ async Task initializeClientAppDirectory() Directory.CreateDirectory(pkgDir); } - UpdateInfo determineUpdateInfo(IEnumerable localReleases, IEnumerable remoteReleases, bool ignoreDeltaUpdates) + UpdateInfo determineUpdateInfo(UpdaterIntention intention, IEnumerable localReleases, IEnumerable remoteReleases, bool ignoreDeltaUpdates) { var packageDirectory = Utility.PackageDirectoryForAppDir(rootAppDirectory); localReleases = localReleases ?? Enumerable.Empty(); @@ -167,7 +172,12 @@ UpdateInfo determineUpdateInfo(IEnumerable localReleases, IEnumera } if (!localReleases.Any()) { - this.Log().Warn("First run or local directory is corrupt, starting from scratch"); + if (intention == UpdaterIntention.Install) { + this.Log().Info("First run, starting from scratch"); + } else { + this.Log().Warn("No local releases found, starting from scratch"); + } + return UpdateInfo.Create(null, new[] {latestFullRelease}, packageDirectory); } diff --git a/src/Squirrel/UpdateManager.cs b/src/Squirrel/UpdateManager.cs index 6e9d59f59..9f96e773e 100644 --- a/src/Squirrel/UpdateManager.cs +++ b/src/Squirrel/UpdateManager.cs @@ -49,12 +49,12 @@ public UpdateManager(string urlOrPath, this.rootAppDirectory = Path.Combine(rootDirectory ?? GetLocalAppDataDirectory(), this.applicationName); } - public async Task CheckForUpdate(bool ignoreDeltaUpdates = false, Action progress = null) + public async Task CheckForUpdate(bool ignoreDeltaUpdates = false, Action progress = null, UpdaterIntention intention = UpdaterIntention.Update) { var checkForUpdate = new CheckForUpdateImpl(rootAppDirectory); await acquireUpdateLock(); - return await checkForUpdate.CheckForUpdate(Utility.LocalReleaseFileForAppDir(rootAppDirectory), updateUrlOrPath, ignoreDeltaUpdates, progress, urlDownloader); + return await checkForUpdate.CheckForUpdate(intention, Utility.LocalReleaseFileForAppDir(rootAppDirectory), updateUrlOrPath, ignoreDeltaUpdates, progress, urlDownloader); } public async Task DownloadReleases(IEnumerable releasesToDownload, Action progress = null) @@ -75,7 +75,7 @@ public async Task ApplyReleases(UpdateInfo updateInfo, Action progr public async Task FullInstall(bool silentInstall = false, Action progress = null) { - var updateInfo = await CheckForUpdate(); + var updateInfo = await CheckForUpdate(intention: UpdaterIntention.Install); await DownloadReleases(updateInfo.ReleasesToApply); var applyReleases = new ApplyReleasesImpl(rootAppDirectory); diff --git a/src/Update/Program.cs b/src/Update/Program.cs index 6c23be6e1..d9a0af72b 100644 --- a/src/Update/Program.cs +++ b/src/Update/Program.cs @@ -1,4 +1,7 @@ -using System; +using NuGet; +using Splat; +using Squirrel.Json; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -7,13 +10,9 @@ using System.Linq; using System.Reflection; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using Mono.Options; -using Splat; -using Squirrel.Json; -using NuGet; -using System.Text.RegularExpressions; namespace Squirrel.Update { @@ -24,7 +23,7 @@ enum UpdateAction { class Program : IEnableLogger { - static OptionSet opts; + static StartupOption opt; public static int Main(string[] args) { @@ -41,17 +40,27 @@ public static int Main(string[] args) int main(string[] args) { + try { + opt = new StartupOption(args); + } catch (Exception ex) { + using (var logger = new SetupLogLogger(true, "OptionParsing") { Level = LogLevel.Info }) { + Locator.CurrentMutable.Register(() => logger, typeof(Splat.ILogger)); + logger.Write($"Failed to parse command line options. {ex.Message}", LogLevel.Error); + } + throw; + } + // NB: Trying to delete the app directory while we have Setup.log // open will actually crash the uninstaller - bool isUninstalling = args.Any(x => x.Contains("uninstall")); + bool isUninstalling = opt.updateAction == UpdateAction.Uninstall; - using (var logger = new SetupLogLogger(isUninstalling) {Level = LogLevel.Info}) { + using (var logger = new SetupLogLogger(isUninstalling, opt.updateAction.ToString()) {Level = LogLevel.Info}) { Locator.CurrentMutable.Register(() => logger, typeof (Splat.ILogger)); try { return executeCommandLine(args); } catch (Exception ex) { - logger.Write("Unhandled exception: " + ex, LogLevel.Fatal); + logger.Write("Finished with unhandled exception: " + ex, LogLevel.Fatal); throw; } } @@ -81,116 +90,53 @@ int executeCommandLine(string[] args) return 0; } - bool silentInstall = false; - var updateAction = default(UpdateAction); - - string target = default(string); - string releaseDir = default(string); - string packagesDir = default(string); - string bootstrapperExe = default(string); - string backgroundGif = default(string); - string signingParameters = default(string); - string baseUrl = default(string); - string processStart = default(string); - string processStartArgs = default(string); - string setupIcon = default(string); - string icon = default(string); - string shortcutArgs = default(string); - string frameworkVersion = "net45"; - bool shouldWait = false; - bool noMsi = (Environment.OSVersion.Platform != PlatformID.Win32NT); // NB: WiX doesn't work under Mono / Wine - bool noDelta = false; - bool packageAs64Bit = false; - - opts = new OptionSet() { - "Usage: Squirrel.exe command [OPTS]", - "Manages Squirrel packages", - "", - "Commands", - { "install=", "Install the app whose package is in the specified directory", v => { updateAction = UpdateAction.Install; target = v; } }, - { "uninstall", "Uninstall the app the same dir as Update.exe", v => updateAction = UpdateAction.Uninstall}, - { "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; } }, - { "processStart=", "Start an executable in the latest version of the app package", v => { updateAction = UpdateAction.ProcessStart; processStart = v; }, true}, - { "processStartAndWait=", "Start an executable in the latest version of the app package", v => { updateAction = UpdateAction.ProcessStart; processStart = v; shouldWait = true; }, true}, - "", - "Options:", - { "h|?|help", "Display Help and exit", _ => {} }, - { "r=|releaseDir=", "Path to a release directory to use with releasify", v => releaseDir = v}, - { "p=|packagesDir=", "Path to the NuGet Packages directory for C# apps", v => packagesDir = v}, - { "bootstrapperExe=", "Path to the Setup.exe to use as a template", v => bootstrapperExe = v}, - { "g=|loadingGif=", "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}, - { "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}, - }; - - 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; - - if (updateAction == UpdateAction.Unset) { + if (opt.updateAction == UpdateAction.Unset) { ShowHelp(); return -1; } - switch (updateAction) { + switch (opt.updateAction) { #if !MONO case UpdateAction.Install: var progressSource = new ProgressSource(); - if (!silentInstall) { + if (!opt.silentInstall) { AnimatedGifWindow.ShowWindow(TimeSpan.FromSeconds(4), animatedGifWindowToken.Token, progressSource); } - Install(silentInstall, progressSource, Path.GetFullPath(target)).Wait(); + Install(opt.silentInstall, progressSource, Path.GetFullPath(opt.target)).Wait(); animatedGifWindowToken.Cancel(); break; case UpdateAction.Uninstall: Uninstall().Wait(); break; case UpdateAction.Download: - Console.WriteLine(Download(target).Result); + Console.WriteLine(Download(opt.target).Result); break; case UpdateAction.Update: - Update(target).Wait(); + Update(opt.target).Wait(); break; case UpdateAction.CheckForUpdate: - Console.WriteLine(CheckForUpdate(target).Result); + Console.WriteLine(CheckForUpdate(opt.target).Result); break; case UpdateAction.UpdateSelf: UpdateSelf().Wait(); break; case UpdateAction.Shortcut: - Shortcut(target, shortcutArgs, processStartArgs, setupIcon); + Shortcut(opt.target, opt.shortcutArgs, opt.processStartArgs, opt.setupIcon); break; case UpdateAction.Deshortcut: - Deshortcut(target, shortcutArgs); + Deshortcut(opt.target, opt.shortcutArgs); break; case UpdateAction.ProcessStart: - ProcessStart(processStart, processStartArgs, shouldWait); + ProcessStart(opt.processStart, opt.processStartArgs, opt.shouldWait); break; #endif case UpdateAction.Releasify: - Releasify(target, releaseDir, packagesDir, bootstrapperExe, backgroundGif, signingParameters, baseUrl, setupIcon, !noMsi, packageAs64Bit, frameworkVersion, !noDelta); + Releasify(opt.target, opt.releaseDir, opt.packagesDir, 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"); return 0; } @@ -253,7 +199,7 @@ public async Task Update(string updateUrl, string appName = null) retry: try { - var updateInfo = await mgr.CheckForUpdate(ignoreDeltaUpdates: ignoreDeltaUpdates, progress: x => Console.WriteLine(x / 3)); + var updateInfo = await mgr.CheckForUpdate(intention: UpdaterIntention.Update, ignoreDeltaUpdates: ignoreDeltaUpdates, progress: x => Console.WriteLine(x / 3)); await mgr.DownloadReleases(updateInfo.ReleasesToApply, x => Console.WriteLine(33 + x / 3)); await mgr.ApplyReleases(updateInfo, x => Console.WriteLine(66 + x / 3)); } catch (Exception ex) { @@ -294,7 +240,7 @@ public async Task Download(string updateUrl, string appName = null) this.Log().Info("Fetching update information, downloading from " + updateUrl); using (var mgr = new UpdateManager(updateUrl, appName)) { - var updateInfo = await mgr.CheckForUpdate(progress: x => Console.WriteLine(x / 3)); + 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)); var releaseNotes = updateInfo.FetchReleaseNotes(); @@ -318,7 +264,7 @@ public async Task CheckForUpdate(string updateUrl, string appName = null this.Log().Info("Fetching update information, downloading from " + updateUrl); using (var mgr = new UpdateManager(updateUrl, appName)) { - var updateInfo = await mgr.CheckForUpdate(progress: x => Console.WriteLine(x)); + var updateInfo = await mgr.CheckForUpdate(intention: UpdaterIntention.Update, progress: x => Console.WriteLine(x)); var releaseNotes = updateInfo.FetchReleaseNotes(); var sanitizedUpdateInfo = new { @@ -561,7 +507,7 @@ public void ProcessStart(string exeName, string arguments, bool shouldWait) public void ShowHelp() { ensureConsole(); - opts.WriteOptionDescriptions(Console.Out); + opt.WriteOptionDescriptions(); } void waitForParentToExit() @@ -865,16 +811,16 @@ class SetupLogLogger : Splat.ILogger, IDisposable readonly object gate = 42; public Splat.LogLevel Level { get; set; } - public SetupLogLogger(bool saveInTemp) + public SetupLogLogger(bool saveInTemp, string commandSuffix = null) { for (int i=0; i < 10; i++) { try { var dir = saveInTemp ? Path.GetTempPath() : Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); - - var file = Path.Combine(dir, String.Format("SquirrelSetup.{0}.log", i).Replace(".0.log", ".log")); - var str = File.Open(file, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); + var fileName = commandSuffix == null ? String.Format($"Squirrel.{i}.log", i) : String.Format($"Squirrel-{commandSuffix}.{i}.log", i); + var file = Path.Combine(dir, fileName.Replace(".0.log", ".log")); + var str = File.Open(file, FileMode.Append, FileAccess.Write, FileShare.Read); inner = new StreamWriter(str, Encoding.UTF8, 4096, false) { AutoFlush = true }; return; } catch (Exception ex) { @@ -892,7 +838,7 @@ public void Write(string message, LogLevel logLevel) return; } - lock (gate) inner.WriteLine("{0}> {1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), message); + lock (gate) inner.WriteLine($"[{DateTime.Now.ToString("dd/MM/yy HH:mm:ss")}] {logLevel.ToString().ToLower()}: {message}"); } public void Dispose() diff --git a/src/Update/StartupOption.cs b/src/Update/StartupOption.cs new file mode 100644 index 000000000..379c1ac17 --- /dev/null +++ b/src/Update/StartupOption.cs @@ -0,0 +1,85 @@ +using Mono.Options; +using System; + +namespace Squirrel.Update +{ + 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 packagesDir { 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 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; } = "net45"; + 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; + + public StartupOption(string[] args) { + optionSet = Parse(args); + } + + private OptionSet Parse(string[] args) { + var opts = new OptionSet() { + "Usage: Squirrel.exe command [OPTS]", + "Manages Squirrel packages", + "", + "Commands", + { "install=", "Install the app whose package is in the specified directory", v => { updateAction = UpdateAction.Install; target = v; } }, + { "uninstall", "Uninstall the app the same dir as Update.exe", v => updateAction = UpdateAction.Uninstall}, + { "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; } }, + { "processStart=", "Start an executable in the latest version of the app package", v => { updateAction = UpdateAction.ProcessStart; processStart = v; }, true}, + { "processStartAndWait=", "Start an executable in the latest version of the app package", v => { updateAction = UpdateAction.ProcessStart; processStart = v; shouldWait = true; }, true}, + "", + "Options:", + { "h|?|help", "Display Help and exit", _ => {} }, + { "r=|releaseDir=", "Path to a release directory to use with releasify", v => releaseDir = v}, + { "p=|packagesDir=", "Path to the NuGet Packages directory for C# apps", v => packagesDir = v}, + { "bootstrapperExe=", "Path to the Setup.exe to use as a template", v => bootstrapperExe = v}, + { "g=|loadingGif=", "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}, + { "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}, + }; + + 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; + } + + internal void WriteOptionDescriptions() { + optionSet.WriteOptionDescriptions(Console.Out); + } + } +} + diff --git a/src/Update/Update-Mono.csproj b/src/Update/Update-Mono.csproj index f26ae6c3d..f7330bc2b 100644 --- a/src/Update/Update-Mono.csproj +++ b/src/Update/Update-Mono.csproj @@ -82,6 +82,7 @@ + diff --git a/src/Update/Update.csproj b/src/Update/Update.csproj index 788bdab97..fe176f29c 100644 --- a/src/Update/Update.csproj +++ b/src/Update/Update.csproj @@ -83,6 +83,7 @@ + @@ -130,4 +131,4 @@ ren "$(TargetFileName).tmp" "$(TargetFileName)" --> - + \ No newline at end of file