How to get the "Application Name" from hWnd for Windows 10 Store Apps (e.g. Edge)
Asked Answered
N

5

12

I'm trying to get an understandable "Process Name" for Windows 10 apps. Currently, all of them use ApplicationFrameHost, so I thought I could use either the ModelId or the PackageName, but it seems Windows 10 Store Apps (I tried with Mail, Store and Edge) won't work with the Package query API

Using kernel32.dll, GetApplicationUserModelId returns APPMODEL_ERROR_NO_APPLICATION and GetPackageId returns APPMODEL_ERROR_NO_PACKAGE.

How can I get an identifier for a Windows 10 Store App, so that I can uniquely identify, say, Edge but also any other Windows 10 Store Apps?


Update

I'm getting the process ID from the hWnd (the window handle), so I think my problem is actually how to get the "real" process ID from a window handle. From there, using those methods would probably work.

Nefertiti answered 14/8, 2015 at 3:0 Comment(8)
Maybe there's something here I missed: msdn.microsoft.com/en-us/library/windows/apps/br211377.aspx - I'll need to dig further...Nefertiti
You may be interested in the answers on my question: #32360649 using EnumWindows or the UIAutomation APIs.Fayth
@TimBeaudet thanks for the reference. This solution only works when the window is not minimized, which is a problem for an alt+tab program :) Your technique works for non-minimized cases, so it does help for at least some use cases! Thanks, though this is still not completely solved.Nefertiti
I'm at a point where I think the best idea would be to: 1. List all windows, 2. List all processes, 3. Identify Windows 10 Apps processes (e.g. by their paths), 4. Find all processes that do not have an open window and show them, otherwise show their windows... That becomes complicated...Nefertiti
I'm a bit late to the question, but what I know: ApplicationFrameHost owns the parent (root) window for each application, but inside this window it has child window that is owned by the application. You can see this in Spy++. You can get child window of AppFrameHost window and get name from it's process.Robot
@OlehNechytailo this only works when the application is not minimized (I'd have to check again to make sure what I say is correct)Nefertiti
@ChristianRondeau Could you share your solution for getting the application information when it's minimized? This has to be achievable since Task Manager clearly has this information...Bridlewise
@Bridlewise you can look at the code here : github.com/christianrondeau/GoToWindow but I never fully solved it. It works though, but I don't remember exactly what and how.Nefertiti
T
9

UWP apps are wrapped into an other app/process. If this has focus, then try and find the child UWP process.

You will need some P/Invoke methods. Take a look at this class, which provide all the code you need to do the job:

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace Stackoverflow
{
    internal struct WINDOWINFO
    {
        public uint ownerpid;
        public uint childpid;
    }

    public class UwpUtils
    {
        #region User32
        [DllImport("user32.dll")]
        public static extern IntPtr GetForegroundWindow();
        [DllImport("user32.dll", SetLastError = true)]
        public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
        // When you don't want the ProcessId, use this overload and pass IntPtr.Zero for the second parameter
        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);
        /// <summary>
        /// Delegate for the EnumChildWindows method
        /// </summary>
        /// <param name="hWnd">Window handle</param>
        /// <param name="parameter">Caller-defined variable; we use it for a pointer to our list</param>
        /// <returns>True to continue enumerating, false to bail.</returns>
        public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);

        [DllImport("user32", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool EnumChildWindows(IntPtr hWndParent, EnumWindowProc lpEnumFunc, IntPtr lParam);
        #endregion

        #region Kernel32
        public const UInt32 PROCESS_QUERY_INFORMATION = 0x400;
        public const UInt32 PROCESS_VM_READ = 0x010;

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool QueryFullProcessImageName([In]IntPtr hProcess, [In]int dwFlags, [Out]StringBuilder lpExeName, ref int lpdwSize);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr OpenProcess(
            UInt32 dwDesiredAccess,
            [MarshalAs(UnmanagedType.Bool)]
            Boolean bInheritHandle,
            Int32 dwProcessId
        );
        #endregion

        public static string GetProcessName(IntPtr hWnd)
        {
            string processName = null;

            hWnd = GetForegroundWindow();

            if (hWnd == IntPtr.Zero)
                return null;

            uint pID;
            GetWindowThreadProcessId(hWnd, out pID);

            IntPtr proc;
            if ((proc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, (int)pID)) == IntPtr.Zero)
                return null;

            int capacity = 2000;
            StringBuilder sb = new StringBuilder(capacity);
            QueryFullProcessImageName(proc, 0, sb, ref capacity);

            processName = sb.ToString(0, capacity);

            // UWP apps are wrapped in another app called, if this has focus then try and find the child UWP process
            if (Path.GetFileName(processName).Equals("ApplicationFrameHost.exe"))
            {
                processName = UWP_AppName(hWnd, pID);
            }

            return processName;
        }

        #region Get UWP Application Name

        /// <summary>
        /// Find child process for uwp apps, edge, mail, etc.
        /// </summary>
        /// <param name="hWnd">hWnd</param>
        /// <param name="pID">pID</param>
        /// <returns>The application name of the UWP.</returns>
        private static string UWP_AppName(IntPtr hWnd, uint pID)
        {
            WINDOWINFO windowinfo = new WINDOWINFO();
            windowinfo.ownerpid = pID;
            windowinfo.childpid = windowinfo.ownerpid;

            IntPtr pWindowinfo = Marshal.AllocHGlobal(Marshal.SizeOf(windowinfo));

            Marshal.StructureToPtr(windowinfo, pWindowinfo, false);

            EnumWindowProc lpEnumFunc = new EnumWindowProc(EnumChildWindowsCallback);
            EnumChildWindows(hWnd, lpEnumFunc, pWindowinfo);

            windowinfo = (WINDOWINFO)Marshal.PtrToStructure(pWindowinfo, typeof(WINDOWINFO));

            IntPtr proc;
            if ((proc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, (int)windowinfo.childpid)) == IntPtr.Zero)
                return null;

            int capacity = 2000;
            StringBuilder sb = new StringBuilder(capacity);
            QueryFullProcessImageName(proc, 0, sb, ref capacity);

            Marshal.FreeHGlobal(pWindowinfo);

            return sb.ToString(0, capacity);
        }

        /// <summary>
        /// Callback for enumerating the child windows.
        /// </summary>
        /// <param name="hWnd">hWnd</param>
        /// <param name="lParam">lParam</param>
        /// <returns>always <c>true</c>.</returns>
        private static bool EnumChildWindowsCallback(IntPtr hWnd, IntPtr lParam)
        {
            WINDOWINFO info = (WINDOWINFO)Marshal.PtrToStructure(lParam, typeof(WINDOWINFO));

            uint pID;
            GetWindowThreadProcessId(hWnd, out pID);

            if (pID != info.ownerpid)
                info.childpid = pID;

            Marshal.StructureToPtr(info, lParam, true);

            return true;
        }
        #endregion
    }
}

Now, get a handle to the current foreground window using another P/Invoke method

[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();

Use the return value and call the GetProcessName method from the code above. You should receive the correct name/path to the process.

Here is a simple Form to test the code:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using StackOverflow;

namespace Stackoverflow.Test
{
    public partial class TestForm : Form
    {
        WinEventDelegate dele = null;
        delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);

        [DllImport("user32.dll")]
        static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);

        private const uint WINEVENT_OUTOFCONTEXT = 0;
        private const uint EVENT_SYSTEM_FOREGROUND = 3;

        [DllImport("user32.dll")]
        public static extern IntPtr GetForegroundWindow();
        public TestForm()
        {
            InitializeComponent();

            dele = new WinEventDelegate(WinEventProc);
            IntPtr m_hhook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, dele, 0, 0, WINEVENT_OUTOFCONTEXT);
        }

        public void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
        {
            textBox1.AppendText(GetActiveWindowTitle() + "\n");
        }

        private string GetActiveWindowTitle()
        {
            return UwpUtils.GetProcessName(GetForegroundWindow());
        }
    }
}

You can download the full code, including the example/test on GitHub.

Talbert answered 6/1, 2018 at 0:33 Comment(3)
This no longer works for the latest Mail app on Windows 10 21H2. There does not seem to be any child window.Cystocarp
@Cystocarp I gave it a try and on my system it shows C:\Program Files\WindowsApps\microsoft.windowscommunicationsapps_16005.14326.20544.0_x64__8wekyb3d8bbwe\HxOutlook.exe as the process, when selecting the Mail App. Do you get an error message or something else?Talbert
I just rerun the code, and it worked correctly. Perhaps I made a mistake.Cystocarp
R
1

You can use GetPackageId() and then PackageFullNameFromId().

E.g.:

HANDLE hProcess = OpenProcess(
    PROCESS_QUERY_LIMITED_INFORMATION,
    false,
    pe32.th32ProcessID);

UINT32 bufferLength = 0;

LONG result = GetPackageId(hProcess, &bufferLength, nullptr);

BYTE* buffer = (PBYTE) malloc(bufferLength);
result = GetPackageId(hProcess, &bufferLength, buffer);

PACKAGE_ID* packageId = reinterpret_cast<PACKAGE_ID*>(buffer);
wprintf(L"Name: %s\n", packageId->name);
Revealment answered 14/8, 2015 at 4:27 Comment(7)
That does not seem to be working. Even doing something as simple as GetPackageId(hProcess, ref len, IntPtr.Zero) will yield APPMODEL_ERROR_NO_PACKAGE on all apps I tried (Windows 10)Nefertiti
Note that maybe I'm getting the process wrong... remember that the process I'm loading is ApplicationFrameHost, so maybe I need to find the "actual" process?Nefertiti
Try getting the process handle with GetProcessHandleFromHwnd and then get the process id with GetProcessId. You can verify the process id you have represents a running process by opening the Task Manager and looking at PID column in the Details tab.Revealment
The question specifies C#, your answer should match.Annihilator
@Annihilator even though you are technically correct, I don't think @kiewic's answer warrants a -1 since my question relates to kernel32.dll, therefore native calls... I would suggest an improvement rather than punish the answer.Nefertiti
kiewic's answer shows one way to get a process' package identity (if it has one). GetPackageFullName[FromToken](), GetPackageFamilyName[FromToken]() and GetApplicationUserModelId[FromToken]() can get a process' package and application identity. See MSDN for more detailsRemotion
The problem is you're looking at a process that's doing work ON BEHALF OF an application, but is not part of the application, i.e. a broker.Remotion
R
1

GetPackageFullName/FamilyName/Id(hprocess,...) etc return APPMODEL_ERROR_NO_PACKAGE if the process has no package identity. Ditto GetApplicationUserModelId(hprocess...) returns APPMODEL_ERROR_NO_APPLICATION because likewise the process has no application identity.

Sounds like you have an HWND for a process that does work on behalf of the application, but is not the application. This is quite common - RuntimeBroker and other processes run as 'Desktop apps' (i.e. process w/o package or application identity) as brokers to do things for application processes which they can't do for themselves.

To your original question, "I'm getting the process ID from the hWnd (the window handle), so I think my problem is actually how to get the "real" process ID from a window handle" this is a fundamentally flawed approach. You have a pid from an HWND, but if the process is a broker it can do work on behalf of multiple applications - the broker process has no identity; it knows *per request/WinRT API call/etc who its caller is and scopes its work to that identity. You can't discover that at the process level.

Remotion answered 15/8, 2015 at 17:30 Comment(1)
You are saying I'm using a fundamentally flawed approach; in the case of Windows 10 apps, all windows (the main window of Mail, Calendar, Store and all windows of Edge) are hosted under ApplicationFrameHost. The objective of my application is to show all opened windows, and for each of them I need the process and icon. And I see that Windows is indeed able to get that information, if you try Win and Tab, as well as run Task Manager, you'll see the icon is indeed associated with the window. So how can I then achieve the same result? (Workaround?)Nefertiti
R
0

So first of all there is a thing called AppUserModelID, it's ID of window that is used by taskbar to group windows. Because all WinRT windows are from same process but they aren't grouped, it means that each app has own UserModelID.

To get UserModelID from HWND you can use method from this answer.

#include "Propsys.h"
#include <propkey.h>

#pragma comment (lib, "Shell32.lib")

//.........

IPropertyStore* propStore;

auto weatherWnd = FindWindow(L"ApplicationFrameWindow", L"Weather");
SHGetPropertyStoreForWindow(weatherWnd, IID_IPropertyStore, (void**)&propStore);

PROPVARIANT prop;
propStore->GetValue(PKEY_AppUserModel_ID, &prop);

And prop will contain value LPWSTR = 0x00838f68 L"Microsoft.BingWeather_8wekyb3d8bbwe!App". This is full entry point name in format <FullPackageFamilyName>!<EntryPoint>. Entry point for launched apps usually called App. Entry points are defined in app manifest.

Also interesting thing - child window that is owned by app is not destroyed, but is moved away from app frame host into desktop window. I don't know why it happens, but you must be careful because FindWindow(nullptr, L"Weather") returned child app window and not appframehost window.

P.S. AppUserModelID is just a string and it's format is not documented, so this method is not exactly the most reliable one.

P.P.S. Also I noticed that you want to have icon and name, you can use PackageManager for that, it requires you to reference winmd assembly, how to do this look here

Robot answered 1/3, 2016 at 0:41 Comment(5)
FYI "AppUserModelID" confusingly exists in 2 ways: 1. Win7: Invented as a string dangling off an HWND. Managed by User32. Used by glomming etc (UI isms) 2. Win8: Invested as a string in a process' token. Managed by the kernel (not merely a UI thing). Used by the appmodel to indicate a process has application identity.Remotion
Packge'd processes have their ApplicationUserModelID* burned into their process token at process creation time. This is part of their DNA and can't be changed, thus it's reliably trustable and used by the appmodel to identify an application. Contract with the Win7 User32 glomming trick which is just a string associated with a window so it's in usermode memory; as it's potentially modifiable it's shouldn't be used for trust or security decisions. Hence the reason we** added AUMID to the process token. There's even a patent based on it*** :-)Remotion
* ApplicationUserModelID aka AppUserModelID aka AUIMD aka AppIdRemotion
** Disclaimer: I work at Microsoft. Seem my bio for what (hint: I've worked on AppX... :-)Remotion
*** Pervasive Package Identifiers -- google.com/patents/US20130062401Remotion
P
0

Below is a similar one for getting the actual process name, Name of process for active window in Windows 8/10

With Spy++ utility, confirmed that Windows.Core.UI.CoreWindow is a child window of Weather and it is the one that we are interested in. (Verified on Win10 10563)

Palpitate answered 6/6, 2017 at 3:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.