2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

msix、コード上からインストール(WPF)

Posted at

msixによる配布はこちらで説明しています。
WPFアプリのmsixによるweb配布、自動更新方法

今回はインストーラー経由で更新するのではなく、
コード上から直接パッケージをダウンロード、インストールする方法を説明します。

この記事で達成したいこと

msixファイルの置いてあるサーバーには認証がかかっていて、msixの自動更新機能では通れないときに、コード上でダウンロードして、インストールしたい。

もしくは、ランチャーアプリのように、複数のmsixアプリのダウンロードやインストールを管理するアプリを作るとき

手順

こちらで作成したプロジェクトの続きから行います。
コード上からインストールする場合も、ここまでは手順が一緒です。
WPFアプリのmsixによるweb配布、自動更新方法

クライアント側に追加していきます。

サーバーからファイルをダウンロードするクラス
(Newtonsoft.Jsonをnugetで落としてます)

RestClient.cs
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;

namespace MSIX_Test
{
    public class RestClient
    {
        protected HttpClient client { get; set; }

        public RestClient()
        {
            client = new HttpClient();
        }
        public async Task<Stream> GetStream(string api, object param = null)
        {
            var res = await client.GetAsync(GetParamApi(api, param), HttpCompletionOption.ResponseHeadersRead);
            return await res.Content.ReadAsStreamAsync();
        }

        protected string GetParamApi(string api, object param = null)
        {
            if (param != null)
            {
                var query = HttpUtility.ParseQueryString(string.Empty);
                var json = JsonConvert.SerializeObject(param);
                var jobj = JObject.Parse(json);
                foreach (var keyValue in jobj)
                {
                    query[keyValue.Key] = keyValue.Value.ToString();
                }
                api += "?" + query.ToString();
            }
            return api;
        }
    }
}

msixを扱うためのクラスです、msixに関する処理を書いていきます。

PackageService.cs
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Foundation;
using Windows.Management.Deployment;

namespace MSIX_Test
{
    public class PackageService
    {
        private RestClient _client { get; set; }
        private PackageManager _packageManager = new PackageManager();
        /// <summary>
        /// 現在インストールされているパッケージ
        /// </summary>
        private Package? _package { get; set; }
        /// <summary>
        /// Package.appxmanifestファイルのパッケージ化タブのパッケージ名
        /// </summary>
        private string _packageName { get; set; }
        /// <summary>
        /// インストール完了イベント
        /// </summary>
        public AsyncOperationWithProgressCompletedHandler<DeploymentResult, DeploymentProgress> InstallResultHandler { get; set; }
        /// <summary>
        /// インストールの進捗イベント
        /// </summary>
        public AsyncOperationProgressHandler<DeploymentResult, DeploymentProgress> InstallProgressHandler { get; set; }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="packageName">Package.appxmanifestファイルのパッケージ化タブのパッケージ名</param>
        public PackageService(string packageName)
        {
            _client = new RestClient();
            _packageName = packageName;
            _package = GetPackage();
        }

        /// <summary>
        /// PCにインストールされているパッケージを取得
        /// </summary>
        /// <returns></returns>
        private Package? GetPackage()
        {
            Package package = null;
            try
            {
                // パッケージの検索
                // 見つからない場合にExceptionになるのでTry
                var packages = _packageManager.FindPackagesForUser(string.Empty);
                var packageFullName = packages.FirstOrDefault(x => x.Id.Name == _packageName)?.Id.FullName ?? "";
                package = _packageManager.FindPackageForUser(string.Empty, packageFullName);
            }
            catch (Exception e)
            {
            }
            return package;
        }

        /// <summary>
        /// 指定バージョンのパッケージをサーバーからダウンロード
        /// </summary>
        /// <param name="version"></param>
        /// <returns></returns>
        private async Task DownloadPackageAsync(string version)
        {   
            var stream  = await _client.GetStream($"https://localhost:44346/MSIX", new { version });
            using (var fileStream = new FileStream(GetSaveFilePath(), FileMode.Create, FileAccess.Write, FileShare.None))
            {
                await stream.CopyToAsync(fileStream);
            }
        }

        /// <summary>
        /// msixファイルがダウンロードされるパスを取得
        /// </summary>
        /// <returns></returns>
        private string GetSaveFilePath()
        {
            var localDir = "";
            try
            {
                // UWPアプリ
                localDir = Windows.Storage.ApplicationData.Current.LocalFolder.Path;
            }
            catch (Exception ex)
            {
                // UWPアプリでない
                localDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
            }
            string saveDirectory = Path.Combine(localDir, "MSIXPackages");

            Directory.CreateDirectory(saveDirectory);

            return Path.Combine(saveDirectory, _packageName);
        }

        /// <summary>
        /// ダウンロードしたパッケージのインストール
        /// </summary>
        /// <returns></returns>
        private async Task InstallAsync()
        {
            var uri = new Uri(GetSaveFilePath());
            PackageManager packagemanager = new PackageManager();

            var asyncProgress = packagemanager.AddPackageAsync(
                uri,
                null,
                DeploymentOptions.ForceApplicationShutdown
            );

            if (InstallResultHandler != null)
            {
                asyncProgress.Completed += InstallResultHandler;
            }

            if (InstallProgressHandler != null)
            {
                asyncProgress.Progress += InstallProgressHandler;
            }

            // ただasyncProgressをawaitするとフリーズすることがあるのでTaskRun&whileで待つ
            await Task.Run(() =>
            {
                while (asyncProgress.Status == AsyncStatus.Started)
                {
                }
            });

        }

        /// <summary>
        ///  パッケージのダウンロードとインストール
        /// </summary>
        /// <param name="version"></param>
        /// <returns></returns>
        private async Task DownlogdAndInstall(string version)
        {
            await DownloadPackageAsync(version);
            await InstallAsync();
        }

        /// <summary>
        /// 現在インストールされているパッケージのバージョンと
        /// サーバーにあるバージョンを比較して、必要の場合はダウンロードとインストールを行う
        /// </summary>
        /// <returns></returns>
        public async Task UpdatePackageAsync()
        {
            var newVersion = GetNewVersion();
            if (_package == null)
            {
                // なければ新規インストール
                await DownlogdAndInstall(newVersion.ToString());
                return;
            }

            var currentVersion = new Version(GetPackageVersionText());
            if (newVersion.CompareTo(currentVersion) > 0)
            {
                await DownlogdAndInstall(newVersion.ToString());
            }
        }

        /// <summary>
        /// サーバーで指定されているパッケージのバージョン取得
        /// </summary>
        /// <returns></returns>
        private Version GetNewVersion()
        {
            // 今回はテストのため必ず、アップデートが走るように発行するパッケージより高くしておきます
            // 実際にはサーバー等から取得しましょう
            return new Version("2.0.0.0");
        }

        /// <summary>
        /// 現在インストールされているパッケージのバージョンを取得
        /// </summary>
        /// <returns></returns>
        public string GetPackageVersionText()
        {
            if (_package == null)
                return "";

            var v = _package.Id.Version;
            return string.Format("{0}.{1}.{2}.{3}", v.Major, v.Minor, v.Build, v.Revision);
        }

    }
}

バージョンの変化がわかりやすいように、インストールされているパッケージのバージョンを画面に表示させます。

MainWindow.xaml.cs
<Window
    x:Class="MSIX_Test.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:MSIX_Test"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    mc:Ignorable="d">
    <StackPanel>
        <TextBlock
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="50"
            Text="バージョン" />
        <TextBlock
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="50"
            Text="{Binding Version}" />
    </StackPanel>
</Window>

MainWindow.xaml.cs
using System.Threading.Tasks;
using System.Windows;

namespace MSIX_Test
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            this.DataContext = new MainWindowViewModel();
           
        }
    }

    public class MainWindowViewModel
    {
        public string Version { get; set; }
        public MainWindowViewModel()
        {
            var packageService = new PackageService("994c6583-42dd-49b5-9e3a-bf85f3f8b568");
            Version = packageService.GetPackageVersionText();
            MessageBox.Show("更新します", "", MessageBoxButton.OK);
            Task.Run(async () => await packageService.UpdatePackageAsync());
        }
    }

}

msixファイル配置サーバーの作成

image.png
image.png
image.png

Controllerを作成

using Microsoft.AspNetCore.Mvc;
using System.IO;
using System.Threading.Tasks;

namespace MSIX_Server.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class MSIXController : ControllerBase
    {
        [HttpGet]
        public async Task<IActionResult> Get()
        {
            using FileStream fs = System.IO.File.Open("MSIX_Test_Package.msix", FileMode.Open);
            Response.Headers.Add("fileSize", fs.Length.ToString());
            return File(fs, "application/force-download", "");
        }
    }
}

後はバージョンの「1.0.0.0」と「1.0.1.0」など新旧2つのパッケージを発行して、
旧を手動インストールした状態で、サーバー直下に新しいパッケージを置いておけば、ダウンロードとインストールがされると思います。

発行

image.png
image.png
インストールしておく
image.png

同じ手順で「1.0.1.0」発行
image.png
Server直下に名前(Controllerで指定したやつに)を変えて置く
image.png

VisualStudioでサーバープロジェクトを起動してから、アプリを起動します
image.png

更新ダイアログの後にバージョン「1.0.0.0」が表示されたあと、アプリが終了すると思います(インストールさされたから)
もう一度アプリを起動すると、更新ダイアログの後のバージョンが「1.0.1.0」に変わると思います。

以上でアプリの更新は終了です。
結構端折ってる箇所や、抜けてる部分があるかもしれないので、気づいたらご連絡いただければ幸いです。

コードはgithubにあげてあります。
https://github.com/shinnosukekubo/MSIX_Test

他にもこんなことができるので、機会があれば記事にしようかと思います。

・インストール後に自動で再起動(PCの設定によっては機能しない端末もありました。起動後60秒立たないと機能しない制約があります)
キーワード:RegisterApplicationRestart、RestartFlags

・PCにインストールされた別のmsixアプリを起動する
キーワード:LauncherOptions、TargetApplicationPackageFamilyName、Windows.System.Launcher.LaunchUriAsync

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?