5

My WPF application needs to list the localized names of all Metro/WinRT applications installed for the user. I created a repo to store a working sample for the code presented: https://github.com/luisrigoni/metro-apps-list

1) Using PackageManager.FindPackagesForUser() method

var userSecurityId = WindowsIdentity.GetCurrent().User.Value;
var packages = packageManager.FindPackagesForUser(userSecurityId);
foreach (var package in packages)
    Debug.WriteLine(package.Id.Name);
}

// output:
// Microsoft.BingFinance
// Microsoft.BingMaps
// Microsoft.BingSports
// Microsoft.BingTravel
// Microsoft.BingWeather
// Microsoft.Bing
// Microsoft.Camera
// microsoft.microsoftskydrive
// microsoft.windowscommunicationsapps
// microsoft.windowsphotos
// Microsoft.XboxLIVEGames
// Microsoft.ZuneMusic
// Microsoft.ZuneVideo

These outputs don't seems too friendly to show to the user...

2) Reading the AppxManifest.xml of each of these apps

var userSecurityId = WindowsIdentity.GetCurrent().User.Value;
var packages = packageManager.FindPackagesForUser(userSecurityId);
foreach (var package in packages)
{
    var dir = package.InstalledLocation.Path;
    var file = Path.Combine(dir, "AppxManifest.xml");
    var obj = SerializationExtensions.DeSerializeObject<Package>(file);

    if (obj.Applications != null)
    {
        foreach (var application in obj.Applications)
        {
            Debug.WriteLine(application.VisualElements.DisplayName);
        }
    }
}

// output:
// ms-resource:AppTitle
// ms-resource:AppDisplayName
// ms-resource:BingSports
// ms-resource:AppTitle
// ms-resource:AppTitle
// ms-resource:app_name
// ms-resource:manifestDisplayName
// ms-resource:ShortProductName
// ms-resource:mailAppTitle
// ms-resource:chatAppTitle
// ms-resource:///resources/residTitle
// ms-resource:///strings/peopleAppName
// ms-resource:///photo/residAppName
// ms-resource:34150
// ms-resource:33273
// ms-resource:33270

Definitely not friendly...

Update 1) Increasing above item (2) with SHLoadIndirectString funcion (hint by Erik F)

[DllImport("shlwapi.dll", BestFitMapping = false, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = false, ThrowOnUnmappableChar = true)]
private static extern int SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, int cchOutBuf, IntPtr ppvReserved);

static internal string ExtractStringFromPRIFile(string pathToPRI, string resourceKey)
{
    string sWin8ManifestString = string.Format("@{{{0}? {1}}}", pathToPRI, resourceKey);
    var outBuff = new StringBuilder(1024);
    int result = SHLoadIndirectString(sWin8ManifestString, outBuff, outBuff.Capacity, IntPtr.Zero);
    return outBuff.ToString();
}
[...]
foreach (var application in obj.Applications)
{
    Uri uri = new Uri(application.VisualElements.DisplayName);
    var resourceKey = string.Format("ms-resource://{0}/resources/{1}", package.Id.Name, uri.Segments.Last());
    Debug.WriteLine(ExtractStringFromPRIFile("<path/to/pri>", resourceKey));
}
[...]

// output:
// Finance
// Maps
// Sports
// Travel
// Weather
// Bing
// Camera
// SkyDrive
// Mail
// Messaging
// Calendar
// People
// Photos
// Games
// Music
// Video

Much, much better. We already have english labels. But how to extract other language resources?

I'm expecting retrieve the same label that is shown on Start Screen for each app, something like "Finanças", "Esportes", "Clima" if my language is pt-BR; "Finances", "Sports", "Weather" if my language is en-US.

[Q] Is there another way to get the application names? Maybe native/Win32 (DISM API/...)? Is possible to load the .pri file of each app to get the localized name?

As said, an updated working sample is here: https://github.com/luisrigoni/metro-apps-list

4

4 回答 4

7

使用 SHLoadIndirectString,您应该能够为 @{PackageFullName?resource-id} 形式的包名称和资源 ID 构建完全限定的引用

记录在这里:

http://msdn.microsoft.com/en-us/library/windows/desktop/bb759919(v=vs.85).aspx

不过,您必须将清单字符串转换为正确的形式。它应该是:ms-resource://PackageName/Resources/Id

PackageName 是名称而不是全名。资源不是严格要求的,但它是默认设置,并且通常存在。我会尝试在不插入资源的情况下查找资源,如果失败则重试。

例如,相机应用在清单中有“ms-resource:manifestDisplayName”,所以首先你应该尝试(*):@{Microsoft.Camera_6.2.8376.0_x64__8wekyb3d8bbwe? ms-resource://Microsoft.Camera/manifestAppDescription}

如果失败,请插入“资源”并尝试:@{Microsoft.Camera_6.2.8376.0_x64__8wekyb3d8bbwe? ms-resource://Microsoft.Camera/resources/manifestAppDescription}

那应该行得通。你会想要尝试这两种形式,因为盲目地插入“资源”会破坏直接插入路径第一部分的应用程序,如 skydrive、通信和照片。

仍然有点痛苦,但比转储和解析巨大的 XML 文件要好。

(*) "Microsoft.Camera_6.2.8376.0_x64__8wekyb3d8bbwe" 取自一个示例 - 您显然需要系统上实际存在的全名。

于 2013-08-21T22:28:31.023 回答
3

看起来你被困住了makepri.exe dump /if <prifile>.pri /of <outfile>.xml。然后您所要做的就是解析/反序列化 XML 文件。

于 2013-08-13T23:42:58.170 回答
1

除了Erik F上面所说的以及 Luis Rigoni (OP) 的更新问题之外,这里还有更多提示:

  1. 我发现 PRI 的路径是提供包名称的更好解决方案。很多时候 SHLoadIndirectString 在只给出包名称时不会解析资源。PRI 的路径是包的安装位置 + resources.pri 。示例:C:\Program Files\WindowsApps\Microsoft.MicrosoftEdge_8wekyb3d8bbwe\Resources.pri。

  2. VisualElements/DisplayName 可能包含资源的完整 url。如果是这样,您不必使用包名称和“资源”文件夹(如 ms-resource://{0}/resources/{1})进一步对其进行格式化。如果 DisplayName 包含包名本身,那么您可以假设它是一个完整的 url。

  3. 就像 Erik F 指出的那样,当 SHLoadIndirectString 失败时,请在没有 /resources/ 文件夹的情况下重试。

  4. 有时资源文件夹本身也是 VisualElements/DisplayName 的一部分。示例:ms-resource:///MSWifiResources/AppDisplayName。另外,请注意三个 ///。是的,你必须照顾好它。您只需要获取 MSWifiResources/AppDisplayName 并将其后缀为 ms-resource:///MSWifiResources/AppDisplayName

.

[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);

//If VisualElements/DisplayName contains ms-resource: then call the below
//function. identity is nothing but package name that can be retrieved from
//Identity/Name element in AppxManifest.xml.

private static string GetName(string installPath, string name, string identity) {
    StringBuilder sb = new StringBuilder();
    int result;

    //if name itself contains the package name then assume full url else 
    //format the resource url

    var resourceKey = (name.ToLower().Contains(identity.ToLower())) ? name : string.Format("ms-resource://{0}/resources/{1}", identity, name.Split(':')[[1]].TrimStart('/'));
    string source = string.Format("@{{{0}? {1}}}", Path.Combine(installPath, "resources.pri"), resourceKey);
    result = SHLoadIndirectString(source, sb, -1, IntPtr.Zero);

    if (result == 0)
        return sb.ToString();

    //if the above fails then we try the url without /resources/ folder
    //because some apps do not place the resources in that resources folder

    resourceKey = string.Format("ms-resource://{0}/{1}", identity, name.Split(':')[[1]].TrimStart('/'));
    source = string.Format("@{{{0}? {1}}}", Path.Combine(installPath, "resources.pri"), resourceKey);
    result = SHLoadIndirectString(source, sb, -1, IntPtr.Zero);

    if (result == 0)
        return sb.ToString();
    return string.Empty;
}
于 2016-10-26T08:50:57.550 回答
0

实际上,您可以比 makepri 做得更好 - 查看 ResourceIndexer:

http://msdn.microsoft.com/en-us/library/windows/apps/windows.applicationmodel.resources.management.resourceindexer.aspx

您应该能够为 IndexFileContentsAsync 提供一个 PRI 文件并取回文件中的所有候选资源。您必须重新组合和重新解释它们,但它会为您提供所有可能的资源价值。

至少对于 Windows 8 应用程序而言。

对于利用资源包(在 Windows 8.1 中引入)的应用程序,包中的 resources.pri 仅包含默认值。要获取任何其他已安装语言(或比例因子)的资源,您还需要从其他资源包中索引 PRI 文件。

于 2014-02-06T17:50:16.007 回答