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 = System.Diagnostics.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);
        }
Exemple #2
0
		/// <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);
						if (NauIpc.LaunchProcessAndSendDto(dto, info, Config.UpdateProcessName) == null)
							throw new UpdateProcessFailedException("Could not launch cold update process");

						Environment.Exit(0);
					}

					State = UpdateProcessState.AppliedSuccessfully;
					UpdatesToApply.Clear();
				}
			}
		}
Exemple #3
0
        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;

            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"
                };

                try
                {
                    NauIpc.LaunchProcessAndSendDto(_dto, info, syncProcessName);
                    _appRunning = true;
                }
                catch (Exception ex)
                {
                    throw new UpdateProcessFailedException("Unable to relaunch application and/or send DTO", ex);
                }
            }
        }