2011-01-21 2 views
5

Я разрабатываю приложение на основе .NET4 на C#, которое работает как служба Windows.Применение (обслуживание) самообновления?

Я бы хотел, чтобы это приложение было в состоянии обновить себя, когда ему было поручено сделать это с помощью веб-сервиса, к которому он периодически подключается. Есть ли приемлемый способ сделать это? Возможно ли это?

То, как я думаю о нем что-то вроде этого:

  1. Сервис окна (.exe) код загружает его замену и вспомогательные библиотеки DLL в качестве почтового индекса и извлекает их во временный каталог. Почтовый индекс также включает в себя небольшой исполняемый файл «upgraderader» или скрипт.
  2. Обслуживающие вилы дочерний процесс для запуска Upgrader, передавая папку назначения и любую другую необходимую информацию о командной строке
  3. Служба выключается
  4. Upgrader процесс ожидает службы, чтобы полностью остановить, а затем перемещает необходимые файлы (новый .exe, DLL) в конечную директорию установки, заменив старый файл (ы)
  5. Upgrader перезапускает службу Windows, который порождает (модернизированный) EXE-файл и завершает работу, как только он начинает

Будет ли это работать? Вы можете обнаружить из моей терминологии и подхода, что я на фоне UNIX, а не на фоне окон. Я сделал этот подход для работы в UNIX, но я понятия не имею, какие окна могут возникнуть ...

ОБНОВЛЕНИЕ: Мое основное мотивирование для этого вопроса связано с технической осуществимостью самообновляющегося приложения .NET (как сделать замену на .LLL на месте и т. д.). Как отмечалось в комментариях, существует множество других соображений, связанных с реализацией такой функции, в частности проблемы безопасности, связанные с проверкой применения новых программных компонентов, на самом деле являются законными. Они также важны, но не специфичны для .NET или Windows (imo). Замечания по этим областям, конечно же, приветствуются, но в настоящее время они не являются моей главной задачей ...

+4

Не забудьте сохранить веб-сервис и пакет обновления. Автоматическое выполнение ненадежного кода из Интернета, особенно при работе в качестве привилегированного сервиса, является очень плохим. – ChrisV

+0

С тех пор, как сказал ChrisV, это отличная идея. Вы можете использовать порожденный процесс updrader для остановки и возобновления службы с помощью диспетчера управления сервисом. –

+0

«Разветвленный» процесс, вероятно, лучше всего реализован как еще один сервис Windows. Эта служба Windows может отвечать за запуск/остановку и проверку обновлений. Вот как работают программы проверки вирусов, такие как Norton Anti Virus, с помощью службы Live Update. В UNIX это действительно проще, так как механизм разворачивания дочернего процесса существует очень долго. Как я уже сказал, наличие двух сервисов, работающих там, где происходит четкое разделение обязанностей, превращает сервис обновления в повторно используемый фрагмент кода, который вы можете использовать для других сервисов, поскольку он не будет иметь никаких зависимостей. – CodeMonkeyKing

ответ

0

Вы должны взглянуть на this от Фила Хаака, горячего пресса с этой недели. Я не думаю, что это именно то, что вы ищете, но это может сэкономить вам время. NuGet - хорошее время в любом случае.

0

Это можно сделать с помощью ClickOnce, но, возможно, не совсем так, как вы хотите.

Взгляните на этот класс

Imports System.Deployment.Application 
Imports System.ComponentModel 

Public Class UpdateChecker 

    Public Enum UpdateType 
     Automatic 
     Manual 
    End Enum 

    Private Shared MyInstance As UpdateChecker 
    Public Shared ReadOnly Property Current() As UpdateChecker 
     Get 
      If MyInstance Is Nothing Then 
       MyInstance = New UpdateChecker 
      End If 
      Return MyInstance 
     End Get 
    End Property 

    Private WithEvents CurrDeployment As ApplicationDeployment 
    Private CurrType As UpdateType 
    Private _checking As Boolean = False 
    Private _lastErrorSentOnCheck As DateTime? 

    Public ReadOnly Property LastUpdateCheck() As DateTime? 
     Get 
      If CurrDeployment IsNot Nothing Then 
       Return CurrDeployment.TimeOfLastUpdateCheck 
      End If 
      Return Nothing 
     End Get 
    End Property 

    Public Sub CheckAsync(ByVal checkType As UpdateType) 
     Try 
      Dim show As Boolean = (checkType = UpdateType.Manual) 
      If ApplicationDeployment.IsNetworkDeployed AndAlso _ 
       Not WindowActive(show) AndAlso Not _checking AndAlso _ 
       (checkType = UpdateType.Manual OrElse Not LastUpdateCheck.HasValue OrElse LastUpdateCheck.Value.AddMinutes(60) <= Date.UtcNow) Then 

       _checking = True 

       CurrDeployment = ApplicationDeployment.CurrentDeployment 
       CurrType = checkType 

       Dim bw As New BackgroundWorker 
       AddHandler bw.RunWorkerCompleted, AddressOf CurrDeployment_CheckForUpdateCompleted 
       AddHandler bw.DoWork, AddressOf StartAsync 

       If CurrType = UpdateType.Manual Then ShowWindow() 

       bw.RunWorkerAsync() 
      ElseIf checkType = UpdateType.Manual AndAlso _checking Then 
       CurrType = checkType 
       WindowActive(True) 
      ElseIf checkType = UpdateType.Manual AndAlso Not ApplicationDeployment.IsNetworkDeployed Then 
       MessageBox.Show(MainForm, "Cannot check for updates.", "Update", MessageBoxButtons.OK, MessageBoxIcon.Information) 
      End If 
     Catch ex As Exception 
      If Not _lastErrorSentOnCheck.HasValue OrElse _lastErrorSentOnCheck.Value.AddHours(1) <= Now Then 
       _lastErrorSentOnCheck = Now 
       My.Application.LogError(ex, New StringPair("Update Check", checkType.ToString)) 
      End If 
     End Try 
    End Sub 

    Private Sub StartAsync(ByVal sender As Object, ByVal e As DoWorkEventArgs) 
     e.Result = CurrDeployment.CheckForDetailedUpdate 
    End Sub 

    Private Sub ShowWindow() 
     My.Forms.frmUpdates.MdiParent = MainForm 
     AddHandler My.Forms.frmUpdates.FormClosing, AddressOf frmUpdates_FormClosing 
     My.Forms.frmUpdates.Show() 
    End Sub 

    Protected Sub frmUpdates_FormClosing(ByVal sender As Object, ByVal e As Windows.Forms.FormClosingEventArgs) 
     My.Forms.frmUpdates = Nothing 
    End Sub 

    Private Function WindowActive(ByVal onTop As Boolean) As Boolean 
     If Not My.Forms.frmUpdates Is Nothing Then 
      If Not My.Forms.frmUpdates.Visible AndAlso onTop Then 
       My.Forms.frmUpdates.MdiParent = MainForm 
       My.Forms.frmUpdates.Show() 
      ElseIf onTop Then 
       My.Forms.frmUpdates.Activate() 
      End If 
      Return True 
     End If 
     Return False 
    End Function 

    Private Sub CurrDeployment_CheckForUpdateCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) 
     If MainForm.InvokeRequired Then 
      MainForm.Invoke(New RunWorkerCompletedEventHandler(AddressOf CurrDeployment_CheckForUpdateCompleted), sender, e) 
     Else 
      If e.Error IsNot Nothing Then 
       If WindowActive(True) Then My.Forms.frmUpdates.ShowError("Please try again later.") 

       If Not _lastErrorSentOnCheck.HasValue OrElse _lastErrorSentOnCheck.Value.AddHours(1) <= Now Then 
        _lastErrorSentOnCheck = Now 
        My.Application.LogError(e.Error, New StringPair("Update Check Async", CurrType.ToString)) 
       End If 
      Else 
       Dim updateInfo As UpdateCheckInfo = DirectCast(e.Result, UpdateCheckInfo) 
       Select Case CurrType 
        Case UpdateType.Manual 
         If WindowActive(False) Then My.Forms.frmUpdates.ShowCheckComplete(updateInfo) 
        Case UpdateType.Automatic 
         If updateInfo.UpdateAvailable Then 
          If Not WindowActive(True) Then ShowWindow() 
          My.Forms.frmUpdates.ShowCheckComplete(updateInfo) 
         End If 
       End Select 
      End If 
      _checking = False 
      End If 

     DirectCast(sender, BackgroundWorker).Dispose() 
    End Sub 

    Public Sub UpdateAsync() 
     If ApplicationDeployment.IsNetworkDeployed Then 
      CurrDeployment = ApplicationDeployment.CurrentDeployment 

      Dim bw As New BackgroundWorker 
      AddHandler bw.RunWorkerCompleted, AddressOf CurrDeployment_UpdateCompleted 
      AddHandler bw.DoWork, AddressOf StartUpdateAsync 

      My.Forms.frmUpdates.ShowUpdateStart() 

      bw.RunWorkerAsync() 
     End If 
    End Sub 

    Public Sub StartUpdateAsync() 
     CurrDeployment.Update() 
    End Sub 

    Private Sub CurrDeployment_UpdateCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) Handles CurrDeployment.UpdateCompleted 
     If MainForm.InvokeRequired Then 
      MainForm.Invoke(New AsyncCompletedEventHandler(AddressOf CurrDeployment_UpdateCompleted), sender, e) 
     Else 
      If e.Error IsNot Nothing Then 
       If WindowActive(True) Then My.Forms.frmUpdates.ShowError("Please try again later or close and re-open the application to automatically retrieve updates.") 
       My.Application.LogError(e.Error, New StringPair("Update Async", CurrType.ToString)) 
      Else 
       If WindowActive(True) Then My.Forms.frmUpdates.ShowUpdateComplete() 
      End If 
     End If 
    End Sub 
End Class 

Вот код, чтобы проверить, если требуется новое обновление. вы будете запускать это по таймеру, возможно, каждые 5 минут

UpdateChecker.Current.CheckAsync(UpdateChecker.UpdateType.Automatic) 

Тогда вот код для загрузки обновления.

UpdateChecker.Current.UpdateAsync() 

пользователь должен выйти и запустить приложение, чтобы получить новую версию или после завершения обновления можно также использовать Application.Restart перезапустить приложение

Это не обновит, когда программа не запущена, но с помощью ClickOnce вы можете проверить ее на наличие обновлений при запуске программы.

+0

Это не будет работать для службы Windows, если она не работает как пользователь, который использовал ClickOnce. Обратите внимание, что ClickOnce не сможет напрямую установить службу Windows, если она не работает с полным доверием. – CodeMonkeyKing

0

Если приложение развернуто внутри частной сети (интрасети), вы можете использовать BITS. См. Эту статью MSDN.

+0

Я не вижу никакого преимущества при использовании службы Windows, чтобы запустить приложение после завершения обновления. Кто обновляет обновление? – msms

1

Ваш подход, безусловно, разумный, но если вы не запустите под учетной записью LocalSystem (не рекомендуется!) у вас нет прав на запись в папку вашего приложения или для запуска/остановки вашей собственной службы. Даже если вы запустите под учетной записью пользователя, у вас могут возникнуть проблемы из-за UAC, хотя я не уверен в этом. В любом случае, работает под учетной записью пользователя, это плохо, поскольку для этого требуется, чтобы пользователь вводил пароль учетной записи и имел дополнительные сложности, связанные с сменой пароля, блокировкой и т. Д. Вам нужно будет установить ACL как из вашего установщика, так и в любом случае, для установки службы.

Смежные вопросы