Luís Rigoni Luís Rigoni - 1 month ago 24
C# Question

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

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

Answer

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.

Comments