WinRT:システムにインストールされた Windows ストア アプリを列挙する(3)
執筆日時:
3年前のブログ記事のソースコードを(ググったらでてきた……)そのままコピーしても動かなかったので。
復習しておくと、システムにインストールされた Windows ストア アプリを列挙するフローはこんな感じ。
- Windows.Management.Deployment.PackageManager の FindPackages() などでパッケージ package を取得
- package.DisplayName は残念ながら空
- package.InstalledLocation.Path にある AppxManifest.xml を解析して取得する
- Description や Logo なども同様の手段で取得
- AppxManifest.xml から DisplayName を取得すると ms-resource:// で記述されている場合がある
- SHLoadIndirectString() API で文字列を取得する
まず、AppxManifest.xml を解析。前回との違いは、ネームスペースが変わっていても大丈夫なよう LocalName で要素を探すようにしたところぐらい。
private string GetDisplayNameFromManifest(string installedPath) { var xml = File.ReadAllText(Path.Combine(installedPath, "AppxManifest.xml")); var doc = XDocument.Parse(xml); var value = doc.Descendants().First(_ => _.Name.LocalName == "DisplayName").Value;return value; }
次に ms-resource:// で記述されている場合の処理。ms-resource:// の書き方は数パターンあるようで、それに応じて処理を変えないといけない。
- ms-resource://Microsoft.SkypeApp/Resources/SkypeVideo_ProductName
- ms-resource:///Resources/AppStoreName
- ms-resource:PackageDisplayName
最初のパターンに寄せてから、SHLoadIndirectString() を使うことにした。
[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);private string GetTextFromResource(string installedPath, string name, string key) { var pri = Path.Combine(installedPath, "resources.pri"); var resourceKey = string.Empty;
if (key.Contains(name)) { // キーにアプリ名が含まれているパターン → そのままリソースキーとして扱う // ms-resource://Microsoft.SkypeApp/Resources/SkypeVideo_ProductName resourceKey = key; } else { // リソースキーの解析が必要なパターン // → とりあえずプロトコルを省く // ms-resource:///Resources/AppStoreName // ms-resource:/manifest/DisplayName // ms-resource:PackageDisplayName resourceKey = key.Split(':').Last();
if (resourceKey.StartsWith("/")) { // パスになっているパターン → アプリ名入りのパスに // :///Resources/AppStoreName // :/manifest/DisplayName resourceKey = resourceKey.TrimStart('/'); resourceKey = $"ms-resource://{name}/{resourceKey}"; } else { // リソース名になっているパターン → リソースへのパスに // :PackageDisplayName resourceKey = $"ms-resource://{name}/resources/{resourceKey}"; } }
var buffer = new StringBuilder(1024); // ← 適当
var result = SHLoadIndirectString( $"@{{{pri}? {resourceKey}}}", buffer, buffer.Capacity, IntPtr.Zero );
// 失敗したときはパスをそのまま返しておく return result == 0 ? buffer.ToString() : key; }
うまくいっているみたい。「ストア」アプリと表記の違うパッケージもあるけど、今のところは気にしないことにする。