0

I am trying to restore a minimized, suspended UWP application from another application, but I cant manage to find a workable solution.

Using .NET Core 2.2 and win32 API via PInvoke

I have tried setting the window placement, setting the window position but nothing works. The only reason I find is that the process that I want to restore is suspended. I did some research and the app is a UWP which is managed by the WinRT stack. sadly I cant find a way to resume the process and display it

Every help or suggestion is appreciated.

enter image description here

        Process proc = Process.GetProcessesByName("HxOutlook").FirstOrDefault();

        if (proc != null)
        {
            ShowWindow(proc.MainWindowHandle, SW_SHOWNORMAL);   // Make the window visible if it was hidden
            ShowWindow(proc.MainWindowHandle, SW_RESTORE);      // Next, restore it if it was minimized
            SetForegroundWindow(proc.MainWindowHandle);         // Finally, activate the window 
        }
ppavlov
  • 378
  • 5
  • 14
  • 1
    Mail is a UWP app. – Daniel A. White May 24 '19 at 19:12
  • No proc is not not and has the correct handle. This code works for non UWP apps such as spotify's client. – ppavlov May 24 '19 at 19:18
  • 2
    when thread suspended - it will be not process windows messages. without this - window state will be not changed. so without first unsuspend process - no solution – RbMm May 24 '19 at 19:22
  • Good catch this is what I suspected, but how can I resume the process ? And what does suspend process mean ? All of its threads are suspended and if yes how can I restore it ? Tried several things such as NtResumeProcess, but nothing worked out. – ppavlov May 24 '19 at 19:25
  • 1
    MainWindowHandle is a .NET thing, the concept does not exist in native Win32. Have you tried calling ShowWindow on the actual HWND of the real application window? Did you try ShowWindowAsync? – Anders May 24 '19 at 19:45
  • I haven't, I thought that MainWindowHandle is pointing to the window handle of the application Window. I will try it out. Just to point out that for other non UWP apps such as spotify the code works fine and the handle is correct. – ppavlov May 24 '19 at 19:49
  • 3
    See this Q&A: [App or Code to Suspend Resume UWP app without Visual Studio](https://stackoverflow.com/q/53788142/1889329). – IInspectable May 24 '19 at 20:53
  • This doesn't work, first of all it resumes the current application and second its bound to uwp apps. I want to resume the process from another app. But still its a good starting point for research thanks. – ppavlov May 25 '19 at 08:53
  • It resumes whichever application you reference. The Windows Runtime types being used all have the `DualApiPartition` attribute, meaning that they can be used from UWP as well as Desktop applications – IInspectable May 25 '19 at 13:41
  • Just launch the app again. UWP uses a single-instance app model, the OS will take care of unsuspending and activating the existing app. – Hans Passant May 25 '19 at 20:09
  • @han: That used to be true, but is no longer the case (see [Create a multi-instance Universal Windows App](https://learn.microsoft.com/en-us/windows/uwp/launch-resume/multi-instance-uwp)). – IInspectable May 26 '19 at 09:56

2 Answers2

1

I don't actually think that it matters that the window is part of a UWP app, exactly.

You can restore Mail's main window via the Win32 call ShowWindow(hwnd, SW_RESTORE). However, the trick is you have to find the correct window. In Spy++ you can see that it is some window that has a window class "ApplicationFrameWindow" that is associated with the process with module name "APPLICATIONFRAMEHOST" -- these must be artifacts of UWP's implementation.

In order to find the particular "ApplicationFrameWindow" that is Mail's main window, without relying on something mutable or ephemeral like window text, I found that the correct window is the owner of one of the windows associated with the HxOutlook.exe process. There may be a less convoluted to do this but the following works. This is a native command line application obviously:

#include <Windows.h>
#include <psapi.h>
#include <tchar.h>
#include <vector>

DWORD GetProcessByName(const TCHAR* target_process_name)
{
    DWORD processes[1024], bytes_returned;

    if (!EnumProcesses(processes, sizeof(processes), &bytes_returned))
        return 0;
    int n = bytes_returned / sizeof(DWORD);
    for (int i = 0; i < n; i++) {
        auto pid = processes[i];
        TCHAR process_name[MAX_PATH] = TEXT("");
        HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
        if (process) {
            HMODULE module;
            DWORD bytes_needed;
            if (EnumProcessModules(process, &module, sizeof(module), &bytes_needed)) {
                GetModuleBaseName(process, module, process_name, sizeof(process_name) / sizeof(TCHAR));
                if (_tcscmp(process_name, target_process_name) == 0)
                    return pid;
            }
        }
    }
    return 0;
}

struct HwndFinder {
    std::vector<HWND> windows;
    DWORD pid;

    HwndFinder(DWORD pid) : pid(pid)
    {}
};

BOOL CALLBACK EnumWindowsFindProcessWindow(HWND hwnd, LPARAM lParam)
{
    DWORD pid;
    HwndFinder* param = reinterpret_cast<HwndFinder*>(lParam);
    GetWindowThreadProcessId(hwnd, &pid);
    if (pid == param->pid) {
        param->windows.push_back(hwnd);
    }
    return TRUE;
}

std::vector<HWND> GetWindowsFromProcessID(DWORD pid)
{
    HwndFinder param(pid);
    EnumWindows(EnumWindowsFindProcessWindow, reinterpret_cast<LPARAM>(&param));
    return param.windows;
}

int main()
{
    auto mail_process = GetProcessByName(TEXT("HxOutlook.exe"));
    auto windows = GetWindowsFromProcessID(mail_process);
    for (auto window : windows) {
        auto owner = GetWindow(window, GW_OWNER);
        if (owner)
            ShowWindow(owner, SW_RESTORE);
    }
    return 0;
}

It's finding the process ID for "HxOutlook.exe", enumerating all the windows in which the WNDPROC for the window runs in a thread owned by that process, and then ShowWindow-ing all windows that own those windows, one of which is the main Mail window.

You could do something like the above via platform invoking, or find a simpler way, or put the code above in a DLL and call into it in C# via DLLImport.

jwezorek
  • 8,592
  • 1
  • 29
  • 46
  • Thank you for your answer it worked wonderfully, never thought that I can do it like this. – ppavlov May 26 '19 at 10:03
  • no problem ... fyi, i actually had a bug in my GetProcessByName() function: wasnt using the bytes_returned value to only look at elements in processes[1024] that were actually returned. It's fixed above. – jwezorek May 27 '19 at 09:16
1

The Issue wasn't in the fact that the process was suspended but that I couldn't get the correct window handle via the C# API (System.Diagnostics.Process). All credit goes to jwezorek, this is the C# implementation. (It has issues, but it works)

    static UInt32 GetProccessByName(string targetProcessName)
    {
        UInt32[] processes = new UInt32[1024];
        UInt32 bytesCopied;

        if (!Psapi.EnumProcesses(processes, (UInt32)processes.Length, out bytesCopied))
        {
            return 0;
        }

        foreach (var pid in processes)
        {
            IntPtr handle = Kernel32.OpenProcess(
              (Kernel32.ProcessAccessFlags.QueryInformation |
                Kernel32.ProcessAccessFlags.VirtualMemoryRead),
                false,
                (int)pid);

            UInt32[] modules = new UInt32[1024];
            UInt32 bytesNeeeded;

            if (handle != null)
            {
                if (Psapi.EnumProcessModules(handle, modules, (UInt32)modules.Length, out bytesNeeeded))
                {
                    StringBuilder text = new StringBuilder(1024);
                    Psapi.GetModuleBaseName(handle, IntPtr.Zero, text, (UInt32)text.Capacity);

                    if (text.Equals(targetProcessName))
                    {
                        return pid;
                    }
                }
            }
        }

        return 0;
    }

    public static bool EnumProc(IntPtr hWnd, ref SearchData data)
    {
        UInt32 pid;

        User32.GetWindowThreadProcessId(hWnd, out pid);

        if(pid == data.PID)
        {
            data.Windows.Add(hWnd);
        }

        return true;
    }

    static List<IntPtr> GetWindowFromProcessID(UInt32 pid)
    {
        var searchData = new SearchData(pid);

        User32.EnumWindows(new User32.EnumWindowsProc(EnumProc), ref searchData);

        return searchData.Windows;
    }

    static void Main(string[] args)
    {
        var pid = GetProccessByName("HxOutlook.exe");

        var windows = GetWindowFromProcessID(pid);

        foreach (var window in windows)
        {
            var owner = User32.GetWindow(window, User32.GetWindowType.GW_OWNER);
            if(owner != null)
            {
                User32.ShowWindow(owner, SW_RESTORE);
            }
        }
    }
ppavlov
  • 378
  • 5
  • 14