2011-01-21 9 views
5

Estoy desarrollando una aplicación basada en .NET4 en C# que se ejecuta como un servicio de Windows.¿Autoevaluación de la aplicación (servicio)?

Me gustaría que esta aplicación pueda actualizarse a sí misma cuando se lo indique un servicio web al que se conecte periódicamente. ¿Hay una forma aceptada de lograr esto? ¿Es posible?

La forma en que estoy pensando que es algo como esto:

  1. El servicio de Windows (.exe) código de descarga su reemplazo y DLL compatibles como una cremallera y los extrae en un directorio temporal. El zip también incluye un pequeño ejecutable o secuencia de comandos "mejorador".
  2. Las horquillas de servicios un proceso hijo para ejecutar el actualizador, pasando el directorio de destino y cualquier otra información requerida en la línea de comandos
  3. El servicio se apaga
  4. El mejorador proceso espera para el servicio se detenga por completo, a continuación, mueve los archivos necesarios (nuevos .exe, DLL) al directorio de instalación final, reemplazando los archivos antiguos
  5. La mejora reinicia el servicio de Windows, que genera el .exe actualizado y se cierra una vez que se inicia

¿Funcionaría? Puede detectar por mi terminología y enfoque que soy de un fondo UNIX y no un fondo de Windows. He hecho que este enfoque funcione en UNIX, pero no tengo idea de qué tipo de errores de Windows pueden existir ...

ACTUALIZACIÓN: Mi motivación principal para esta pregunta es acerca de la viabilidad técnica de una aplicación de actualización automática .NET (cómo hacer un reemplazo en el lugar de .DLLs, etc.). Como se señala en los comentarios, hay una serie de otras consideraciones involucradas en la implementación de una característica como esta, en particular las preocupaciones de seguridad sobre la verificación de que los nuevos componentes de software que se están aplicando son, de hecho, legítimos. Estos también son importantes, pero no específicos de .NET o Windows (imo). Los comentarios sobre estas áreas son bienvenidos, por supuesto, pero no son mi principal preocupación en este momento ...

+4

No olvide proteger el servicio web y actualizar el paquete. La ejecución automática de código no confiable fuera de Internet, especialmente cuando se ejecuta como un servicio privilegiado, es una cosa muy mala. – ChrisV

+0

Como está asegurado como dijo ChrisV, es una gran idea. Puede usar el proceso generado updrader para detener y reanudar el servicio con el administrador de control de servicio. –

+0

El proceso 'bifurcado' probablemente se implementa mejor como otro servicio de Windows. Este servicio de Windows podría ser responsable de iniciar/detener y verificar las actualizaciones. Así es como funcionan los programas de comprobación de virus como Norton Anti Virus con el servicio Live Update. En UNIX, de hecho, es más fácil ya que el mecanismo para bifurcar un proceso hijo ha existido por mucho tiempo. Sin embargo, como dije, tener dos servicios en ejecución donde existe una clara separación de responsabilidades hace que el servicio de actualización sea un código reutilizable que puede usar para otros servicios, ya que no tendrá dependencias. – CodeMonkeyKing

Respuesta

0

Debe echar un vistazo a this de Phil Haack, caliente de las prensas de principios de esta semana. No creo que sea exactamente lo que estás buscando, pero podría ahorrarte algo de tiempo. NuGet es buenos tiempos en cualquier caso.

0

Esto podría hacerse usando ClickOnce, pero quizás no tanto como lo desee.

Tome un vistazo a esta clase

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 

Aquí está el código para comprobar si es necesaria una nueva actualización. lo ejecutaría en un temporizador, tal vez cada 5 minutos

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

Luego aquí está el código para descargar la actualización.

UpdateChecker.Current.UpdateAsync() 

El usuario tendría que dejar de fumar y comenzar la aplicación para obtener la nueva versión o después de la actualización completa también se puede utilizar Application.Restart reiniciar la aplicación

Esto no actualizaría cuando el programa no se está ejecutando, pero con ClickOnce puede hacer que busque actualizaciones cuando se inicia el programa.

+0

Esto no funcionará para un servicio de Windows a menos que se ejecute como el usuario que utilizó ClickOnce. Tenga en cuenta que ClickOnce no podrá instalar directamente un servicio de Windows, a menos que se ejecute con plena confianza. – CodeMonkeyKing

0

Si la aplicación se implementa dentro de una red privada (intranet) puede considerar usar BITS. Consulte este artículo MSDN.

+0

No veo ninguna ventaja en el uso de un servicio de Windows para iniciar la aplicación una vez realizada la actualización. ¿Quién actualiza el actualizador? – msms

1

Su enfoque es sin duda razonable, pero a menos que se ejecuta bajo la cuenta LocalSystem (no recomendado!) no tiene permisos para escribir en la carpeta de aplicación o para encender/apagar su propio servicio. Incluso si se ejecuta bajo la cuenta del usuario, puede tener problemas debido a UAC, aunque no estoy seguro de esto. De todos modos, ejecutar bajo la cuenta del usuario no es bueno ya que requiere que el usuario ingrese la contraseña de la cuenta y tiene complicaciones adicionales derivadas del cambio de contraseña, bloqueo, etc. Tendrás que configurar ACL en ambos desde tu instalador, que de todos modos tendrá que ejecutarse de manera elevada. para instalar un servicio

Cuestiones relacionadas