かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

msix でアプリケーションの更新に対応する(多分 ClickOnce の正当な移行先)

Visual Studio 2019 で UWP や Desktop Bridge アプリのパッケージングをすると msix 形式になってますね。 UWP 自体は VS 2017 の頃からいつからか忘れましたが多分 msix になってたけど Desktop Bridge のほうの Windows パッケージ プロジェクトは appx だった気がする?

さて、1 つ前くらいの Windows 10 から .appinstaller という形式のファイルでインストールするとアプリケーションの自動更新に対応できるようになってました。 ただ、勝手にいつの間にかアップデートされてるという感じの挙動でした。

Windows 10 1809 以降は細かな制御も可能になっています。例えば

  • 確認がある場合に更新をするかユーザーに確認する
  • 強制的に更新を適用するように強要する
  • アップデートだけではなくダウングレードも許可する

詳細は以下を確認してください。

docs.microsoft.com

やってみよう

msix なので、とりあえず UWP でやってみようと思いますが、.NET Framework や .NET Core の WPF/WinForms アプリもパッケージングしてしまえば同じなので今からやることは Desktop Bridge アプリでも可能です。 UWP アプリを作って画面に適当な TextBlock を置きます。

<Page
    x:Class="UpdateSampleApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UpdateSampleApp"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <TextBlock Style="{StaticResource HeaderTextBlockStyle}"
                   HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   Text="最初のバージョン" />
    </Grid>
</Page>

プロジェクトの右クリックメニューからストア→アプリパッケージの作成でサイドロード用のパッケージを作成します。

f:id:okazuki:20190420215713p:plain

ウィザードを進めていくと、アプリパッケージを実際に配置する場所のパス(URL やファイルパスなど) を指定するような画面が出てくるのでさくっと入れます。 私は AppPackages フォルダーの直下をとりあえず指定しました。とりあえず試すだけなら、出力先をそのまま書くのが楽ちん。

以下のような感じで出力されます。

f:id:okazuki:20190420220056p:plain

そして、おもむろに .appinstaller ファイルをエディターで開きます。残念ながら更新の制御の設定は、ツールサポートがまだないので手書きです。 基本的に UpdateSettings タグに以下のようなものを書きます。xmlns

<!-- AppInstaller タグの xmlns の最後を 2017/2 から 2018 にしてください -->
<UpdateSettings>
    <OnLaunch HoursBetweenUpdateChecks="0" ShowPrompt="true" UpdateBlocksActivation="true" />
    <ForceUpdateFromAnyVersion>true</ForceUpdateFromAnyVersion>
</UpdateSettings>

ShowPrompt を追加すると更新があるときにダイアログが出ます。ユーザーは更新をせずに起動することが可能です。(次回起動時に自動更新される) UpdateBlocksActivation を追加すると更新を強制できます。

ForceUpdateFromAnyVersion を追加するとダウングレードが可能になります。順にみていきましょう。

ShowPrompt

xmlns を 2018 にして ShowPrompt を true にします。

<UpdateSettings>
    <OnLaunch HoursBetweenUpdateChecks="0" ShowPrompt="true" />
</UpdateSettings>

そして、index.html からインストールをします。事前に証明書(.cer)をローカルマシンの信頼されたルート証明機関に入れておきましょう。以下の場所からダウンロードできます。

f:id:okazuki:20190420220628p:plain

アプリを取得するボタンを押すとインストールが始まって起動します。

f:id:okazuki:20190420220755p:plain

適当にアプリを更新します。TextBlock のテキストを「次のバージョン」にしました。 同じ手順でアプリケーションをサイドロード用のパッケージにパッケージングします。

そして同じように .appinstaller を書き換えて ShowPrompt を追加します。 そしてアプリを起動すると以下のような画面が表示されます。

f:id:okazuki:20190420221438p:plain

ローカライズが非常に残念なのですが、ちょっとちゃんとフィードバックしておきます。 更新完了が Finish updating で営業中が Open now の翻訳になります…。営業中は予想外でした。

更新完了を押すと更新されて起動します。営業中を押すとアプリが更新されずに起動しますが裏で更新が行われます。

f:id:okazuki:20190420221641p:plain

UpdateBlocksActivation

これを追加すると更新を強制出来ます。適当にアプリを更新して .appinstalelr を以下のようにします。名前空間を 2018 に変更するのも忘れずに。

<UpdateSettings>
    <OnLaunch HoursBetweenUpdateChecks="0" ShowPrompt="true" UpdateBlocksActivation="true" />
</UpdateSettings>

こんな感じのダイアログが表示されます。

f:id:okazuki:20190420221947p:plain

更新を押すと更新されて、キャンセルを押すと起動しないけど裏で更新されます。(結局どっち押しても最終的には更新されます)

ForceUpdateFromAnyVersion

さて、重大なバグがあって過去のバージョンに戻したいとしましょう。 .appinstaller に ForceUpdateFromAnyVersion を追加してファイル内のバージョンを表す数字を全部過去の任意のバージョンにします。

  • AppInstaller タグの Version 属性
  • MainBundle タグの Version 属性
  • MainBundle タグの Uri 属性を古いバージョンのファイルへのパスに変更
  • Dependencies タグの Package タグの Uri を古いバージョンのファイルへのパスに変更

この状態でアプリを起動しても普通に起動するのですが、編集した .appinstaller ファイルをダブルクリックするか Web サイトのリンククリックで起動すると以下のようなダイアログが出てきます。

f:id:okazuki:20190420222503p:plain

更新を押すと古いバージョンに更新されます。ForceUpdateFromAnyVersion を設定していないと過去のバージョンへの更新は出来ません。

まとめ

ClickOnce を置き換えてもいいかな?ってくらいの機能が揃ってきたような気がする…? Windows 10 のバージョンの問題さえクリアすればですが…。