应用(服务)自我升级?

时间:2011-01-21 19:26:04

标签: c# .net .net-4.0

我正在用C#开发一个基于.NET4的应用程序,它作为一个Windows服务运行。

我希望这个应用程序能够通过定期连接的Web服务进行升级。有没有可以接受的方法来实现这一目标?它甚至可能吗?

我正在考虑的方式是这样的:

  1. Windows服务(.exe)代码将其替换和支持DLL作为zip下载并将其提取到临时目录。该zip还包括一个小的“升级程序”可执行文件或脚本。
  2. 服务分叉子进程运行升级程序,在命令行上传递目标目录和任何其他必需信息
  3. 服务关闭
  4. 升级程序进程等待服务完全停止,然后将必要的文件(新的.exe,DLL)移动到最终安装目录中,替换旧文件
  5. 升级程序重新启动Windows服务,该服务会生成(已升级的).exe并在启动后退出
  6. 这会有用吗?您可以从我的术语和方法中检测到我来自UNIX背景而不是Windows背景。我已经在UNIX上使用了这种方法,但我不知道可能存在哪种窗口......

    更新:我对这个问题的主要动机是围绕自我更新的.NET应用程序的技术可行性(如何进行.DLL的就地替换等)。正如评论中所指出的,实现这样的功能还涉及许多其他考虑因素,特别是关于验证所应用的新软件组件的安全问题实际上是合法的。这些也很重要,但不是特定于.NET或Windows(imo)。当然欢迎对这些领域的评论,但目前它们不是我主要关注的问题......

4 个答案:

答案 0 :(得分:1)

您的方法当然合理,但除非您在LocalSystem帐户(不推荐!)下运行,否则您无权写入应用程序文件夹或启动/停止自己的服务。即使你在用户的帐户下运行,你也可能因为UAC而遇到问题,尽管我不确定这一点。无论如何在用户帐户下运行都不好,因为它要求用户输入帐户密码并且由于密码更改,锁定等而产生额外的复杂性。您必须在安装程序上设置ACL,无论如何都必须提升安装服务。

答案 1 :(得分:0)

你应该看看菲尔·哈克(Phil Haack)的this,这是本周早些时候的热门新闻。我不认为这正是你想要的,但它可能会节省你一些时间。无论如何,NuGet都是美好的时光。

答案 2 :(得分: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,您可以在程序启动时检查更新。

答案 3 :(得分:0)

如果应用程序部署在专用网络(Intranet)中,您可以考虑使用BITS。请参阅此MSDN文章。