概要
- Microsoft Store で MSIXでパッケージングした WPFアプリをサイレントアップデートする方法。
- StoreContext.TrySilentDownloadAndInstallStorePackageUpdatesAsync でアップデートを行い。
- RegisterApplicationRestart で アップデート後の自動での再起動を行います。
- 検証用のアプリは こちら になります。
WPFアプリの MSIXへのパッケージング方法
WPFアプリのmsixによるweb配布、自動更新方法 が参考になります。
ストアでの配布方法はWPF アプリを Microsoft Store に申請・登録する(アプリ作成編)などを参考にしてください。
MSIXアプリのアップデート方法
ストアで公開されたアプリをコードから更新する が参考になります。
ユーザーの操作を伴わないサイレントアップデート(Silent Update) については、StoreContext.TrySilentDownloadAndInstallStorePackageUpdatesAsync で行うことができます。
詳細な解説とサンプルコードが、Download and install package updates from the Store で公開されているので、参考にすると良いです。
ただし、TrySilentDownloadAndInstallStorePackageUpdatesAsync は、アプリインストール後に、TrySilentDownloadAndInstallStorePackageUpdatesAsync の中で、強制的にアプリが終了してしまいます。
タスクトレイに常駐しているようなアプリをアップデートしたい場合、勝手に終了して欲しくないので、アップデート後に自動で再起動する必要があります。
アップデート後の再起動は、更新後にアプリを自動的に再起動するに記載されている通り、事前にRegisterApplicationRestart を呼び出しておき、その後に TrySilentDownloadAndInstallStorePackageUpdatesAsync でアップデートすることで、実現できます。
アプリ全体はこちら になりますが、重要なところだけ抜粋すると下記になります。
namespace WPF_SilentUpdate
{
namespace WPF_SilentUpdate
{
public partial class App : PrismApplication
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
uint hresult = RelaunchHelper.RegisterApplicationRestart();
Console.WriteLine($"RelaunchHelper.RegisterApplicationRestart: {hresult}");
StoreManager.CheckUpdateAndInstall();
}
}
class StoreManager
{
public static void CheckUpdateAndInstall()
{
Task.Run(async () =>
{
while (true)
{
try
{
Thread.Sleep(65 * 1000);
await SilentDownloadAndInstallUpdatesAsync();
}
catch (Exception e)
{
break;
}
}
});
}
public static async Task<bool> SilentDownloadAndInstallUpdatesAsync(bool preDownload = true)
{
StoreContext context = StoreContext.GetDefault();
if (context.CanSilentlyDownloadStorePackageUpdates == false)
{
return false;
}
IReadOnlyList<StorePackageUpdate> storePackageUpdates = await context.GetAppAndOptionalStorePackageUpdatesAsync();
if (storePackageUpdates.Count == 0)
{
return false;
}
if (preDownload)
{
StorePackageUpdateResult downloadResult = await context.TrySilentDownloadStorePackageUpdatesAsync(storePackageUpdates);
if (downloadResult.OverallState != StorePackageUpdateState.Completed)
{
return false;
}
}
StorePackageUpdateResult installResult = await context.TrySilentDownloadAndInstallStorePackageUpdatesAsync(storePackageUpdates);
if (installResult.OverallState != StorePackageUpdateState.Completed)
{
return false;
}
return true;
}
}
// アプリケーションの再起動の登録
// https://docs.microsoft.com/ja-jp/windows/win32/recovery/registering-for-application-restart
//
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-registerapplicationrestart
// Remarks に記載のことが大事。
//
// デスクトップアプリのリスタートマネージャー2(アプリ停止のためのウィンドウメッセージ)
// https://nishy-software.com/ja/dev-sw/app-restart-manager-2/
class RelaunchHelper
{
// 一般的な HRESULT 値
// https://docs.microsoft.com/ja-jp/windows/win32/seccrypto/common-hresult-values?redirectedfrom=MSDN
public static uint RegisterApplicationRestart()
{
return RelaunchHelper.RegisterApplicationRestart(null, RelaunchHelper.RestartFlags.NONE);
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
internal static extern uint RegisterApplicationRestart(string? pwzCommandLine, RestartFlags dwFlags);
[Flags]
internal enum RestartFlags
{
NONE = 0,
RESTART_NO_CRASH = 1,
RESTART_NO_HANG = 2,
RESTART_NO_PATCH = 4,
RESTART_NO_REBOOT = 8
}
}
}
検証用アプリについて
実行すると下記画面のように、WPFアプリの ProcessId、バージョン、起動後経過秒数、アプリのインストール先フォルダが表示されます。
ProcessId を表示しているのは、Windows アプリ認定キット の rmlogotest.exe で、RegisterApplicationRestart 挙動をテストするためです。
rmlogotest.exe でのテストは、デスクトップアプリのリスタートマネージャー5(テストツール)で解説されていて、お世話になりました。 RegisterApplicationRestart の情報はほんとに少ない(T_T)。
uptime を表示しているのは、RegisterApplicationRestart function (winbase.h) の Remarks に下記のように記載されているためです。
To prevent cyclical restarts,
the system will only restart the application
if it has been running for a minimum of 60 seconds.
「再移動する」ボタンを押すと、0除算でアプリがCRASHし、RegisterApplicationRestart によって再起動します。
RegisterApplicationRestart 以外で試したこと
TrySilentDownloadAndInstallStorePackageUpdatesAsync の前後に再起動
直前に再起動しておくとしても、アップデート前のバイナリで再起動してしまい意味がない。
直後は、TrySilentDownloadAndInstallStorePackageUpdatesAsync の中で終了するので、直後のコードが実行されるかはタイミングなのか不確定で、うまく動かない。
ラッパーとして UWPを作り、windows.updateTask で起動
起動用にUWPアプリを作り、UpdateTask (IBackgroundTask) の中で、FullTrustProcessLauncher で WPFアプリ起動するようにしたら、アップデート後に起動しましたが、UWPのウィンドウが邪魔だけど消せない。
最終的に かずきさんの Twitter で紹介されている C# Discordサーバーで質問させていただき、(かずきさんから) registerapplicationrestart の情報を教えていただき実現できたのでした。。。