読者です 読者をやめる 読者になる 読者になる

プログラマーのメモ書き

伊勢在住のプログラマーが気になることを気ままにメモったブログです

【.Net】 NAppUpdate試してみました

別記事で書いた自動更新ライブラリNAppUpdateを早速試してみました。以下にメモ書きをまとめておきます。

 

使ったのは、2013/2/5時点の最新版のbranch/masterのソースです。

以下では、VS2010で、VB.net(.Net Framework 4) にて開発しています。

 

簡単な使い方

.Netで簡単なアプリを作ります。フォームにボタンを1個配置し、ボタンを押したら更新確認を開始するというものです。

 

Imports NAppUpdate.Framework

Public Class Form1

    Public Sub New()

        ' この呼び出しはデザイナーで必要です。
        InitializeComponent()

        ' InitializeComponent() 呼び出しの後で初期化を追加します。

        UpdateManager.Instance.UpdateSource = New Sources.SimpleWebSource("https://サーバー名/test/nau/versioninfo.xml")
        UpdateManager.Instance.ReinstateIfRestarted()

        Debug.Print("tempフォルダ:" & UpdateManager.Instance.Config.TempFolder)
        Debug.Print("backupフォルダ:" & UpdateManager.Instance.Config.BackupFolder)

    End Sub
    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        'MessageBox.Show("バージョン:" & My.Application.Info.Version.ToString)

        MessageBox.Show("現在の状態:" & UpdateManager.Instance.State.ToString)

        Select Case (UpdateManager.Instance.State)
            Case UpdateManager.UpdateProcessState.NotChecked
                'check2()
                check()
            Case UpdateManager.UpdateProcessState.Checked
                prepare()
            Case UpdateManager.UpdateProcessState.Prepared
                install()
            Case UpdateManager.UpdateProcessState.AppliedSuccessfully
                MessageBox.Show("最新版のインストールに成功しています。")
            Case UpdateManager.UpdateProcessState.RollbackRequired
                rollback()
            Case UpdateManager.UpdateProcessState.AfterRestart
                checkAfterRestart()
        End Select
    End Sub

    Private Sub checkAfterRestart()
        Debug.Print("task数:" & UpdateManager.Instance.Tasks.Count)

        For Each task As NAppUpdate.Framework.Tasks.IUpdateTask In UpdateManager.Instance.Tasks
            MessageBox.Show("taskの説明:" & task.Description & vbCrLf & "taskステータス:" & task.ExecutionStatus.ToString)
        Next
    End Sub

    Private Sub check2()
        Try
            UpdateManager.Instance.CheckForUpdates()
        Catch ex As Exception
            MessageBox.Show("例外発生" & ex.Message)
            Return
        End Try
        If 0 = UpdateManager.Instance.UpdatesAvailable Then
            MessageBox.Show("最新です。")
            Return
        End If
        Debug.Print("UpdatesAvailable is " & UpdateManager.Instance.UpdatesAvailable)
    End Sub
    Private Sub check()
        Try
            'UpdateManager.Instance.CheckForUpdateAsync(New Action(Of Boolean)(AddressOf check_callback))
            UpdateManager.Instance.BeginCheckForUpdates(New AsyncCallback(AddressOf check_callback), Nothing)
        Catch ex As Exception
            If TypeOf ex Is NAppUpdateException Then
                MessageBox.Show("例外発生" & ex.Message)
                Return
            End If
        End Try
    End Sub

    'Private Sub check_callback(ByVal result As Boolean)
    Private Sub check_callback(ar As IAsyncResult)
        UpdateManager.Instance.EndCheckForUpdates(ar)
        If 0 = UpdateManager.Instance.UpdatesAvailable Then
            MessageBox.Show("最新です。")
            Return

        End If
        Debug.Print("UpdatesAvailable is " & UpdateManager.Instance.UpdatesAvailable)

        '        If False = result Then
        'MessageBox.Show("最新です。")
        'Return
        'End If
        prepare()
    End Sub

    Private Sub prepare()
        Dim dr As DialogResult = MessageBox.Show("最新版があります。アップデートしますか?", "更新確認", MessageBoxButtons.YesNo)
        If Windows.Forms.DialogResult.Yes = dr Then
            '            UpdateManager.Instance.PrepareUpdatesAsync(New Action(Of Boolean)(AddressOf prepare_callback))
            UpdateManager.Instance.BeginPrepareUpdates(New AsyncCallback(AddressOf prepare_callback), Nothing)
        End If
    End Sub

    '    Private Sub prepare_callback(ByVal result As Boolean)
    Private Sub prepare_callback(ar As IAsyncResult)
        Try
            UpdateManager.Instance.EndPrepareUpdates(ar)
        Catch ex As Exception
            MessageBox.Show("更新準備に失敗しました:" & UpdateManager.Instance.State.ToString & vbCrLf & ex.Message)
            Return
        End Try

        'If False = result Then
        '    MessageBox.Show("更新準備に失敗しました:" & UpdateManager.Instance.State.ToString & vbCrLf & UpdateManager.Instance.LatestError)
        '    Return
        'End If
        install()
    End Sub

    Private Sub install()
        Dim dr As DialogResult = MessageBox.Show("インストールしますか?", "インストール確認", MessageBoxButtons.YesNo)
        If Windows.Forms.DialogResult.Yes = dr Then
            Try
                'Dim res As Boolean = UpdateManager.Instance.ApplyUpdates(True, True, True)
                'If res Then
                '    MessageBox.Show("更新が完了しました。")
                'Else
                '    MessageBox.Show("インストールに失敗しました。")
                'End If

                'UpdateManager.Instance.ApplyUpdates(True, True, True)
                UpdateManager.Instance.ApplyUpdates(True, True, False)
            Catch ex As Exception
                Debug.Print("例外発生:" & ex.Message)
            End Try
        End If
    End Sub

    Private Sub rollback()
        Dim dr As DialogResult = MessageBox.Show("ロールバックしますか?", "ロールバックの確認", MessageBoxButtons.YesNo)
        If Windows.Forms.DialogResult.Yes = dr Then
            UpdateManager.Instance.RollbackUpdates()
            MessageBox.Show("ロールバックしました")
        End If
        MessageBox.Show("ロールバックしませんでした")
    End Sub
End Class

 

次に、サーバーにversioninfo.xmlファイルを置いて、更新するファイルの情報を書いておきます。簡単な例としてこういうものをおいておきます。

<?xml version="1.0" encoding="utf-8" ?>
<Feed BaseUrl="http://www.google.com/">
  <Tasks>
    <FileUpdateTask hotswap="true" localPath="logo.png" updateTo="http://www.google.com/intl/en_ALL/images/srpr/logo1w.png">
    </FileUpdateTask>
  </Tasks>
</Feed>

 

作成したアプリを実行すると、『最新版があります。アップデートしますか?』とたずねられ、『はい』を選ぶと、更新準備が行われ、『インストールしますか?』と聞かれます。さらに『はい』を選ぶと、Googleのロゴファイルがダウンロードされてきていました。

これで、基本的な動作はすることがわかりました。

 

cold updateについて

実は、最初にGitHubから最新バイナリ(0.2)をダウンロードして試したところ、コールドアップデート(使用中のファイルの置き換え)がうまく動作しませんでした。

 

一つには、VisualStudio のデバッグモードで呼び出すと、デバッグ用にxxx.vshost.exeが起動されるため、オートアップデートが想定している、xxxx.exeでのリスタートがうまく動作しないことが挙げられます。

しかし、GoogleGroupのディスカッションをよく見ると、どうもこれだけが原因ではなく、バグもあったようです。

そのため、上記のサンプルはリポジトリから最新版(試した時点では、2013/2/5のbranch/master、comit 7cdce1cc0a8794b4a9ec420c2df8f43cf5098096 だったはず)をダウンロードしてきて、コンパイルして作成したdllを使いました。これを使うと問題なく動作しました。

 

cold update 時の動作はだいたい次のようなものでした。

  1. ライブラリのprepareUpdateが呼ばれると、tempフォルダ(デフォルトだと、ユーザーAppDataLocaltempに作られるようです)にダウンロードしたファイルが保管されます。
  2. 次に、アップデートの適用(ApplyUpdates)が実行されると、このtempフォルダにアップデート用の実行ファイル(dll内にリソースとして埋め込まれています)とdllそのものがコピーされます。
  3. アップデートを実行していたプログラム自体は終了し、このtempフォルダ内の実行ファイル(デフォルトでは、foo.exeという名前)が実行され、これがファイルのリプレースを行います。
  4. リプレース後、元のプログラムを再度起動し、tempファイル等は削除します。

 

最新版について

バージョン0.2と比べると、メソッドがかなり変わっています。

上記のサンプルプログラム中で、コメントアウトしているソースがあると思いますが、これらはバージョン0.2バイナリを試したときのものです。

 

たとえば、更新確認および更新準備の非同期用メソッドCheckUpdateAsync, PrepareUpdateAsync がなくなり、それに代わるものとして、BeginCheckUpdate, EndCheckUpdate 及び BeginPrepareUpdate, EndPrepareUpdate が追加されています。ちなみに中身を見ると、スレッドプールを使って呼び出しをしているようです。

また、エラー発生時の処理も、LastErrorメソッドはなくなり、例外処理になっているようです。

 

このため、作者のブログの記事を読んでも、ちょこちょこ変わっているのであまり使えないので注意が必要です。

 

確実に動作するサンプルがほしければ、ダウンロードしたソースコードに含まれている、Samples配下のプロジェクトを読むのが一番だと思います。

 

コンパイルについて

配布されているバイナリを使う場合は気にしなくてよいのですが、ソースコードからdllを作る場合は、若干注意が必要です。

VisualStudio上でNAppUpdate.Frameworkプロジェクトのプロパティを見てみますと、ビルド前イベントが設定されています。中身を確認すると、

  • Updaterフォルダがなければ作成する
  • updater.exeがなければ、ダミーファイルを作成する

となっています。また、リソースを確認すると、Updaterupdater.exeを含んでいます。

 

UpdateManager.ApplyUpdatesのソースを見ると、cold updateであれば、リソースに含まれているupdater.exeをtempフォルダに書き出すようです。

 

NAppUpdate.Updaterのプロジェクトプロパティのビルドイベントを確認すると、上記に対応するようにビルド後イベントとして

  • 作成 したexeファイルをframeworkのUpdaterフォルダにupdater.exeとしてコピーする

という処理が入っています。

 

さて、ここで、注意が必要なのが、初めてdllをコンパイルしたタイミングだと、まだupdate.exeは存在していません。(NAppUpdate.UpdaterはNAppUpdate.Framewaorkに依存しているので、NAppUpdate.Framework作成後でないとコンパイルされません)。

このため、最初にコンパイルした段階でのdllを使った場合、ダミーのupdater.exeが含まれた状態なので、cold updateしても必ず失敗します。

 

従って、ソースコードに変更を加えていなくても最低2回コンパイルしないと正しいdllを作ることができません。こちらのディスカッションでも指摘されてました。ご注意ください。

 

その他気づいた点

いろいろ試していて気づいた点を書いておきます。

  • BeginCheckUpdate を呼び出してコールバック関数を指定できるのはいいけど、これはメインスレッドとは別スレッドで呼ばれている。このため、コールバックと思いUIを操作すると例外が発生します。
  • cold update 成功時はバックアップフォルダが消去されます(消去設定があるのか不明です)
  • 更新フィードを作ってくれるFeedBuilderというツールもあります(操作していて落ちることも多いですが)

ほかにもあった気がしますが、思い出したら追記します。

 

 

最後に

既にこの記事を書いている段階でも開発が進んでいるようです(時々コンパイルも通らないこともあります)。

まだまだ開発途中のようなので、積極的にお勧めはしませんが個人的にはかなりよさげな感触を持っています。安定版が早く出てくれることを願っています。