Однажды в Edison Software приехал мужик на гелике. Взглянув на счет, он прищурился и воскликнул: «Почему так дорого?! Я же бедный!». Это стало крылатой фразой и меткой для целого класса проектов. Итак, нам потребовалось реализовать автоматическое обновление для службы Windows, при этом соблюсти нижеследующие условия.



  • Служба обновляется автоматически без участия пользователя.
  • Для хранения пакетов обновления применяется бесплатный сервис или сервис с большим сроком бесплатного использования.
  • Решение простое и не меняет логику работы службы.

Выбор сервиса для хранения пакетов


В качестве сервиса для хранения пакетов обновления был выбран сервис Amazon Web Services (AWS). Основные причины его выбора были такие:
  • AWS предоставляет год бесплатного доступа с небольшими ограничениями;
  • AWS предоставляет API для работы с облачным хранилищем, для работы с которым разработана библиотека, доступная через NuGet (AWSSDK.S3).

Выбор механизма


Были рассмотрены следующие библиотеки для автообновления:
  • AutoUpdate.Net;
  • Squirrel.Windows;
  • ClickOnce.

Но все они не удовлетворяли нашим потребностям.
AutoUpdate.Net требовала участия пользователя при обновлении. Да и сама библиотека давно не поддерживается.
Squirrel.Windows имела все необходимые функции, однако, у неё нашёлся баг, из-за которого приложение не устанавливалось на уровне системы на Win7 x64.
ClickOnce не умеет устанавливать приложение на уровне системы. Поскольку нам требовалось устанавливать сервис, от этого решения также пришлось отказаться.

Механизм обновления




  1. Во время установки программного обеспечения инсталлятор регистрирует в планировщике Windows запуск программы автообновления по расписанию.
  2. Запуск программы автообновления планировщиком.
  3. Проверка наличия нового пакета установки на сервисе AWS
  4. Получение нового пакета установки.
  5. Остановка службы.
  6. Запуск нового пакета в «тихом» режиме, и завершение процесса программы обновления.

Новый пакет установки производит следующие действия.
  1. Удаление предыдущей версии программного обеспечения из системы (uninstall).
  2. Установка новой версии программного обеспечения (install). Вместе с новой версией программного обеспечения обновляется и программа обновления.
  3. Запуск обновленной службы.

Пакет установки MSI создается с использованием WIX (или другой утилиты) и не вызывает трудностей. Однако простота процесса обновления имеет и недостатки:
  • нет контроля ошибок при установке нового пакета обновления;
  • во временном каталоге Windows накапливаются файлы пакетов обновления.

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

Загрузка пакетов с AWS


Пакеты установки будут храниться на сервисе AWS в таком виде.

string BucketKeyName = "general/{0}/Setup.msi"; // где {0} – версия установочного пакета

Для получения номера последней версии, которая хранится на сервисе AWS, реализована следующая функция (AwsKeyId, AwsSecretKey и AwsRegion доступны в личном кабинете после регистрации на сервисе).

private static Version GetLatestVersion()
        {
            using (var client = new AmazonS3Client(AwsKeyId, AwsSecretKey, AwsRegion))
            {
                var listRequest = new ListObjectsRequest()
                {
                    BucketName = BucketName,
                    Prefix = "global/",
                    Delimiter = "/",
                };

                var response = client.ListObjects(listRequest);
                var versions = response.CommonPrefixes.Select(prefix => {
                    try
                    {
                        var versionString = prefix.Replace(listRequest.Prefix, "").Replace("/", "");
                        return new Version(versionString);
                    }
                    catch
                    {
                        return new Version();
                    }
                });

                return versions.Max();
            }
        }

Для загрузки пакета установки применяется следующая функция.

private static FileInfo DownloadInstallerFor(Version version)
        {
            var request = new GetObjectRequest
            {
                BucketName = BucketName,
                Key = string.Format("general/{0}/Setup.msi", version.ToString()),
            };

            string dest = Path.Combine(Path.GetTempPath(), request.Key);
            using (var client = new AmazonS3Client(AwsKeyId, AwsSecretKey, AwsRegion))
            {
                using (var response = client.GetObject(request))
                {
                    if (!File.Exists(dest))
                    {
                        response.WriteResponseStreamToFile(dest);
                    }
                }
            }

            return new FileInfo(dest);
        }

Основная функция программы автообновления.

static void Main(string[] args)
        {
            var currentFolder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
            var serviceFullFileName = Path.Combine(currentFolder, ServiceFileName);
            Console.WriteLine(serviceFullFileName);

// 1. получение текущей версии службы
            var serviceFileInfo = FileVersionInfo.GetVersionInfo(serviceFullFileName);
            var currentVersion = new Version(String.Format("{0}.{1}.{2}", serviceFileInfo.ProductMajorPart, serviceFileInfo.ProductMinorPart, serviceFileInfo.ProductBuildPart) ?? "0.0.0");
            Console.WriteLine(currentVersion);

// 2. получение версии службы с AWS
            var latestVersion = GetLatestVersion();
            Console.WriteLine(latestVersion);

            if (latestVersion<=currentVersion) return;

// 3. загрузка пакета обновления с AWS
            var fileInfo = DownloadInstallerFor(latestVersion);
            if (fileInfo.Exists)
            {
// 4. остановка службы, запуск пакета установки и перезапуск службы
                StopService();
                var process = new Process();
                process.StartInfo.FileName = fileInfo.FullName;
                process.StartInfo.Arguments = "/quiet";
                process.Start();
		  
            }

        }

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

Комментарии (1)


  1. musuk
    13.11.2015 14:47
    -1

    А какое количество клиентов/трафик?
    А то, может и шаред-хостинг за 1500 рублей в год подошёл бы.