Лучшая практика для написания самообновляющейся службы Windows

Нам нужно создать службу Windows с возможностью самообновления.

На ум приходят три варианта:

  1. вторая служба, которая управляет получением, удалением и установкой первой службы.

  2. Использование сторонних фреймворков (предложения приветствуются. Я считаю, что .NET поддерживает автоматическое обновление для приложений Windows Forms, но не для служб Windows)

  3. Использование модели плагина, при которой служба представляет собой просто оболочку, содержащую логику обновления и запуска, а бизнес-логика службы содержится в DLL, которую можно заменить.

Может ли кто-нибудь пролить свет на решение этой проблемы?

Спасибо


person Ben Aston    schedule 22.10.2009    source источник


Ответы (3)


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

В Интернете есть статья, в которой более подробно объясняется подробно описано, как можно использовать Omaha для обновления служб Windows. В нем утверждается, что Омаха особенно хорошо подходит для Сервисов (по сравнению, скажем, с приложениями с графическим интерфейсом) из-за своей асинхронной природы.

Таким образом, вы можете реализовать свои пункты 2 и 1, используя Омаху. Боюсь, я не знаю, как бы вы поступили 3.

person Declan Nelson    schedule 27.10.2020

Просто некоторые мысли у меня были.

1 кажется проблематичным, потому что вы в конечном итоге имеете дело с ситуацией, которую пытаетесь решить, потому что в какой-то момент программе обновления потребуется обновление. 3 звучит хорошо, но если под «заменой» вы имеете в виду использование какого-то причудливого отражения для загрузки библиотеки DLL во время выполнения, я не уверен, что производительность станет проблемой.

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

person Spencer Ruport    schedule 22.10.2009
comment
+1 У нас действительно есть самообновляющаяся служба Windows .Net, и она делает это при загрузке более новой версии: 1) Устанавливает новую службу, если двоичные версии отличаются (хотя это может быть просто изменение конфигурации, и в этом случае мы просто хотим начать заново). 2) Создает вспомогательный процесс, который: A) останавливает старую службу, B) запускает новую службу, C) (если версии, а не только файлы конфигурации) удаляет старую службу. Это была одна из самых сложных областей, в которой нужно было разобраться, и против нее было зарегистрировано много ошибок, и отладка была сукой :( - person Hamish Grubijan; 03.08.2010
comment
Во-первых, если новый сервис построен на новой платформе .net ... о, о, о, о, мальчик! Мы используем несколько .bat файлов, вроде работает (иногда с перезапуском), но плохо. Во-вторых, поскольку отладка - это боль, LOG, LOG, LOG! Вспомогательный процесс не должен писать в один и тот же журнал, поэтому он должен писать в свой собственный. Trace.Writeln - самый простой способ войти в журнал, если в app.config файле есть правильная запись. Просто не забудьте установить для auto-flush значение true. Что-то такое хрупкое, лучше бегать медленно, чем без подсказок. Если говорить о «медленно», помощнику в любом случае нужно поспать несколько секунд. - person Hamish Grubijan; 03.08.2010
comment
@ Дмитрий Нестерук, сначала это xml для ведения журнала: ‹? Xml version = 1.0?› ‹Configuration› ‹system.diagnostics› ‹trace autoflush = true indentsize = 4› ‹listeners› ‹remove name = Default /› ‹add name = myListener type = System.Diagnostics.TextWriterTraceListener initializeData = c: \ myListener.log / ›‹/listeners› ‹/trace›‹ /system.diagnostics ›‹startup› поддерживается ‹Runtime version = v4.0 sku = .NETFramework, Version = v4.0 / ›‹/startup› ‹/configuration›, и вы входите в систему с помощью Trace.Writeline и т. д. - person Hamish Grubijan; 06.05.2011
comment
Средство обновления вызывается следующим образом: string strNewVersion = Path.Combine (_application.Client.BaseDir, _server.AvailableVersion + \\ service.exe); var inst = new AssemblyInstaller (strNewVersion, новая строка [] {}); installer.Installers.Add (инсталлятор); installer.Install (новый System.Collections.Hashtable ()); var psii = новый ProcessStartInfo (); psii.Arguments = String.Format ({0} {1}, GetCurrentRunningDirVersion (), _server.AvailableVersion); psii.FileName = UpdaterHelper.exe; psii.UseShellExecute = false; var procc = Process.Start (psii); procc.WaitForExit (); // Возможно, добавим сон до или после. - person Hamish Grubijan; 06.05.2011

Я использую вариант 1. В наши дни процесс обновления обновляется очень редко. Он использует XML-файл, содержащий подробную информацию о том, откуда взять файлы (в настоящее время поддерживает SVN, работает над добавлением поддержки NuGet) и где их разместить. Он также указывает, какие из них являются службами, а какие - веб-сайтами, и указывает имя службы, которая будет использоваться для каждого проекта.

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

Вот основной фрагмент кода для средства обновления, которое останавливает существующую службу, копирует файлы, а затем перезапускает ее.

if (isService)
{
    log.Debug("Stopping service " + project.ServiceName);

    var service = GetService(project);
    if (service != null && 
        service.Status != System.ServiceProcess.ServiceControllerStatus.Stopped && service.Status != System.ServiceProcess.ServiceControllerStatus.StopPending)
    {
        service.Stop();
    }

    service.WaitForStatus(System.ServiceProcess.ServiceControllerStatus.Stopped, new TimeSpan(0, 1, 0));
    if (service.Status == System.ServiceProcess.ServiceControllerStatus.Stopped)
        log.Debug("Service stopped");
    else
        log.Error("ERROR: Expected Stopped by Service is " + service.Status);

}

log.Debug("Copying files over");
CopyFolder(checkoutDirectory, destinationDirectory);

if (isService)
{
    log.Debug("Starting service");
    var service = GetService(project);

    // Currently it doesn't create services, you need to do that manually
    if (service != null)
    {
        service.Start();

        service.WaitForStatus(System.ServiceProcess.ServiceControllerStatus.Running, new TimeSpan(0, 1, 0));

        if (service.Status == System.ServiceProcess.ServiceControllerStatus.Running)
            log.Debug("Service running");
        else
            log.Error("Service " + service.Status);
    }
}
person Ian Mercer    schedule 17.09.2011