diff --git a/src/Rust/src/commands/apply.rs b/src/Rust/src/commands/apply.rs index 8380367cd..797dba6b1 100644 --- a/src/Rust/src/commands/apply.rs +++ b/src/Rust/src/commands/apply.rs @@ -21,7 +21,7 @@ pub fn apply<'a>(restart: bool, wait_for_parent: bool, package: Option<&PathBuf> } if restart { - shared::start_package(&app, &root_path, exe_args)?; + shared::start_package(&app, &root_path, exe_args, Some("CLOWD_SQUIRREL_RESTART"))?; } Ok(()) @@ -79,7 +79,7 @@ fn apply_package<'a>(package: Option<&PathBuf>, app: &Manifest, root_path: &Path info!("Applying package to current: {}", found_version); #[cfg(target_os = "windows")] - crate::windows::run_hook(&app, &root_path, "--squirrel-obsoleted", 15); + crate::windows::run_hook(&app, &root_path, "--squirrel-obsolete", 15); let current_dir = app.get_current_path(&root_path); shared::replace_dir_with_rollback(current_dir.clone(), || { diff --git a/src/Rust/src/commands/start.rs b/src/Rust/src/commands/start.rs index 0f8714e35..6546ae60c 100644 --- a/src/Rust/src/commands/start.rs +++ b/src/Rust/src/commands/start.rs @@ -30,11 +30,11 @@ pub fn start(wait_for_parent: bool, exe_name: Option<&String>, exe_args: Option< info!("About to launch: '{}' in dir '{}'", exe_to_execute.to_string_lossy(), current); if let Some(args) = exe_args { - crate::shared::run_process(exe_to_execute, args, current)?; + crate::windows::run_process(exe_to_execute, args, current)?; } else if let Some(args) = legacy_args { crate::windows::run_process_raw_args(exe_to_execute, args, current)?; } else { - crate::shared::run_process(exe_to_execute, vec![], current)?; + crate::windows::run_process(exe_to_execute, vec![], current)?; }; Ok(()) diff --git a/src/Rust/src/setup.rs b/src/Rust/src/setup.rs index adb6f9011..43031ed08 100644 --- a/src/Rust/src/setup.rs +++ b/src/Rust/src/setup.rs @@ -254,9 +254,8 @@ fn install_app(pkg: &bundle::BundleInfo, root_path: &PathBuf, tx: &std::sync::mp app.write_uninstall_entry(root_path)?; if !dialogs::get_silent() { - info!("Starting app: \"{}\" --squirrel-firstrun", main_exe_path); - let args = vec!["--squirrel-firstrun"]; - let _ = shared::run_process(&main_exe_path, args, ¤t_path); + info!("Starting app..."); + shared::start_package(&app, &root_path, None, Some("CLOWD_SQUIRREL_FIRSTRUN"))?; } Ok(()) diff --git a/src/Rust/src/shared/util_common.rs b/src/Rust/src/shared/util_common.rs index de24c73c6..53f8b3d30 100644 --- a/src/Rust/src/shared/util_common.rs +++ b/src/Rust/src/shared/util_common.rs @@ -2,11 +2,9 @@ use anyhow::{anyhow, bail, Result}; use rand::distributions::{Alphanumeric, DistString}; use regex::Regex; use std::{ - ffi::OsStr, fs, io::{self}, path::{Path, PathBuf}, - process::Command as Process, thread, time::Duration, }; @@ -62,15 +60,6 @@ where } } -pub fn run_process(exe: S, args: Vec<&str>, work_dir: P) -> Result<()> -where - S: AsRef, - P: AsRef, -{ - Process::new(exe).args(args).current_dir(work_dir).spawn()?; - Ok(()) -} - pub fn retry_io(op: F) -> io::Result where F: Fn() -> io::Result, diff --git a/src/Rust/src/shared/util_osx.rs b/src/Rust/src/shared/util_osx.rs index fc5dd370c..5a0484e05 100644 --- a/src/Rust/src/shared/util_osx.rs +++ b/src/Rust/src/shared/util_osx.rs @@ -25,21 +25,24 @@ pub fn force_stop_package>(root_dir: P) -> Result<()> { Ok(()) } -pub fn start_package>(_app: &Manifest, root_dir: P, exe_args: Option>) -> Result<()> { +pub fn start_package>(_app: &Manifest, root_dir: P, exe_args: Option>, set_env: Option<&str>) -> Result<()> { let root_dir = root_dir.as_ref().to_string_lossy().to_string(); let mut args = vec!["-n", &root_dir]; if let Some(a) = exe_args { args.push("--args"); args.extend(a); } - Process::new("/usr/bin/open").args(args).spawn().map_err(|z| anyhow!("Failed to start application ({}).", z))?; + let mut psi = Process::new("/usr/bin/open").args(args); + if let Some(env) = set_env { + psi.env(env, "true"); + } + psi.spawn().map_err(|z| anyhow!("Failed to start application ({}).", z))?; Ok(()) } #[test] #[ignore] -fn test_start_and_stop_package() -{ +fn test_start_and_stop_package() { let mani = Manifest::default(); let root_dir = "/Applications/Calcbot.app"; let _ = force_stop_package(root_dir); @@ -59,4 +62,4 @@ fn test_start_and_stop_package() force_stop_package(root_dir).unwrap(); std::thread::sleep(Duration::from_secs(1)); assert!(!is_running()); -} \ No newline at end of file +} diff --git a/src/Rust/src/shared/util_windows.rs b/src/Rust/src/shared/util_windows.rs index 992685366..7a61a0f83 100644 --- a/src/Rust/src/shared/util_windows.rs +++ b/src/Rust/src/shared/util_windows.rs @@ -2,6 +2,7 @@ use anyhow::{anyhow, bail, Result}; use std::{ collections::HashMap, path::{Path, PathBuf}, + process::Command as Process, }; use winsafe::{self as w, co, prelude::*}; @@ -118,7 +119,7 @@ pub fn force_stop_package>(root_dir: P) -> Result<()> { Ok(()) } -pub fn start_package>(app: &Manifest, root_dir: P, exe_args: Option>) -> Result<()> { +pub fn start_package>(app: &Manifest, root_dir: P, exe_args: Option>, set_env: Option<&str>) -> Result<()> { let root_dir = root_dir.as_ref().to_path_buf(); let current = app.get_current_path(&root_dir); let exe = app.get_main_exe_path(&root_dir); @@ -130,11 +131,19 @@ pub fn start_package>(app: &Manifest, root_dir: P, exe_args: Opti crate::windows::assert_can_run_binary_authenticode(&exe_to_execute)?; + let mut psi = Process::new(&exe_to_execute); + psi.current_dir(¤t); if let Some(args) = exe_args { - super::run_process(exe_to_execute, args, current)?; - } else { - crate::shared::run_process(exe_to_execute, vec![], current)?; - }; + psi.args(args); + } + if let Some(env) = set_env { + debug!("Setting environment variable: {}={}", env, "true"); + psi.env(env, "true"); + } + + info!("About to launch: '{}' in dir '{}'", exe_to_execute.to_string_lossy(), current); + info!("Args: {:?}", psi.get_args()); + psi.spawn().map_err(|z| anyhow!("Failed to start application ({}).", z))?; Ok(()) } diff --git a/src/Rust/src/windows/util.rs b/src/Rust/src/windows/util.rs index ad3d2f231..9b7fd0601 100644 --- a/src/Rust/src/windows/util.rs +++ b/src/Rust/src/windows/util.rs @@ -255,6 +255,15 @@ where } } +pub fn run_process(exe: S, args: Vec<&str>, work_dir: P) -> Result<()> +where + S: AsRef, + P: AsRef, +{ + Process::new(exe).args(args).current_dir(work_dir).spawn()?; + Ok(()) +} + pub fn run_process_no_console(exe: S, args: Vec<&str>, work_dir: P) -> Result<()> where S: AsRef, diff --git a/src/Squirrel/Locators/ISquirrelLocator.cs b/src/Squirrel/Locators/ISquirrelLocator.cs index a3d36846b..b0906911c 100644 --- a/src/Squirrel/Locators/ISquirrelLocator.cs +++ b/src/Squirrel/Locators/ISquirrelLocator.cs @@ -44,7 +44,7 @@ public interface ISquirrelLocator /// /// Finds latest .nupkg file in the PackagesDir or null if not found. /// - public ReleaseEntry GetLatestLocalPackage(); + public ReleaseEntry GetLatestLocalFullPackage(); /// /// Unique identifier for this user which is used to calculate whether this user is eligible for diff --git a/src/Squirrel/Locators/SquirrelLocator.cs b/src/Squirrel/Locators/SquirrelLocator.cs index 449c52fc7..4392e40b8 100644 --- a/src/Squirrel/Locators/SquirrelLocator.cs +++ b/src/Squirrel/Locators/SquirrelLocator.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using NuGet.Versioning; namespace Squirrel.Locators @@ -21,14 +22,16 @@ public abstract class SquirrelLocator : ISquirrelLocator /// public static SquirrelLocator GetDefault(ILogger logger) { + var log = logger ?? NullLogger.Instance; + if (_current != null) return _current; if (SquirrelRuntimeInfo.IsWindows) - return _current ??= new WindowsSquirrelLocator(logger); + return _current ??= new WindowsSquirrelLocator(log); if (SquirrelRuntimeInfo.IsOSX) - return _current ??= new OsxSquirrelLocator(logger); + return _current ??= new OsxSquirrelLocator(log); throw new NotSupportedException($"OS platform '{SquirrelRuntimeInfo.SystemOs.GetOsLongName()}' is not supported."); } @@ -78,6 +81,9 @@ protected SquirrelLocator(ILogger logger) /// public virtual List GetLocalPackages() { + if (CurrentlyInstalledVersion == null) + return new List(0); + return Directory.EnumerateFiles(PackagesDir, "*.nupkg") .Select(x => ReleaseEntry.GenerateFromFile(x)) .Where(x => x?.Version != null) @@ -85,10 +91,11 @@ public virtual List GetLocalPackages() } /// - public ReleaseEntry GetLatestLocalPackage() + public ReleaseEntry GetLatestLocalFullPackage() { return GetLocalPackages() .OrderByDescending(x => x.Version) + .Where(x => !x.IsDelta) .FirstOrDefault(); } diff --git a/src/Squirrel/SquirrelApp.cs b/src/Squirrel/SquirrelApp.cs new file mode 100644 index 000000000..83c720e5a --- /dev/null +++ b/src/Squirrel/SquirrelApp.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Versioning; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using NuGet.Versioning; +using Squirrel.Locators; + +namespace Squirrel +{ + /// + /// A delegate type for handling Squirrel startup events + /// + /// The currently executing version of this application + public delegate void SquirrelHook(SemanticVersion version); + + /// + /// SquirrelApp helps you to handle Squirrel app activation events correctly. + /// This should be used as early as possible in your application startup code. + /// (eg. the beginning of Main() in Program.cs) + /// + public sealed class SquirrelApp + { + ISquirrelLocator _locator; + SquirrelHook _install; + SquirrelHook _update; + SquirrelHook _obsolete; + SquirrelHook _uninstall; + SquirrelHook _firstrun; + SquirrelHook _restarted; + string[] _args; + bool _autoApply = true; + + private SquirrelApp() + { + } + + /// + /// Creates and returns a new Squirrel application builder. + /// + public static SquirrelApp Build() => new SquirrelApp(); + + /// + /// Override the command line arguments used to determine the Squirrel hook to run. + /// If this is not set, the command line arguments passed to the application will be used. + /// + public SquirrelApp SetArgs(string[] args) + { + _args = args; + return this; + } + + /// + /// Set whether to automatically apply downloaded updates on startup. This is ON by default. + /// + public SquirrelApp SetAutoApplyOnStartup(bool autoApply) + { + _autoApply = autoApply; + return this; + } + + /// + /// Override the default used to search for application paths. + /// + public SquirrelApp SetLocator(ISquirrelLocator locator) + { + _locator = locator; + return this; + } + + /// + /// This hook is triggered when the application is started for the first time after installation. + /// + public SquirrelApp WithFirstRun(SquirrelHook hook) + { + _firstrun = hook; + return this; + } + + /// + /// This hook is triggered when the application is restarted by Squirrel after installing updates. + /// + public SquirrelApp WithRestarted(SquirrelHook hook) + { + _restarted = hook; + return this; + } + + /// + /// WARNING: FastCallback hooks are run during critical stages of Squirrel operations. + /// Your code will be run and then will be called. + /// If your code has not completed within 30 seconds, it will be terminated. + /// Only supported on windows; On other operating systems, this will never be called. + /// + [SupportedOSPlatform("windows")] + public SquirrelApp WithAfterInstallFastCallback(SquirrelHook hook) + { + _install = hook; + return this; + } + + /// + /// WARNING: FastCallback hooks are run during critical stages of Squirrel operations. + /// Your code will be run and then will be called. + /// If your code has not completed within 15 seconds, it will be terminated. + /// Only supported on windows; On other operating systems, this will never be called. + /// + [SupportedOSPlatform("windows")] + public SquirrelApp WithAfterUpdateFastCallback(SquirrelHook hook) + { + _update = hook; + return this; + } + + /// + /// WARNING: FastCallback hooks are run during critical stages of Squirrel operations. + /// Your code will be run and then will be called. + /// If your code has not completed within 15 seconds, it will be terminated. + /// Only supported on windows; On other operating systems, this will never be called. + /// + [SupportedOSPlatform("windows")] + public SquirrelApp WithBeforeUpdateFastCallback(SquirrelHook hook) + { + _obsolete = hook; + return this; + } + + /// + /// WARNING: FastCallback hooks are run during critical stages of Squirrel operations. + /// Your code will be run and then will be called. + /// If your code has not completed within 30 seconds, it will be terminated. + /// Only supported on windows; On other operating systems, this will never be called. + /// + [SupportedOSPlatform("windows")] + public SquirrelApp WithBeforeUninstallFastCallback(SquirrelHook hook) + { + _uninstall = hook; + return this; + } + + /// + /// Runs the Squirrel application startup code and triggers any configured hooks. + /// + /// A logging interface for diagnostic messages. + public void Run(ILogger logger = null) + { + var args = _args ?? Environment.GetCommandLineArgs().Skip(1).ToArray(); + var log = logger ?? NullLogger.Instance; + var locator = _locator ?? SquirrelLocator.GetDefault(log); + + // internal hook run by the Squirrel tooling to check everything is working + if (args.Length >= 1 && args[0].Equals("--squirrel-version", StringComparison.OrdinalIgnoreCase)) { + Console.WriteLine(SquirrelRuntimeInfo.SquirrelNugetVersion); + Environment.Exit(0); + } + + log.Info("Starting Squirrel App (Run)."); + + // first, we run any fast exit hooks + SquirrelHook defaultBlock = ((v) => { }); + var fastExitlookup = new[] { + new { Key = "--squirrel-install", Value = _install ?? defaultBlock }, + new { Key = "--squirrel-updated", Value = _update ?? defaultBlock }, + new { Key = "--squirrel-obsolete", Value = _obsolete ?? defaultBlock }, + new { Key = "--squirrel-uninstall", Value = _uninstall ?? defaultBlock }, + }.ToDictionary(k => k.Key, v => v.Value, StringComparer.OrdinalIgnoreCase); + if (args.Length >= 2 && fastExitlookup.ContainsKey(args[0])) { + try { + log.Info("Found fast exit hook: " + args[0]); + var version = SemanticVersion.Parse(args[1]); + fastExitlookup[args[0]](version); + log.Info("Completed hook, exiting..."); + Environment.Exit(0); + } catch (Exception ex) { + log.Error(ex, $"Error occurred executing user defined Squirrel hook. ({args[0]})"); + Environment.Exit(-1); + } + } + + // some initial setup/state + var myVersion = locator.CurrentlyInstalledVersion; + var firstrun = !String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CLOWD_SQUIRREL_FIRSTRUN")); + var restarted = !String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("CLOWD_SQUIRREL_RESTART")); + var localPackages = locator.GetLocalPackages(); + var latestLocal = locator.GetLatestLocalFullPackage(); + + // if we've not just been restarted via Squirrel apply, and there is a local update available, + // we should install it first. + if (latestLocal != null && latestLocal.Version > myVersion) { + log.Info($"Launching app is out-dated. Current: {myVersion}, Newest Local Available: {latestLocal.Version}"); + if (!restarted && _autoApply) { + log.Info("Auto apply is true, so restarting to apply update..."); + var um = new UpdateManager(log, locator); + um.ApplyUpdatesAndRestart(args); + } + } + + // clean up old versions of the app + var pkgPath = locator.PackagesDir; + foreach (var package in localPackages) { + if (package.Version == latestLocal.Version || package.Version == myVersion) { + continue; + } + try { + log.Info("Removing old package: " + package.OriginalFilename); + var p = Path.Combine(pkgPath, package.OriginalFilename); + File.Delete(p); + } catch (Exception ex) { + log.Error(ex, $"Failed to remove old package '{package.OriginalFilename}'"); + } + } + + // run non-exiting user hooks + if (firstrun) { + try { + _firstrun(myVersion); + } catch (Exception ex) { + log.Error(ex, $"Error occurred executing user defined Squirrel hook. (firstrun)"); + } + } + if (restarted) { + try { + _restarted(myVersion); + } catch (Exception ex) { + log.Error(ex, $"Error occurred executing user defined Squirrel hook. (restarted)"); + } + } + } + } +} diff --git a/src/Squirrel/UpdateManager.cs b/src/Squirrel/UpdateManager.cs index 1030568b5..b5f559e6b 100644 --- a/src/Squirrel/UpdateManager.cs +++ b/src/Squirrel/UpdateManager.cs @@ -29,7 +29,7 @@ public class UpdateManager /// True if there is a local update prepared that requires a call to to be applied. public virtual bool IsUpdatePendingRestart { get { - var latestLocal = Locator.GetLatestLocalPackage(); + var latestLocal = Locator.GetLatestLocalFullPackage(); if (latestLocal != null && latestLocal.Version > CurrentVersion) return true; return false; @@ -70,11 +70,16 @@ public UpdateManager(string urlOrPath, string channel = null, ILogger logger = n /// This should usually be left null. Providing an allows you to mock up certain application paths. /// For example, if you wanted to test that updates are working in a unit test, you could provide an instance of . public UpdateManager(IUpdateSource source, ILogger logger = null, ISquirrelLocator locator = null) + : this(logger, locator) { if (source == null) { throw new ArgumentNullException(nameof(source)); } Source = source; + } + + internal UpdateManager(ILogger logger, ISquirrelLocator locator) + { Log = logger ?? NullLogger.Instance; Locator = locator ?? SquirrelLocator.GetDefault(Log); } @@ -97,7 +102,7 @@ public virtual async Task CheckForUpdatesAsync(CancellationToken can EnsureInstalled(); var installedVer = CurrentVersion; var betaId = Locator.GetOrCreateStagedUserId(); - var latestLocalFull = Locator.GetLatestLocalPackage(); + var latestLocalFull = Locator.GetLatestLocalFullPackage(); Log.Debug("Retrieving latest release feed."); var feed = await Source.GetReleaseFeed(betaId, latestLocalFull?.Identity).ConfigureAwait(false); diff --git a/test/Squirrel.Packaging.Tests/WindowsPackTests.cs b/test/Squirrel.Packaging.Tests/WindowsPackTests.cs index 54f0fba8c..b6c41fd59 100644 --- a/test/Squirrel.Packaging.Tests/WindowsPackTests.cs +++ b/test/Squirrel.Packaging.Tests/WindowsPackTests.cs @@ -7,6 +7,7 @@ using System.Runtime.Versioning; using System.Text; using System.Xml.Linq; +using Microsoft.Extensions.Logging; using Microsoft.Win32; using NuGet.Packaging; using Squirrel.Compression; @@ -265,6 +266,55 @@ public void PackBuildsPackageWhichIsInstallable() } } + [SkippableFact] + public void TestAllApplicationHooks() + { + Skip.IfNot(SquirrelRuntimeInfo.IsWindows); + using var logger = _output.BuildLoggerFor(); + using var _1 = Utility.GetTempDirectory(out var releaseDir); + using var _2 = Utility.GetTempDirectory(out var installDir); + string id = "SquirrelHookTest"; + var appPath = Path.Combine(installDir, "current", "TestApp.exe"); + + // pack v1 + PackTestApp(id, "1.0.0", "version 1 test", releaseDir, logger); + + // install app + var setupPath1 = Path.Combine(releaseDir, $"{id}-Setup-[win-x64].exe"); + RunNoCoverage(setupPath1, new string[] { "--nocolor", "--verbose", "--installto", installDir }, + Environment.GetFolderPath(Environment.SpecialFolder.Desktop), logger); + + var argsPath = Path.Combine(installDir, "args.txt"); + Assert.True(File.Exists(argsPath)); + Assert.Equal("--squirrel-install 1.0.0", File.ReadAllText(argsPath).Trim()); + + var firstRun = Path.Combine(installDir, "firstrun"); + Assert.True(File.Exists(argsPath)); + Assert.Equal("1.0.0", File.ReadAllText(firstRun).Trim()); + + // pack v2 + PackTestApp(id, "2.0.0", "version 2 test", releaseDir, logger); + + // install v2 + RunCoveredDotnet(appPath, new string[] { "download", releaseDir }, installDir, logger); + RunCoveredDotnet(appPath, new string[] { "apply", releaseDir }, installDir, logger, exitCode: null); + + Thread.Sleep(2000); + + var logFile = Path.Combine(installDir, "Clowd.Squirrel.log"); + logger.Info("TEST: update log output - " + Environment.NewLine + File.ReadAllText(logFile)); + + Assert.Contains("--squirrel-obsolete 1.0.0", File.ReadAllText(argsPath).Trim()); + Assert.Contains("--squirrel-updated 2.0.0", File.ReadAllText(argsPath).Trim()); + + var restartedPath = Path.Combine(installDir, "restarted"); + Assert.True(File.Exists(restartedPath)); + Assert.Equal("2.0.0,test,args !!", File.ReadAllText(restartedPath).Trim()); + + var updatePath = Path.Combine(installDir, "Update.exe"); + RunNoCoverage(updatePath, new string[] { "--nocolor", "--silent", "--uninstall" }, Environment.CurrentDirectory, logger); + } + [SkippableFact] public void TestPackedAppCanDeltaUpdateToLatest() { @@ -280,7 +330,8 @@ public void TestPackedAppCanDeltaUpdateToLatest() // install app var setupPath1 = Path.Combine(releaseDir, $"{id}-Setup-[win-x64].exe"); - RunNoCoverage(setupPath1, new string[] { "--nocolor", "--silent", "--installto", installDir }, Environment.GetFolderPath(Environment.SpecialFolder.Desktop), logger); + RunNoCoverage(setupPath1, new string[] { "--nocolor", "--silent", "--installto", installDir }, + Environment.GetFolderPath(Environment.SpecialFolder.Desktop), logger); // check app installed correctly var appPath = Path.Combine(installDir, "current", "TestApp.exe"); @@ -335,7 +386,7 @@ public void TestPackedAppCanDeltaUpdateToLatest() // check new obsoleted/updated hooks have run var argsContentv3 = File.ReadAllText(argsPath).Trim(); Assert.Contains("--squirrel-install 1.0.0", argsContentv3); - Assert.Contains("--squirrel-obsoleted 1.0.0", argsContentv3); + Assert.Contains("--squirrel-obsolete 1.0.0", argsContentv3); Assert.Contains("--squirrel-updated 3.0.0", argsContentv3); logger.Info("TEST: hooks verified"); diff --git a/test/TestApp/Program.cs b/test/TestApp/Program.cs index e7285d772..6c0d2312f 100644 --- a/test/TestApp/Program.cs +++ b/test/TestApp/Program.cs @@ -1,10 +1,30 @@ -using Squirrel; +#pragma warning disable CA1416 // Validate platform compatibility +using System.Diagnostics; +using System.Reflection.Metadata; +using Squirrel; using Squirrel.Locators; try { - if (args.Length >= 1 && args[0].StartsWith("--squirrel")) { - // squirrel hooks - File.AppendAllText(Path.Combine(AppContext.BaseDirectory, "..", "args.txt"), String.Join(" ", args) + Environment.NewLine); + bool shouldExit = false; + SquirrelApp.Build() + .SetAutoApplyOnStartup(false) + .WithFirstRun((v) => { + debugFile("firstrun", v.ToString()); + Console.WriteLine("was first run"); + shouldExit = true; + }) + .WithRestarted((v) => { + debugFile("restarted", v.ToString() + "," + String.Join(",", args)); + Console.WriteLine("app just restarted"); + shouldExit = true; + }) + .WithAfterInstallFastCallback((v) => debugFile("args.txt", String.Join(" ", args))) + .WithBeforeUpdateFastCallback((v) => debugFile("args.txt", String.Join(" ", args))) + .WithAfterUpdateFastCallback((v) => debugFile("args.txt", String.Join(" ", args))) + .WithBeforeUninstallFastCallback((v) => debugFile("args.txt", String.Join(" ", args))) + .Run(new ConsoleLogger()); + + if (shouldExit) { return 0; } @@ -50,15 +70,22 @@ return -1; } Console.WriteLine("applying..."); - um.ApplyUpdatesAndExit(); + um.ApplyUpdatesAndRestart(new[] { "test", "args !!" }); return 0; } } } catch (Exception ex) { Console.WriteLine("exception: " + ex.ToString()); + if (Debugger.IsAttached) throw; return -1; } Console.WriteLine("Invalid args: " + String.Join(", ", args)); -return -1; \ No newline at end of file +return -1; + +void debugFile(string name, string message) +{ + var path = Path.Combine(AppContext.BaseDirectory, "..", name); + File.AppendAllText(path, message + Environment.NewLine); +}