2011-01-21 16 views
5

Sto sviluppando un'applicazione basata su .NET4 in C# che viene eseguita come servizio Windows.Auto-aggiornamento applicazione (servizio)?

Mi piacerebbe che questa applicazione sia in grado di aggiornarsi da sola quando viene richiesto da un servizio web a cui si connette periodicamente. C'è un modo accettato per realizzare questo? È possibile?

Il modo in cui sto pensando di esso è qualcosa di simile:

  1. Il servizio di Windows (exe) Codice scarica la sua sostituzione e la DLL di supporto come un lampo e loro estratti in una directory temporanea. Lo zip include anche un piccolo eseguibile o script di "aggiornamento".
  2. Le forche servizio un processo figlio per eseguire l'upgrader, passando la directory di destinazione e qualsiasi altra informazione richiesta sulla riga di comando
  3. Il servizio si spegne
  4. L'upgrader processo attende per il servizio di fermare completamente, quindi sposta i file necessari (nuovo .exe, DLL) nella directory di installazione finale, sostituendo il vecchio file.
  5. L'aggiornamento riavvia il servizio Windows, che genera (aggiornato) .exe e si chiude una volta avviato

Questo lavoro dovrebbe funzionare? È possibile rilevare dalla mia terminologia e approccio che io sono da uno sfondo UNIX e non uno sfondo di Windows. Ho fatto funzionare questo approccio su UNIX, ma non ho idea di quale tipo di getchas di finestre possa esistere ...

AGGIORNAMENTO: La mia motivazione principale per questa domanda riguarda la fattibilità tecnica di un'applicazione .NET autoaggiornante. (come fare una sostituzione sul posto di. DLL, ecc.). Come sottolineato nei commenti, ci sono una serie di altre considerazioni relative all'implementazione di una funzionalità come questa, in particolare i problemi di sicurezza legati alla verifica dei nuovi componenti software applicati sono di fatto legittimi. Anche questi sono importanti, ma non specifici per .NET o Windows (imo). I commenti su queste aree sono benvenuti, naturalmente, ma non sono la mia preoccupazione principale in questo momento ...

+4

Non dimenticare di proteggere il servizio Web e aggiornare il pacchetto. L'esecuzione automatica di un codice non sicuro da Internet, soprattutto quando è in esecuzione come servizio privilegiato, è una cosa molto brutta. – ChrisV

+0

Poiché è protetto come ha detto ChrisV, è una grande idea. È possibile utilizzare il processo generato da updrader per interrompere e riprendere il servizio con il gestore controllo servizi. –

+0

Il processo "forked" è probabilmente meglio implementato come un altro servizio Windows. Questo servizio Windows potrebbe essere responsabile dell'avvio/arresto e del controllo degli aggiornamenti. In questo modo i programmi di controllo antivirus come Norton Anti Virus funzionano con il servizio di aggiornamento automatico. In UNIX è davvero più facile in quanto il meccanismo di forking di un processo figlio è in circolazione da molto tempo. Come ho detto, avere due servizi in esecuzione in cui esiste una netta separazione delle responsabilità rende il servizio di aggiornamento in una parte di codice riutilizzabile che è possibile utilizzare per altri servizi poiché non avrà alcuna dipendenza. – CodeMonkeyKing

risposta

0

Dovresti dare un'occhiata a this da Phil Haack, caldo delle stampanti di questa settimana. Non penso che sia esattamente quello che stai cercando, ma potrebbe farti risparmiare tempo. NuGet è sempre un buon momento.

0

Questo potrebbe essere fatto utilizzando ClickOnce, ma forse non proprio nella misura desiderata.

Date un'occhiata a questa classe

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 

Ecco il codice per verificare se è necessario un nuovo aggiornamento. lo esegui su un timer, forse ogni 5 minuti

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

Quindi, ecco il codice per scaricare l'aggiornamento.

UpdateChecker.Current.UpdateAsync() 

L'utente deve chiudere e avviare l'applicazione per ottenere la nuova versione o dopo il termine dell'aggiornamento si potrebbe anche usare Application.Restart per riavviare l'applicazione

Questo non sarebbe aggiornare quando il il programma non è in esecuzione, ma con ClickOnce è possibile controllare gli aggiornamenti all'avvio del programma.

+0

Questo non funzionerà per un servizio Windows a meno che non sia in esecuzione come utente che ha utilizzato ClickOnce. Si noti che ClickOnce non sarà in grado di installare direttamente un servizio Windows a meno che non sia in esecuzione con piena attendibilità. – CodeMonkeyKing

0

Se l'applicazione è distribuita in una rete privata (intranet), è possibile utilizzare BITS. Vedi questo articolo MSDN.

+0

Non vedo alcun vantaggio nell'utilizzo di un servizio di Windows per avviare l'applicazione una volta completato l'aggiornamento. Chi aggiorna il programma di aggiornamento? – msms

1

L'approccio è certamente ragionevole, ma a meno che non si esegua l'account LocalSystem (non consigliato!) non si dispone delle autorizzazioni per scrivere nella cartella dell'applicazione o per avviare/interrompere il proprio servizio. Anche se si esegue sotto l'account dell'utente, si potrebbero avere problemi a causa di UAC anche se non sono sicuro di ciò. In ogni caso, l'esecuzione con l'account dell'utente non è buona poiché richiede all'utente di inserire la password dell'account e presenta ulteriori complicazioni derivanti dalla modifica della password, dal blocco ecc. Dovrai impostare ACL su entrambi dal tuo programma di installazione, che dovrà comunque funzionare in modo elevato per installare un servizio.

Problemi correlati