public void UpdaterDeploymentWorks() { var path = Path.Combine(Path.GetTempPath(), "NAppUpdate-Tests"); NauIpc.ExtractUpdaterFromResource(path, "Foo.exe"); Assert.IsTrue(Directory.Exists(path)); Assert.IsTrue(File.Exists(Path.Combine(path, "Foo.exe"))); Assert.IsTrue(File.Exists(Path.Combine(path, "NAppUpdate.Framework.dll"))); // Cleanup test FileSystem.DeleteDirectory(path); }
public void ReinstateIfRestarted() { lock (UpdatesToApply) { var dto = NauIpc.ReadDto(Config.UpdateProcessName) as NauIpc.NauDto; if (dto != null) { Config = dto.Configs; UpdatesToApply = dto.Tasks; Logger = new Logger(dto.LogItems); State = UpdateProcessState.AfterRestart; } } }
public void UpdaterDeploymentAndIPCWorks() { var dto = new NauIpc.NauDto { Configs = UpdateManager.Instance.Config, Tasks = new List <IUpdateTask> { new FileUpdateTask { Description = "Task #1", ExecutionStatus = TaskExecutionStatus.RequiresAppRestart } }, AppPath = Process.GetCurrentProcess().MainModule.FileName, WorkingDirectory = Environment.CurrentDirectory, RelaunchApplication = false }; var path = dto.Configs.TempFolder; if (Directory.Exists(path)) { FileSystem.DeleteDirectory(path); } NauIpc.ExtractUpdaterFromResource(path, dto.Configs.UpdateExecutableName); var info = new ProcessStartInfo { UseShellExecute = true, WorkingDirectory = Environment.CurrentDirectory, FileName = Path.Combine(path, dto.Configs.UpdateExecutableName), Arguments = string.Format(@"""{0}"" -showConsole", dto.Configs.UpdateProcessName) }; var p = NauIpc.LaunchProcessAndSendDto(dto, info, dto.Configs.UpdateProcessName); Assert.IsNotNull(p); p.WaitForExit(); Assert.IsTrue(Directory.Exists(path)); Assert.IsTrue(File.Exists(Path.Combine(path, "Foo.exe"))); Assert.IsTrue(File.Exists(Path.Combine(path, "NAppUpdate.Framework.dll"))); // Cleanup test FileSystem.DeleteDirectory(path); }
public void ReinstateIfRestarted() { if (!IsAfterRestart()) { return; } lock (UpdatesToApply) { NauIpc.NauDto dto = NauIpc.ReadDto(Config.UpdateProcessName); if (dto == null) { return; } Config = dto.Configs; UpdatesToApply = dto.Tasks; Logger = new Logger(dto.LogItems); State = UpdateProcessState.AfterRestart; } }
private static void PerformUpdates() { string syncProcessName = _args.ProcessName; if (string.IsNullOrEmpty(syncProcessName)) { throw new ArgumentException("Required command line argument is missing", "ProcessName"); } Log("Update process name: '{0}'", syncProcessName); // Load extra assemblies to the app domain, if present var availableAssemblies = FileSystem.GetFiles(_workingDir, "*.exe|*.dll", SearchOption.TopDirectoryOnly); foreach (var assemblyPath in availableAssemblies) { Log("Loading {0}", assemblyPath); if (assemblyPath.Equals(Assembly.GetEntryAssembly().Location, StringComparison.InvariantCultureIgnoreCase) || assemblyPath.EndsWith("NAppUpdate.Framework.dll")) { Log("\tSkipping (part of current execution)"); continue; } try { // ReSharper disable UnusedVariable var assembly = Assembly.LoadFile(assemblyPath); // ReSharper restore UnusedVariable } catch (BadImageFormatException ex) { Log("\tSkipping due to an error: {0}", ex.Message); } } // Connect to the named pipe and retrieve the updates list _dto = NauIpc.ReadDto(syncProcessName); // Make sure we start updating only once the application has completely terminated Thread.Sleep(1000); // Let's even wait a bit bool createdNew; using (var mutex = new Mutex(false, syncProcessName + "Mutex", out createdNew)) { try { if (!createdNew) { mutex.WaitOne(); } } catch (AbandonedMutexException) { // An abandoned mutex is exactly what we are expecting... } finally { Log("The application has terminated (as expected)"); _appRunning = false; } } _logger.LogItems.InsertRange(0, _dto.LogItems); _dto.LogItems = _logger.LogItems; // Get some required environment variables string appPath = _dto.AppPath; string appDir = _dto.WorkingDirectory ?? Path.GetDirectoryName(appPath) ?? string.Empty; string relaunchAppArgs = _dto.Configs.RelaunchAppArgs; if (!string.IsNullOrEmpty(_dto.AppPath)) { _logFilePath = Path.Combine(Path.GetDirectoryName(_dto.AppPath), @"NauUpdate.log"); // now we can log to a more accessible location } if (_dto.Tasks == null) { throw new Exception("The Task list received in the dto is null"); } else if (_dto.Tasks.Count == 0) { throw new Exception("The Task list received in the dto is empty"); } Log("Got {0} task objects", _dto.Tasks.Count); // Perform the actual off-line update process foreach (var t in _dto.Tasks) { Log("Task \"{0}\": {1}", t.Description, t.ExecutionStatus); if (t.ExecutionStatus != TaskExecutionStatus.RequiresAppRestart && t.ExecutionStatus != TaskExecutionStatus.RequiresPrivilegedAppRestart) { Log("\tSkipping"); continue; } Exception exception = null; try { Log("\tExecuting..."); t.ExecutionStatus = t.Execute(true); } catch (Exception ex) { t.ExecutionStatus = TaskExecutionStatus.Failed; exception = ex; } if (t.ExecutionStatus != TaskExecutionStatus.Successful) { string taskFailedMessage = string.Format("Update failed, task execution failed, description: {0}, execution status: {1}", t.Description, t.ExecutionStatus); throw new Exception(taskFailedMessage, exception); } } Log("Finished successfully"); Log("Removing backup folder"); if (Directory.Exists(_dto.Configs.BackupFolder)) { FileSystem.DeleteDirectory(_dto.Configs.BackupFolder); } // Start the application only if requested to do so if (_dto.RelaunchApplication) { Log("Re-launching process {0} with working dir {1}", appPath, appDir); bool useShellExecute = !_args.ShowConsole; ProcessStartInfo info = new ProcessStartInfo { UseShellExecute = useShellExecute, WorkingDirectory = appDir, FileName = appPath, Arguments = "-nappupdate-afterrestart" + " " + relaunchAppArgs }; try { NauIpc.LaunchProcessAndSendDto(_dto, info, syncProcessName); _appRunning = true; } catch (Exception ex) { throw new UpdateProcessFailedException("Unable to relaunch application and/or send DTO", ex); } } }
private static void Main() { string tempFolder = string.Empty; string logFile = string.Empty; _args = ArgumentsParser.Get(); _logger = UpdateManager.Instance.Logger; _args.ParseCommandLineArgs(); if (_args.ShowConsole) { //_console = new ConsoleForm(); //_console.Show(); } Log("Starting to process cold updates..."); Log("Arguments parsed: {0}{1}.", Environment.NewLine, _args.DumpArgs()); var workingDir = Directory.GetCurrentDirectory(); if (_args.Log) { // Setup a temporary location for the log file, until we can get the DTO logFile = Path.Combine(workingDir, @"NauUpdate.log"); } try { // Get the update process name, to be used to create a named pipe and to wait on the application // to quit string syncProcessName = _args.ProcessName; if (string.IsNullOrEmpty(syncProcessName)) //Application.Exit(); { throw new ArgumentException("The command line needs to specify the mutex of the program to update.", "ar" + "gs"); } Log("Update process name: '{0}'", syncProcessName); //TODO: I suppose this was done to load custom tasks, however there were some problems when loading an assembly which later was updated (msg: can't update because file is in use). // // Load extra assemblies to the app domain, if present // Log("Getting files in : '{0}'", workingDir); // var availableAssemblies = FileSystem.GetFiles(workingDir, "*.exe|*.dll", SearchOption.TopDirectoryOnly); // foreach (var assemblyPath in availableAssemblies) { // Log("Loading {0}", assemblyPath); // if (assemblyPath.Equals(Assembly.GetEntryAssembly().Location, StringComparison.InvariantCultureIgnoreCase) || assemblyPath.EndsWith("NAppUpdate.Framework.dll")) { // Log("\tSkipping (part of current execution)"); // continue; // } // try { //// ReSharper disable UnusedVariable // var assembly = Assembly.LoadFile(assemblyPath); //// ReSharper restore UnusedVariable // } catch (BadImageFormatException ex) { // Log("\tSkipping due to an error: {0}", ex.Message); // } // } // Connect to the named pipe and retrieve the updates list var dto = NauIpc.ReadDto(syncProcessName) as NauIpc.NauDto; // Make sure we start updating only once the application has completely terminated Thread.Sleep(100); // hell, let's even wait a bit bool createdNew; using (var mutex = new Mutex(false, syncProcessName + "Mutex", out createdNew)) { try { if (!createdNew) { mutex.WaitOne(); } } catch (AbandonedMutexException) { // An abandoned mutex is exactly what we are expecting... } finally { Log("The application has terminated (as expected)"); } } bool updateSuccessful = true; if (dto == null || dto.Configs == null) { throw new Exception("Invalid DTO received"); } if (dto.LogItems != null) // shouldn't really happen { _logger.LogItems.InsertRange(0, dto.LogItems); } dto.LogItems = _logger.LogItems; // Get some required environment variables string appPath = dto.AppPath; string appDir = dto.WorkingDirectory ?? Path.GetDirectoryName(appPath) ?? string.Empty; tempFolder = dto.Configs.TempFolder; string backupFolder = dto.Configs.BackupFolder; bool relaunchApp = dto.RelaunchApplication; if (!string.IsNullOrEmpty(dto.AppPath)) { logFile = Path.Combine(Path.GetDirectoryName(dto.AppPath), @"NauUpdate.log"); // now we can log to a more accessible location } if (dto.Tasks == null || dto.Tasks.Count == 0) { throw new Exception("Could not find the updates list (or it was empty)."); } Log("Got {0} task objects", dto.Tasks.Count); //This can be handy if you're trying to debug the updater.exe! //#if (DEBUG) //{ // if (_args.ShowConsole) { // _console.WriteLine(); // _console.WriteLine("Pausing to attach debugger. Press any key to continue."); // _console.ReadKey(); // } //} //#endif // Perform the actual off-line update process foreach (var t in dto.Tasks) { Log("Task \"{0}\": {1}", t.Description, t.ExecutionStatus); if (t.ExecutionStatus != TaskExecutionStatus.RequiresAppRestart && t.ExecutionStatus != TaskExecutionStatus.RequiresPrivilegedAppRestart) { Log("\tSkipping"); continue; } Log("\tExecuting..."); // TODO: Better handling on failure: logging, rollbacks try { t.ExecutionStatus = t.Execute(true); } catch (Exception ex) { Log(ex); updateSuccessful = false; t.ExecutionStatus = TaskExecutionStatus.Failed; } if (t.ExecutionStatus == TaskExecutionStatus.Successful) { continue; } Log("\tTask execution failed"); updateSuccessful = false; break; } if (updateSuccessful) { Log("Finished successfully"); Log("Removing backup folder"); if (Directory.Exists(backupFolder)) { FileSystem.DeleteDirectory(backupFolder); } } else { Console.WriteLine("Update Failed"); //MessageBox.Show(); Log(Logger.SeverityLevel.Error, "Update failed"); } // Start the application only if requested to do so if (relaunchApp) { Log("Re-launching process {0} with working dir {1}", appPath, appDir); var info = new ProcessStartInfo { UseShellExecute = false, WorkingDirectory = appDir, FileName = appPath, }; var p = NauIpc.LaunchProcessAndSendDto(dto, info, syncProcessName); if (p == null) { throw new UpdateProcessFailedException("Unable to relaunch application and/or send DTO"); } } Log("All done"); //Application.Exit(); } catch (Exception ex) { // supressing catch because if at any point we get an error the update has failed Log(ex); } finally { if (_args.Log) { // at this stage we can't make any assumptions on correctness of the path FileSystem.CreateDirectoryStructure(logFile, true); _logger.Dump(logFile); } if (_args.ShowConsole) { if (_args.Log) { Console.WriteLine(); Console.WriteLine("Log file was saved to {0}", logFile); Console.WriteLine(); } Console.WriteLine(); Console.WriteLine("Exiting."); //Must not read console! //see: http://msdn.microsoft.com/en-us/library/system.console.aspx //Especially: //Console class members that work normally when the underlying stream is directed to a console might //throw an exception if the stream is redirected, for example, to a file. Program your application to //catch System.IO.IOException exceptions if you redirect a standard stream. You can also use //the IsOutputRedirected, IsInputRedirected, and IsErrorRedirected properties to determine whether //a standard stream is redirected before performing an operation that throws an System.IO.IOException exception. } if (!string.IsNullOrEmpty(tempFolder)) { SelfCleanUp(tempFolder); } //return; //Application.Exit(); } }
/// <summary> /// Starts the updater executable and sends update data to it /// </summary> /// <param name="relaunchApplication">true if relaunching the caller application is required; false otherwise</param> /// <param name="updaterDoLogging">true if the updater writes to a log file; false otherwise</param> /// <param name="updaterShowConsole">true if the updater shows the console window; false otherwise</param> /// <returns>True if successful (unless a restart was required</returns> public void ApplyUpdates(bool relaunchApplication, bool updaterDoLogging, bool updaterShowConsole) { if (IsWorking) { throw new InvalidOperationException("Another update process is already in progress"); } lock (UpdatesToApply) { using (WorkScope.New(isWorking => IsWorking = isWorking)) { bool revertToDefaultBackupPath = true; // Set current directory the the application directory // this prevents the updater from writing to e.g. c:\windows\system32 // if the process is started by autorun on windows logon. // ReSharper disable AssignNullToNotNullAttribute Environment.CurrentDirectory = Path.GetDirectoryName(ApplicationPath); // ReSharper restore AssignNullToNotNullAttribute // Make sure the current backup folder is accessible for writing from this process string backupParentPath = Path.GetDirectoryName(Config.BackupFolder) ?? string.Empty; if (Directory.Exists(backupParentPath) && PermissionsCheck.HaveWritePermissionsForFolder(backupParentPath)) { // Remove old backup folder, in case this same folder was used previously, // and it wasn't removed for some reason try { if (Directory.Exists(Config.BackupFolder)) { FileSystem.DeleteDirectory(Config.BackupFolder); } revertToDefaultBackupPath = false; } catch (UnauthorizedAccessException) { } // Attempt to (re-)create the backup folder try { Directory.CreateDirectory(Config.BackupFolder); if (!PermissionsCheck.HaveWritePermissionsForFolder(Config.BackupFolder)) { revertToDefaultBackupPath = true; } } catch (UnauthorizedAccessException) { // We're having permissions issues with this folder, so we'll attempt // using a backup in a default location revertToDefaultBackupPath = true; } } if (revertToDefaultBackupPath) { Config._backupFolder = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Config.UpdateProcessName + "UpdateBackups" + DateTime.UtcNow.Ticks); try { Directory.CreateDirectory(Config.BackupFolder); } catch (UnauthorizedAccessException ex) { // We can't backup, so we abort throw new UpdateProcessFailedException("Could not create backup folder " + Config.BackupFolder, ex); } } bool runPrivileged = false, hasColdUpdates = false; State = UpdateProcessState.RollbackRequired; foreach (var task in UpdatesToApply) { IUpdateTask t = task; task.ProgressDelegate += status => TaskProgressCallback(status, t); try { // Execute the task task.ExecutionStatus = task.Execute(false); } catch (Exception ex) { task.ExecutionStatus = TaskExecutionStatus.Failed; // mark the failing task before rethrowing throw new UpdateProcessFailedException("Update task execution failed: " + task.Description, ex); } if (task.ExecutionStatus == TaskExecutionStatus.RequiresAppRestart || task.ExecutionStatus == TaskExecutionStatus.RequiresPrivilegedAppRestart) { // Record that we have cold updates to run, and if required to run any of them privileged runPrivileged = runPrivileged || task.ExecutionStatus == TaskExecutionStatus.RequiresPrivilegedAppRestart; hasColdUpdates = true; continue; } // We are being quite explicit here - only Successful return values are considered // to be Ok (cold updates are already handled above) if (task.ExecutionStatus != TaskExecutionStatus.Successful) { throw new UpdateProcessFailedException("Update task execution failed: " + task.Description); } } // If an application restart is required if (hasColdUpdates) { var dto = new NauIpc.NauDto { Configs = Instance.Config, Tasks = Instance.UpdatesToApply, AppPath = ApplicationPath, WorkingDirectory = Environment.CurrentDirectory, RelaunchApplication = relaunchApplication, LogItems = Logger.LogItems, }; NauIpc.ExtractUpdaterFromResource(Config.TempFolder, Instance.Config.UpdateExecutableName); var info = new ProcessStartInfo { UseShellExecute = true, WorkingDirectory = Environment.CurrentDirectory, FileName = Path.Combine(Config.TempFolder, Instance.Config.UpdateExecutableName), Arguments = string.Format(@"""{0}"" {1} {2}", Config.UpdateProcessName, updaterShowConsole ? "-showConsole" : string.Empty, updaterDoLogging ? "-log" : string.Empty), }; if (!updaterShowConsole) { info.WindowStyle = ProcessWindowStyle.Hidden; info.CreateNoWindow = true; } // If we can't write to the destination folder, then lets try elevating priviledges. if (runPrivileged || !PermissionsCheck.HaveWritePermissionsForFolder(Environment.CurrentDirectory)) { info.Verb = "runas"; } bool createdNew; _shutdownMutex = new Mutex(true, Config.UpdateProcessName + "Mutex", out createdNew); try { NauIpc.LaunchProcessAndSendDto(dto, info, Config.UpdateProcessName); } catch (Exception ex) { throw new UpdateProcessFailedException("Could not launch cold update process", ex); } Environment.Exit(0); } State = UpdateProcessState.AppliedSuccessfully; UpdatesToApply.Clear(); } } }
private static void Main() { //Debugger.Launch(); string tempFolder = string.Empty; string logFile = string.Empty; _args = ArgumentsParser.Get(); _logger = UpdateManager.Instance.Logger; _args.ParseCommandLineArgs(); if (_args.ShowConsole) { _console = new ConsoleForm(); _console.Show(); } Log("Starting to process cold updates..."); var workingDir = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location); if (_args.Log) { // Setup a temporary location for the log file, until we can get the DTO logFile = Path.Combine(workingDir, @"NauUpdate.log"); } try { // Get the update process name, to be used to create a named pipe and to wait on the application // to quit string syncProcessName = _args.ProcessName; if (string.IsNullOrEmpty(syncProcessName)) { //Application.Exit(); throw new ArgumentException("The command line needs to specify the mutex of the program to update.", "args"); } Log("Update process name: '{0}'", syncProcessName); // Load extra assemblies to the app domain, if present var availableAssemblies = FileSystem.GetFiles(workingDir, "*.exe|*.dll", SearchOption.TopDirectoryOnly); foreach (var assemblyPath in availableAssemblies) { Log("Loading {0}", assemblyPath); if (assemblyPath.Equals(System.Reflection.Assembly.GetEntryAssembly().Location, StringComparison.InvariantCultureIgnoreCase) || assemblyPath.EndsWith("NAppUpdate.Framework.dll")) { Log("\tSkipping (part of current execution)"); continue; } try { var assembly = System.Reflection.Assembly.LoadFile(assemblyPath); } catch (System.BadImageFormatException ex) { Log("\tSkipping due to an error: {0}", ex.Message); } } // Connect to the named pipe and retrieve the updates list var dto = NauIpc.ReadDto(syncProcessName) as NauIpc.NauDto; // Make sure we start updating only once the application has completely terminated Thread.Sleep(100); // hell, let's even wait a bit bool createdNew; using (var mutex = new Mutex(false, syncProcessName + "Mutex", out createdNew)) { try { if (!createdNew) { mutex.WaitOne(); } } catch (AbandonedMutexException) { // An abandoned mutex is exactly what we are expecting... Log("The application has terminated (as expected)"); } } bool updateSuccessful = true; if (dto == null || dto.Configs == null) { throw new Exception("Invalid DTO received"); } if (dto.LogItems != null) // shouldn't really happen { _logger.LogItems.InsertRange(0, dto.LogItems); } dto.LogItems = _logger.LogItems; // Get some required environment variables string appPath = dto.AppPath; string appDir = dto.WorkingDirectory ?? Path.GetDirectoryName(appPath) ?? string.Empty; tempFolder = dto.Configs.TempFolder; string backupFolder = dto.Configs.BackupFolder; bool relaunchApp = dto.RelaunchApplication; if (!string.IsNullOrEmpty(dto.AppPath)) { logFile = Path.Combine(Path.GetDirectoryName(dto.AppPath), @"NauUpdate.log"); // now we can log to a more accessible location } if (dto.Tasks == null || dto.Tasks.Count == 0) { throw new Exception("Could not find the updates list (or it was empty)."); } Log("Got {0} task objects", dto.Tasks.Count); // Perform the actual off-line update process foreach (var t in dto.Tasks) { Log("Task \"{0}\": {1}", t.Description, t.ExecutionStatus); if (t.ExecutionStatus != TaskExecutionStatus.RequiresAppRestart && t.ExecutionStatus != TaskExecutionStatus.RequiresPrivilegedAppRestart) { Log("\tSkipping"); continue; } Log("\tExecuting..."); // TODO: Better handling on failure: logging, rollbacks try { t.ExecutionStatus = t.Execute(true); } catch (Exception ex) { Log(ex); updateSuccessful = false; t.ExecutionStatus = TaskExecutionStatus.Failed; } if (t.ExecutionStatus != TaskExecutionStatus.Successful) { Log("\tTask execution failed"); updateSuccessful = false; break; } } if (updateSuccessful) { Log("Finished successfully"); Log("Removing backup folder"); if (Directory.Exists(backupFolder)) { FileSystem.DeleteDirectory(backupFolder); } } else { MessageBox.Show("Update Failed"); Log(Logger.SeverityLevel.Error, "Update failed"); } // Start the application only if requested to do so if (relaunchApp) { Log("Re-launching process {0} with working dir {1}", appPath, appDir); var info = new ProcessStartInfo { UseShellExecute = true, WorkingDirectory = appDir, FileName = appPath, }; var p = NauIpc.LaunchProcessAndSendDto(dto, info, syncProcessName); if (p == null) { throw new UpdateProcessFailedException("Unable to relaunch application"); } } Log("All done"); //Application.Exit(); } catch (Exception ex) { // supressing catch because if at any point we get an error the update has failed Log(ex); } finally { if (_args.Log) { // at this stage we can't make any assumptions on correctness of the path FileSystem.CreateDirectoryStructure(logFile, true); _logger.Dump(logFile); } if (_args.ShowConsole) { if (_args.Log) { _console.WriteLine(); _console.WriteLine("Log file was saved to {0}", logFile); _console.WriteLine(); } _console.WriteLine(); _console.WriteLine("Press any key or close this window to exit."); _console.ReadKey(); } if (!string.IsNullOrEmpty(tempFolder)) { SelfCleanUp(tempFolder); } Application.Exit(); } }