Pull to refresh

Автоматическое обновление программ на C#

Reading time5 min
Views89K
Несколько лет назад, программируя еще на Delphi, лично для себя соорудил некий код автоматического обновления, который в последствии стал незаменимым при разработке любой программы, где есть обновление. В настоящий момент этот код полностью переписан на c# и я хочу с Вами им поделиться.

image

Вначале определим цели этой реализации:

  1. При обнаружении новой версии обновление должно происходить автоматически;
  2. После обновления программа должна автоматически перезапускаться;
  3. После обновления имя программы должно остаться прежним.

Проблема состоит в том, что программа не может саму себя удалить, заменить и вновь запустить. И, казалось бы, как решить этот вопрос? Здесь нам поможет второй файл, отвечающий за переименование и перезапуск программы, так как мы не гонимся за целью хранить все коды в 1 файле.


Этапы


Этап 1: Проверка версии

В силу своей лени искать оптимальный вариант, на сайте было выложено 2 файла:
  • myprogram.exe
  • version.xml

Да, именно XML-формат использую. Забегая вне темы скажу, что в файле version.xml у меня находится список нескольких версий файлов, но мы рассмотрим только одну.

Идем дальше. Структура файла версий выглядит следующим образом:

<version>
     <myprogram>1.0.2.37</myprogram>
</version>

На форму добавлен компонент backgroundWorker (для реализации фоновой загрузки файла) со следующим кодом внутри обработчика DoWork:
try
	{
		double versionRemote = Convert.ToDouble(doc.GetElementsByTagName("myprogram")[0].InnerText.Replace(".", "")),
				thisVersion = Convert.ToDouble(Application.ProductVersion.Replace(".", ""));

		if (thisVersion < versionRemote)
		{
			MessageBox.Show(this, "Обнаружена новая версия (" + doc.GetElementsByTagName("myprogram")[0].InnerText + ")" + Environment.NewLine +
				"Приложение будет автоматически обновлено и перезапущено.", Application.ProductName + " v" + Application.ProductVersion, MessageBoxButtons.OK, MessageBoxIcon.Information);

			var client = new WebClient();
			client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(download_ProgressChanged);
			client.DownloadFileCompleted += new AsyncCompletedEventHandler(download_Completed);
			client.DownloadFileAsync(new Uri(@"http://mysite/myprogram.exe"), "temp_myprogram");
		}
	}
catch (Exception) { }

Что мы видим в коде выше:
Так как версия у нас может иметь большое число, используем тип переменной double. Для сравнения версий мы удаляем все точки и конвертируем версию из строки в число (в примере получится число 10237).
Точно также мы поступим и с версией самого файла, присвоенной переменной thisVersion.

После этого нам нужно сравнить локальную версию с удаленной и если наша версия меньше удаленной, то сперва выводим сообщение, информирующее о дальнейшем обновлении. После этого программа начинает скачивать файл в ту же папку, откуда она запущена. Файлу присваивается имя temp_myprogram.

Для отслеживания статуса загрузки на форму был добавлен компонент progressBar, и в код добавлена функция:

private void download_ProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
	try
		{
			progressBar1.Value = e.ProgressPercentage;
		}
	catch (Exception) { }
}

Функция отображает в прогрессбаре статус загрузки файла. Это нужно лишь для наглядного отображения.
Итак, мы загрузили наш файл и что делать дальше? А дальше вступает в бой функция download_Completed, содержащая код:

private void download_Completed(object sender, AsyncCompletedEventArgs e)
{
	try
	{
		Process.Start("updater.exe", "temp_myprogram myprogram.exe");
		Process.GetCurrentProcess().Kill();
	}
	catch (Exception) { }
}

Здесь все просто: запускаем файл updater.exe с параметрами, о которых расскажу в следующем этапе.
Второй строкой указываем о необходимости принудительного завершения работы приложения.

Этап 2: Обработка обновления

Далее на помощь приходит утилита updater.exe, функциональной особенностью которой является проверка завершения работы основного приложения и обработка обновления.
Ну да не будем вдаваться в текст и сразу перейдем к коду:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Threading;

namespace Updater
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
				string process = args[1].Replace(".exe", "");
			
                Console.WriteLine("Terminate process!");
                while (Process.GetProcessesByName(process).Length > 0)
                {
                    Process[] myProcesses2 = Process.GetProcessesByName(process);
                    for (int i = 1; i < myProcesses2.Length; i++) { myProcesses2[i].Kill(); }
					
					Thread.Sleep(300);
                }

				if(File.Exits(args[1])){ File.Delete(args[1]); }
				
				File.Move(args[1], args[0]);
				
                Console.WriteLine("Starting "+args[1]);
                Process.Start(args[1]);
            }
            catch (Exception) { }
        }
    }
}

Так как нам не нужны формы, проект собран как обычное консольное приложение, действия которого довольно просты.
Задаем цикл, который проверяет запущен ли процесс, указанный во 2-ом параметре. Если процесс найден, то ему будет передана команда Kill() для принудительного завершения, после чего выжидаем 300 миллисекунд и повторяем. Цикл будет работать до тех пор, пока процесс не завершится.

Далее удаляем старый файл. Для устранения некоторых ошибок (скорее ошибок в мозгу) добавляем функцию проверки существования файла.
После удаления переименовываем имя файла, заданного в 1-ом параметре на имя, заданное во 2-ом параметре. В нашем случае произойдет переименовывание файла temp_myprogram в myprogram.exe, после чего процесс myprogram.exe будет запущен, а окно данного апдейтера закрыто.
Также хочу сказать, что файл программы «updater» я использую во всех своих проектах, где он требуется, так как у него нет привязки к какому-то конкретному приложению.

И переходим к следующему этапу:

Этап 3: Завершение


И вот мы видим, что обновленный файл версии успешно запустился, а окно «апдейтера» закрылось. Profit!

Статья написана на основании лаунчера для модпака «PROТанки» к игре «World of Tanks» с оригинальными скриншотами приложения. Для тех, кто скажет «нет там этого функционала» сразу скажу, что данный лаунчер находится на бета-тесте и доступен ограниченному количеству лиц.

Если кому будет нужен файл updater.exe, то Вы всегда сможете скачать его актуальную версию ЗДЕСЬ, на моем официальном сайте. В настоящий момент актуальной версией является 1.0.0.2.

И на этой строчке наш код автоматического обновления подходит к концу.

UPD. Мной написана вторая статья, содержащая часть внесенных поправок.
Убедительная просьба, у кого еще имеются мысли по поводу «кривых рук», «кривого кода» и пр., пишите в комментариях хотя бы что не так. Опираясь на Вашу конструктивную критику я улучшу свою работу, тем самым научившись писать более качественный код.
Заранее благодарен!


С уважением, Андрей Helldar!
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 31: ↑16 and ↓15+1
Comments55

Articles