2011-01-21 213 views
5

我正在C#中開發一個基於.NET4的應用程序,它作爲一個Windows服務運行。應用程序(服務)自我升級?

我希望此應用程序能夠在定期連接的Web服務指示的情況下自行升級。有沒有一種可以接受的方式來完成這一點它甚至有可能嗎?

我想它的方式是這樣的:

  1. Windows服務(.exe)的代碼會下載更換和支持DLL作爲一個zip,並將其提取到一個臨時目錄。該zip還包含一個小的「升級」可執行文件或腳本。
  2. 服務叉一個子進程來運行升級程序,通過在目標目錄,並在命令行
  3. 的服務關閉
  4. 升級程序進程等待該服務完全停止任何其他所需信息,然後將所需的文件(新的.exe,dll)移動到最終的安裝目錄中,替換舊的文件
  5. 升級程序重新啓動windows服務,該服務會生成(升級).exe並在啓動後退出

這項工作?您可能會從我的術語和方法中發現,我來自UNIX背景,而不是Windows背景。我已經使這種方法在UNIX上工作,但我不知道可能存在哪些窗口陷阱......

更新:我的主要動機是圍繞自我更新的.NET應用程序的技術可行性(如何執行.DLL的原地替換等)。正如評論中所指出的那樣,在實現這樣一個功能時還有很多其他的考慮因素,特別是關於驗證正在使用的新軟件組件的安全問題實際上是合法的。這些也很重要,但不是特定於.NET或Windows(imo)。對這些方面的評論當然是受歡迎的,但它們目前並不是我最關心的問題......

+4

不要忘記保護Web服務和更新包。在互聯網上自動執行不受信任的代碼,特別是作爲特權服務運行時,這是非常不好的事情。 – ChrisV 2011-01-21 19:30:10

+0

由於ChrisV說,這是一個好主意。您可以使用updrader spawned進程來停止並恢復服務控制管理器的服務。 – 2011-01-21 19:43:39

+0

'分叉'過程可能是最好的另一個Windows服務。這個Windows服務可能負責啓動/停止並檢查更新。這是病毒檢查程序(如Norton Anti Virus)如何與Live Update服務配合使用。在UNIX中,確實很容易,因爲分支子進程的機制已經存在很長時間了。就像我之前所說的那樣,有兩個服務在責任清晰分離的情況下運行,使得updater服務成爲可用於其他服務的可重用代碼,因爲它不具有依賴關係。 – CodeMonkeyKing 2011-01-31 21:59:24

回答

0

你應該看看Phil Haack的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

如果應用程序部署在專用網絡(Intranet)內,則可以考慮使用BITS。這篇文章見MSDN

1

你的做法是合理的肯定,但除非你的本地系統帳戶下運行(不推薦!)你沒有權限寫入到您的應用程序文件夾或啓動/停止自己的服務。即使您在用戶帳戶下運行,由於UAC,您可能會遇到問題,但我不確定這一點。無論如何,在用戶帳戶下運行並不好,因爲它要求用戶輸入帳戶密碼,並且由於密碼更改,鎖定等問題而導致更多複雜問題。您必須從安裝程序中設置ACL,無論如何,該安裝程序必須運行提升安裝服務。