/// <summary> /// Delete the temp folder as a whole and fail silently /// </summary> public void CleanUp() { Abort(true); lock (UpdatesToApply) { UpdatesToApply.Clear(); State = UpdateProcessState.NotChecked; try { if (Directory.Exists(Config.TempFolder)) { FileSystem.DeleteDirectory(Config.TempFolder); } } catch { } try { if (Directory.Exists(Config.BackupFolder)) { FileSystem.DeleteDirectory(Config.BackupFolder); } } catch { } ShouldStop = false; } }
/// <summary> /// Check for updates synchronouly /// </summary> /// <param name="source">Updates source to use</param> /// <param name="callback">Callback function to call when done</param> /// <returns>true if successful and updates exist</returns> private bool CheckForUpdates(IUpdateSource source, Action <int> callback) { LatestError = null; if (UpdateFeedReader == null) { throw new ArgumentException("An update feed reader is required; please set one before checking for updates"); } if (source == null) { throw new ArgumentException("An update source was not specified"); } lock (UpdatesToApply) { UpdatesToApply.Clear(); var tasks = UpdateFeedReader.Read(source.GetUpdatesFeed()); foreach (var t in tasks) { if (ShouldStop) { return(false); } if (t.UpdateConditions.IsMet(t)) // Only execute if all conditions are met { UpdatesToApply.Add(t); } } } if (ShouldStop) { return(false); } State = UpdateProcessState.Checked; if (callback != null) { callback.BeginInvoke(UpdatesToApply.Count, null, null); } if (UpdatesToApply.Count > 0) { return(true); } return(false); }
/// <summary> /// Check for updates synchronouly /// </summary> /// <param name="source">Updates source to use</param> public void CheckForUpdates(IUpdateSource source) { if (IsWorking) { throw new InvalidOperationException("Another update process is already in progress"); } using (WorkScope.New(isWorking => IsWorking = isWorking)) { if (UpdateFeedReader == null) { throw new ArgumentException("An update feed reader is required; please set one before checking for updates"); } if (source == null) { throw new ArgumentException("An update source was not specified"); } if (State != UpdateProcessState.NotChecked) { throw new InvalidOperationException("Already checked for updates; to reset the current state call CleanUp()"); } lock (UpdatesToApply) { UpdatesToApply.Clear(); using (Stream s = source.GetUpdatesFeed()) { var tasks = UpdateFeedReader.Read(s); foreach (var t in tasks) { if (ShouldStop) { throw new UserAbortException(); } if (t.UpdateConditions == null || t.UpdateConditions.IsMet(t)) // Only execute if all conditions are met { UpdatesToApply.Add(t); } } } } State = UpdateProcessState.Checked; } }
/// <summary> /// Check for updates synchronously /// </summary> public void CheckForUpdates() { if (IsWorking) { throw new InvalidOperationException("Another update process is already in progress"); } else if (UpdateFeedReader == null) { throw new InvalidOperationException("UpdateFeedReader must be set before calling CheckForUpdates()"); } else if (UpdateSource == null) { throw new InvalidOperationException("UpdateSource must be set before calling CheckForUpdates()"); } using (WorkScope.New(isWorking => IsWorking = isWorking)) { if (State != UpdateProcessState.NotChecked) { throw new InvalidOperationException("Already checked for updates; to reset the current state call CleanUp()"); } lock (UpdatesToApply) { UpdatesToApply.Clear(); var tasks = UpdateFeedReader.Read(UpdateSource.GetUpdatesFeed()); foreach (var t in tasks) { if (ShouldStop) { throw new UserAbortException(); } if (t.UpdateConditions == null || t.UpdateConditions.IsMet(t)) // Only execute if all conditions are met { UpdatesToApply.Add(t); } } } State = UpdateProcessState.Checked; } }
/// <summary> /// Delete the temp folder as a whole and fail silently /// </summary> public void CleanUp() { Abort(true); lock (UpdatesToApply) { UpdatesToApply.Clear(); State = UpdateProcessState.NotChecked; try { Directory.Delete(TempFolder, true); } catch { } try { Directory.Delete(BackupFolder, true); } catch { } } }
/// <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(); } } }
/// <summary> /// Check for updates synchronously /// </summary> public void CheckForUpdates() { if (IsWorking) { throw new InvalidOperationException("Another update process is already in progress"); } else if (UpdateFeedReader == null) { throw new InvalidOperationException("UpdateFeedReader must be set before calling CheckForUpdates()"); } else if (UpdateSource == null) { throw new InvalidOperationException("UpdateSource must be set before calling CheckForUpdates()"); } using (WorkScope.New(isWorking => IsWorking = isWorking)) { if (State != UpdateProcessState.NotChecked) { throw new InvalidOperationException("Already checked for updates; to reset the current state call CleanUp()"); } lock (UpdatesToApply) { UpdatesToApply.Clear(); IList <IUpdateTask> tasks = null; int currentRetry = 0; while (currentRetry < MaximumRetries) { ++currentRetry; try { tasks = UpdateFeedReader.Read(UpdateSource.GetUpdatesFeed()); break; } catch (Exception ex) { Logger.Log(ex); if (ex is WebException) { var e = ex as WebException; if (e.Status == WebExceptionStatus.Timeout || e.Status == WebExceptionStatus.ConnectFailure || e.Status == WebExceptionStatus.NameResolutionFailure) { throw new UpdateProcessFailedException("Failed to retrieve feed: " + ex.ToString()); } } if (currentRetry == MaximumRetries) { throw new UpdateProcessFailedException("Failed to retrieve feed: " + ex.ToString()); } Thread.Sleep(RetriesTimeout); } } foreach (var t in tasks) { if (ShouldStop) { throw new UserAbortException(); } if (t.UpdateConditions == null || t.UpdateConditions.IsMet(t)) // Only execute if all conditions are met { UpdatesToApply.Add(t); } } } State = UpdateProcessState.Checked; } }
/// <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> /// <returns>True if successful (unless a restart was required</returns> public bool ApplyUpdates(bool relaunchApplication) { lock (UpdatesToApply) { LatestError = null; // Make sure the current backup folder is accessible for writing from this process if (!Utils.PermissionsCheck.HaveWritePermissionsForFolder(BackupFolder)) { _BackupFolder = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), UpdateProcessName + "UpdateBackups"); } else { // Remove old backup folder, in case this same folder was used previously, // and it wasn't removed for some reason if (Directory.Exists(BackupFolder)) { Directory.Delete(BackupFolder, true); } } Directory.CreateDirectory(BackupFolder); var executeOnAppRestart = new Dictionary <string, object>(); State = UpdateProcessState.RollbackRequired; foreach (var task in UpdatesToApply) { // First, execute the task if (!task.Execute()) { // TODO: notify about task execution failure using exceptions continue; } // Add any pending cold updates to the list var en = task.GetColdUpdates(); while (en.MoveNext()) { // Last write wins executeOnAppRestart[en.Current.Key] = en.Current.Value; } } // If an application restart is required if (executeOnAppRestart.Count > 0) { // Add some environment variables to the dictionary object which will be passed to the updater executeOnAppRestart["ENV:AppPath"] = ApplicationPath; executeOnAppRestart["ENV:WorkingDirectory"] = Environment.CurrentDirectory; executeOnAppRestart["ENV:TempFolder"] = TempFolder; executeOnAppRestart["ENV:BackupFolder"] = BackupFolder; executeOnAppRestart["ENV:RelaunchApplication"] = relaunchApplication; // Naming it updater.exe seem to trigger the UAC, and we don't want that var updStarter = new UpdateStarter(Path.Combine(TempFolder, "foo.exe"), executeOnAppRestart, UpdateProcessName); bool createdNew; using (var _ = new Mutex(true, UpdateProcessName, out createdNew)) { if (!updStarter.Start()) { return(false); } Environment.Exit(0); } } State = UpdateProcessState.AppliedSuccessfully; UpdatesToApply.Clear(); } return(true); }