Get localized friendly names for all winrt/metro apps installed from WPF application
Asked Answered
O

4

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

Osbourne answered 13/8, 2013 at 21:57 Comment(0)
B
7

Using SHLoadIndirectString, you should be able to construct a fully-qualified reference for Package name and resource ID of the form @{PackageFullName?resource-id}

Documented here:

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

You'll have to transform the manifest string into the proper form, though. It should be: ms-resource://PackageName/Resources/Id

PackageName is the name rather than the full name. Resources isn't strictly required but it's the default and it's usually there. I'd try to look up the resource without inserting resources and then try again if that fails.

For example, the camera app has "ms-resource:manifestDisplayName" in the manifest, so first you should try(*): @{Microsoft.Camera_6.2.8376.0_x64__8wekyb3d8bbwe? ms-resource://Microsoft.Camera/manifestAppDescription}

When that fails, insert "resources" and try: @{Microsoft.Camera_6.2.8376.0_x64__8wekyb3d8bbwe? ms-resource://Microsoft.Camera/resources/manifestAppDescription}

That should work. You'll want to try both forms because blindly inserting "resources" will break apps like skydrive, communications and photos which insert the first part of the path directly.

Still a bit of a pain, but better than dumping and parsing gigantic XML files.

(*) "Microsoft.Camera_6.2.8376.0_x64__8wekyb3d8bbwe" is taken from an example - you'll obviously want the FullName of the one that's actually present on your system.

Bywoods answered 21/8, 2013 at 22:28 Comment(3)
This function worked great to extract the en-us resources. How configure to get other languages resources values?Branchia
This kind of reference will always use your default languages or the application primary language override. That means that you have to change add the other language to the front of the language list in your user language profile in the control panel.Bywoods
What if I use this method for an app which is installed for some other user, and the user has a different language than my user? (both users on the same system), is SHLoadIndirectString() guaranteed to work in such a case. (I will be using SHLoadIndirectString() on the resource string obtained from some other user)Precursor
E
3

Looks like you're stuck with makepri.exe dump /if <prifile>.pri /of <outfile>.xml. Then all you have to do is parse/deserialize the XML file.

Ehtelehud answered 13/8, 2013 at 23:42 Comment(2)
Unfortunately, makepri.exe is a developer tool, and requires Windows Kit SDK. We would have to include it with our application, and that is not an option for us. Anyway, thank you for the reply.Scoundrelly
@DiogoMuller Unless you figure out the actual format of a PRI file (which is undocumented), I'm afraid this is your only option at the moment. You could also try do disassemble the makepri tool to take a peek on how it reads/writes a PRI file. But that sounds nasty.Ehtelehud
K
1

In addition to what Erik F told above along with updated question from Luis Rigoni (OP) here are further tips:

  1. I found that path to PRI is better solution that giving package name. Many a times SHLoadIndirectString doesn't resolve the resource when just package name is given. Path to PRI is the package's install location + resources.pri . Example: C:\Program Files\WindowsApps\Microsoft.MicrosoftEdge_8wekyb3d8bbwe\Resources.pri.

  2. The VisualElements/DisplayName may contain the full url to the resource. If so, you don't have to further format it using package name and 'resources' folder like ms-resource://{0}/resources/{1}. If the DisplayName contains the package name itself, then you can assume that it is a full url.

  3. Like Erik F pointed out, when SHLoadIndirectString fails, try again without the /resources/ folder.

  4. Also sometimes the resources folder itself will be part of VisualElements/DisplayName. Example: ms-resource:///MSWifiResources/AppDisplayName. Also, notice the three ///. Yes, you will have to take care of that. You just have to take MSWifiResources/AppDisplayName and suffix it to 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;
}
Kipton answered 26/10, 2016 at 8:50 Comment(3)
Is this an exhaustive list of possibilities?Precursor
HKU can be parsed to get display name strings for all users (https://github.com/Wox-launcher/Wox/issues/198). Can we use these display name strings for SHLoadIndirectString() on HKCU of all users to get display names for all packages? (HKCU will be a subtree of HKU, hence we can get HKCU of all users)Precursor
Sorry. I am unable to answer that. You can try.Kipton
B
0

Actually, you can do better than makepri - check out the ResourceIndexer:

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

You should be able to give IndexFileContentsAsync a PRI file and get back all of the resource candidates in the file. You'll have to reassemble and reinterpret them, but it will get you all of the possible resource values.

For Windows 8 apps, at least.

For apps which take advantage of resource packages (introduced in Windows 8.1), the resources.pri in the package contains only the defaults. To get the resources for the any other installed languages (or scale factors) you'll need to also index the PRI files from the additional resource packages.

Bywoods answered 6/2, 2014 at 17:50 Comment(1)
Can you give any sample code to get display name using this API? I am able to get information about other resources like images, embedded data, etc. but not display name. I am using ResourceIndexer::IndexFileContentsAsync()Precursor

© 2022 - 2024 — McMap. All rights reserved.