🔄

(valopack)C# WPFアプリケーションの自動アップデート実装 & 自動リリース

2024/11/01に公開

はじめに

今回実装に使用するライブラリはvelopackです。自分も最近まで知らなかったのですが、オープンソースゲーム界で知らない人はほぼ居ないosuというゲームのアップデートの実装に使用されているのを見て知りました。今回は、WPFの記事ですが、勿論、CLIにも対応しており、言語もGo、Rust、Javascript、C++と様々な言語に対応しています。WPFで実装する気がなくても他の言語では興味があるという方は、是非、ドキュメントを見てみてください。WPFで実装するにあたって、ドキュメントを見て実装した際、エラーが発生して少しハマったので記事にしました。

インストール

まずNugetPackageをインストールします。

dotnet add package Velopack

本題

WPFの基本設定ではApp.xamlにStartupUri="MainWindow.xaml"と指定されてますが、これを削除してください。そして、App.xamlのStartupUriがあった場所にStartupとしてApp.xaml.csにスタートアップメソッドを作成します。

次にWPFプロジェクトのApp.xamlとApp.xaml.csと同じ階層に、Program.csを作成します。
ここで、ドキュメントに書いてないのですが、自分のプロジェクトの名前の.csprojファイルに以下コードを書きます。

任意の名前.csprojの編集

 <PropertyGroup>
        <OutputType>WinExe</OutputType>
        <TargetFramework>net8.0-windows</TargetFramework>
        <Nullable>enable</Nullable>
        <UseWPF>true</UseWPF>
        <LangVersion>12</LangVersion>
		<StartupObject>自分のプロジェクトの名前.Program</StartupObject>
        <Version>1.0.0</Version>    
    </PropertyGroup>

Program.csに以下のコードを書きます。

Program.csのコード

 public static void Main(string[] args)
        {
            try
            {
                //velopackの初期化&起動 .WithFirstRunで初めて起動したときの制御
                VelopackApp.Build().WithBeforeUninstallFastCallback((v) => { }).WithFirstRun((v) =>
                {
                    MessageBox.Show("Thanks for installing my application!");
                }).Run();
                
                //velopackの初期化と起動そしてアップデートチェック時にもアプリケーションを動作させたいので、別スレッドで起動
               Thread thread = new Thread(() =>
                {
                    var app = new App();
                    app.InitializeComponent();
                    app.Run();
                });
                thread.SetApartmentState(ApartmentState.STA);
                thread.Start();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }

次に、App.xaml.csにアップデートチェックメソッドを追加してあげましょう。

App.xaml.csにメソッド追加

 private static async Task UpdateCheck()
        {
            try
            {
                /*httpsやgitlabなども設定できます。
           今回は、githubで設定しました。ダウングレードやStable、Betaのチャンネル切り替えも出来ますが、今回はシンプルな構造にしました。
                                            */
                var mgr = new UpdateManager(new GithubSource(@"https://github.com/<Your Name>/<Your Proj>", null, false),
                    new UpdateOptions
                    {
                        AllowVersionDowngrade = true
                    });

                if (!mgr.IsInstalled)
                {
                    Console.WriteLine("Application is not installed. Update check cannot proceed.");
                    return;
                }

                var newVersionCheck = await mgr.CheckForUpdatesAsync();
                if (newVersionCheck == null) return;

                var result = MessageBox.Show("New Version Available", "Update", MessageBoxButton.YesNo);
                if (result == MessageBoxResult.Yes)
                {
                    await mgr.DownloadUpdatesAsync(newVersionCheck);
                    mgr.ApplyUpdatesAndRestart(newVersionCheck);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }

そうしたら、先程スタートアップに指定したメソッドにasyncを付けてウィンドウの起動とアップデートチェックをするようにします。

 private async void App_OnStartup(object sender, StartupEventArgs e)
    {
        MainWindow = new MainWindow();
        MainWindow.Show();
        await UpdateCheck();
    }

valopackにはこの他にも、チャンネル切り替えやバージョンのダウングレードも有ります。詳しくはドキュメントをチェックしてください。

ここからは、一番ハマったgithub actionを用いての自動リリースです。
では、いつものようにgithub actionをブランクのまま作成してください。
名前は適当で、自分はbuildにしました。

build.ymlのコード

  1. ブランチがmasterならbranchesをmasterに変えてください。
  2. <自分のアプリケーションのリポジトリURL>を置換してください。
  3. <自分のアプリケーションの.exe含めた名前>を置換してください
  4. <アプリケーション名>を置換してください
  5. <リポジトリの名前>/<アプリケーション名>を置換してください。

```yml
name: Deploy to GitHub Releases

on:
  push:
    branches:
      - main

jobs:
  deploy-to-github-releases:
    runs-on: windows-latest
    steps:
            - name: Checkout Repository
              uses: actions/checkout@v4

            - name: Get Version from Project File
              id: get-version
              shell: bash
              run: echo "version=$(grep -oE '<Version>[^<]+' <リポジトリの名前>/<アプリケーション名>.csproj | sed 's/<Version>//')" >> $GITHUB_OUTPUT

            - name: Get Now Time
              id: get-time
              shell: bash
              run: echo "time=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_OUTPUT

            - name: Install .NET
              uses: actions/setup-dotnet@v4
              with:
                    dotnet-version: 8.0.x


            - name: Publish Application
              run: dotnet publish <リポジトリの名前>/<アプリケーション名>.csproj -c Release -o publish -r win-x64 --self-contained true

            - name: Create Velopack Release
              run: |
                    dotnet tool install -g vpk
                    vpk download github --repoUrl <自分のアプリケーションのリポジトリURL>
                    vpk pack --mainExe <自分のアプリケーションの.exe含めた名前> -v ${{ steps.get-version.outputs.version }} -p publish --packId <アプリケーション名>-${{ steps.get-version.outputs.version }}-${{ steps.get-time.outputs.time }}
                    vpk upload github --token ${{ secrets.TOKEN }}  --repoUrl <自分のアプリケーションのリポジトリURL> --publish --releaseName "<アプリケーション名> ${{ steps.get-version.outputs.version }}" --tag v${{ steps.get-version.outputs.version }}

次に、リポジトリの設定を開き、github actionのRepository secretsにTOKENという名前のSecrets名にし値にgithub アクセストークンを入れてください。権限はActionsとContentsとSecretsで多分okです。
これでmainブランチが更新され、バージョンが違う場合に自動更新して、インストーラーが自動リリースされます。

最後に

このライブラリは非常に便利なのでもっと知られてほしいと思い記事にしました。自分は、CLIで使用したり別の言語で実装することはまだやっていませんが、気が向けば触ってみたいと思います。
今回記事にした内容は本当に最小限のコードなので、まだ便利な機能なども有ります。是非、実際に使ってみてください。 
https://github.com/velopack/velopack

Discussion