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); } } }
/// <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(); } } }