だるろぐ

明日できることは、今日しない。

WinRT:システムにインストールされた Windows ストア アプリを列挙する(2)

WinRT:システムにインストールされた Windows ストア アプリを列挙する - だるろぐ で未解決だった問題を解決しておく。

AppxManifest.xml を読んで DisplayName などを取得する

とりあえず動けばいいので、dynamic(System.Dynamic.ExpandoObject)に AppxManifest.xml で読み込んだ DisplayName、Description、Logo などをぶち込んで ViewModel を作った。

namespace WinRTAppsUpdateChecker
{
    using System.IO;
    using System.Xml.Linq;
    using Windows.Management.Deployment;

    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            var manager = new PackageManager();
            var packages = manager
                .FindPackagesForUser(string.Empty)
                .Select(_ => 
                {
                    dynamic result = new System.Dynamic.ExpandoObject();
                    result.Id = _.Id;

                    try
                    {
                        var path = _.InstalledLocation.Path; // -> exception
                        var xml = File.ReadAllText(Path.Combine(path, "AppxManifest.xml"));
                        var doc = XDocument.Parse(xml);
                        XNamespace ns = "http://schemas.microsoft.com/appx/2010/manifest";

                        result.InstalledLocation = path;
                        result.DisplayName = doc.Descendants(ns + "DisplayName").First().Value;
                        // result.Description = doc.Descendants(ns + "Description").First().Value;
                        result.PublisherDisplayName = doc.Descendants(ns + "PublisherDisplayName").First().Value;
                        result.Logo = doc.Descendants(ns + "Logo").First().Value;
                        result.OSMinVersion = doc.Descendants(ns + "OSMinVersion").First().Value;
                        result.OSMaxVersionTested = doc.Descendants(ns + "OSMaxVersionTested").First().Value;
                        // result.BackgroundColor = doc.Descendants(ns + "VisualElements").First().Attributes("BackgroundColor").First().Value;
                    }
                    catch (Exception e)
                    {
                        result.Error = e.Message; 
                    }
                    return result;
                }).ToList();

            DataContext = new
            {
                Packages = packages,
            };
        }
    }
}

相変わらずダックタイピングでコーディングしてるので汚いけど、基本的に XDocument で読んでいるだけ。

f:id:daruyanagi:20140911023459p:plain

ちゃんととれているみたい。

Description はない場合も多い。タイルの背景色も取得したかったけれど、書いてない AppxManifest.xml も少なくない感じ。もうちょっと調査してみよう。

resources.pri を読んでリソース文字列を取得する

f:id:daruyanagi:20140911023541p:plain

さっきのでだいたいうまくいったかなと思ったのだけど、DisplayName は(おそらく多言語対応のため)ms-resource で指定されている場合がある。ガッデム!

これを読むには、SHLoadIndirectString function | Microsoft Docs を利用する。

namespace WinRTAppsUpdateChecker
{
    using System.IO;
    using System.Runtime.InteropServices;
    using System.Xml.Linq;
    using Windows.Management.Deployment;

    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        [DllImport("shlwapi.dll", BestFitMapping = false, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = false, ThrowOnUnmappableChar = true)]
        public static extern int SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, int cchOutBuf, IntPtr ppvReserved);

        public MainWindow()
        {
            InitializeComponent();

            var manager = new PackageManager();
            var packages = manager
                .FindPackagesForUser(string.Empty)
                .Select(_ => 
                {
                    dynamic result = new System.Dynamic.ExpandoObject();
                    result.Id = _.Id;

                    try
                    {
                        var path = _.InstalledLocation.Path; // -> exception
                        var xml = File.ReadAllText(Path.Combine(path, "AppxManifest.xml"));
                        var doc = XDocument.Parse(xml);
                        XNamespace ns = "http://schemas.microsoft.com/appx/2010/manifest";

                        result.InstalledLocation = path;
                        result.DisplayName = doc.Descendants(ns + "DisplayName").First().Value;
                        // ここから
                        if ((result.DisplayName as string).StartsWith("ms-resource"))
                        {
                            var pri = Path.Combine(path, "resources.pri");
                            var resourceKey = (result.DisplayName as string).Split(':').Last();
                            var resourcePath = string.Format("@{{{0}? ms-resource://{1}/resources/{2}}}", pri, _.Id.Name, resourceKey);
                            var buffer = new StringBuilder(1024);
                            SHLoadIndirectString(resourcePath, buffer, buffer.Capacity, IntPtr.Zero);
                            result.DisplayName = buffer.ToString();
                        }
                        // ここまで
                        // result.Description = doc.Descendants(ns + "Description").First().Value;
                        result.PublisherDisplayName = doc.Descendants(ns + "PublisherDisplayName").First().Value;
                        result.Logo = doc.Descendants(ns + "Logo").First().Value;
                        result.OSMinVersion = doc.Descendants(ns + "OSMinVersion").First().Value;
                        result.OSMaxVersionTested = doc.Descendants(ns + "OSMaxVersionTested").First().Value;
                        // result.BackgroundColor = doc.Descendants(ns + "VisualElements").First().Attributes("BackgroundColor").First().Value;
                    }
                    catch (Exception e)
                    {
                        result.Error = e.Message; 
                    }
                    return result;
                }).ToList();

            DataContext = new
            {
                Packages = packages,
            };
        }
    }
}

リソースのパスの指定がよくわかんなかったのだけど、いろいろ試した結果、これでイケている感じ。ms-resource の読み込み処理はべた書きしたけど、ViewModel をちゃんとクラス化するときに関数にパッケージングして、Description などがリソースで指定されているときにも対応できるようにしておけばいいと思う。

f:id:daruyanagi:20140911025259p:plain

ちゃんと名前もとれている感じ。よくみたら WinJS の名前が取れてないケド、見ないことにした。

追記(2014/09/11 03:08)

f:id:daruyanagi:20140911030830p:plain

リソースパスの指定方法をちょっと変えたら、WinJS にも対応できた。

var resourceKey = (result.DisplayName as string).Split(':').Last();
if (!resourceKey.StartsWith("/")) resourceKey = "/resources/" + resourceKey;
var resourcePath = string.Format("@{{{0}? ms-resource://{1}{2}}}", pri, _.Id.Name, resourceKey);

だたキーが指定されているときは /resources/* で、階層付きで指定されているときはそれをそのまま渡してやればいいみたい。ドキュメントちゃんと読んでないから知らんけど。