11

I'm using automation to test an application, but sometimes I want to start the application via a batch file. When I run "process.WaitForInputIdle(100)" I get an error:

"WaitForInputIdle failed. This could be because the process does not have a graphical interface."

How can I tell if the process has a graphical interface or not?

Lunivore
  • 17,277
  • 4
  • 47
  • 92

5 Answers5

10

See Environment.UserInteractive. That will identify whether the process has an interface at all, e.g. services are not user interactive.

You could also look at Process.MainWindowHandle which will tell you whether there is a graphical interface.

A combination of these two checks should cover all the possibilities.

Paul Ruane
  • 37,459
  • 12
  • 63
  • 82
1

You can simply try and catch the exception:

Process process = ...
try
{
    process.WaitForInputIdle(100);
}
catch (InvalidOperationException ex)
{
    // no graphical interface
}
Dirk Vollmar
  • 172,527
  • 53
  • 255
  • 316
  • Yes, I can... for some reason this seems ugly to me though. Most decent libraries and APIs usually a way of determining whether an operation you're about to perform is valid or not. Microsoft usually provide this. If you don't mind I'll wait a day, see if anyone else has any ideas. – Lunivore Sep 24 '10 at 09:40
  • I agree, its quite ugly to use exceptions like this – Bear Monkey Sep 24 '10 at 09:49
  • @Lunivore, @Bear Monkey: It seems to me to be an exception somewhere in between a [vexing exception and an exogenous exception](http://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx). To avoid the exception being thrown you could P/Invoke the `WaitForInputIdle` function. – Dirk Vollmar Sep 24 '10 at 10:39
  • @Bear Monkey: Good point, though in this specific case you probably won't get any useful information. The [`WaitForInputIdle`](http://msdn.microsoft.com/en-us/library/ms687022%28VS.85%29.aspx) function called under the hood simply returns `WAIT_FAILED` in any of the four error cases. – Dirk Vollmar Sep 24 '10 at 11:16
  • yes your right. I thought it used HRESULTS but it doesn't. I deleted my post before you replied when I realised my mistake. – Bear Monkey Sep 24 '10 at 11:27
1

I was think along the lines of this, Still ugly but trys to avoid exceptions.

Process process = ...

bool hasUI = false;

if (!process.HasExited)
{
    try
    {
        hasUI = process.MainWindowHandle != IntPtr.Zero;
    }
    catch (InvalidOperationException)
    {
        if (!process.HasExited)
            throw;
    }
}

if (!process.HasExited && hasUI)
{

    try
    {
        process.WaitForInputIdle(100);
    }
    catch (InvalidOperationException)
    {
        if (!process.HasExited)
            throw;
    }
}
Bear Monkey
  • 513
  • 2
  • 9
  • Thanks, this works for me, great. I noted that a service can also have a console attached in some conditions, then previous mentioned solutions did not work. But on the other side an EXe can be build as WindowsApplication in build properties, then WaitForInputIdle does not work. The combination of both, MainWindowHandle and WaitForInputHandle does the job well! – b.kiener Jan 04 '22 at 11:52
1

As well as a MainWindowHandle check, one can enumerate the process threads and check if any of them reference a visible window via P/Invokes. This seems to do a good job catching any windows that the first check misses.

private Boolean isProcessWindowed(Process externalProcess)
{
    if (externalProcess.MainWindowHandle != IntPtr.Zero)
    {
        return true;
    }

    foreach (ProcessThread threadInfo in externalProcess.Threads)
    {
        IntPtr[] windows = GetWindowHandlesForThread(threadInfo.Id);

        if (windows != null)
        {
            foreach (IntPtr handle in windows)
            {
                if (IsWindowVisible(handle))
                {
                    return true;
                }
            }
        }
    }

    return false;
}

private IntPtr[] GetWindowHandlesForThread(int threadHandle)
{
    results.Clear();
    EnumWindows(WindowEnum, threadHandle);

    return results.ToArray();
}

private delegate int EnumWindowsProc(IntPtr hwnd, int lParam);

private List<IntPtr> results = new List<IntPtr>();

private int WindowEnum(IntPtr hWnd, int lParam)
{
    int processID = 0;
    int threadID = GetWindowThreadProcessId(hWnd, out processID);
    if (threadID == lParam)
    {
        results.Add(hWnd);
    }

    return 1;
}

[DllImport("user32.Dll")]
private static extern int EnumWindows(EnumWindowsProc x, int y);
[DllImport("user32.dll")]
public static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);
[DllImport("user32.dll")]
static extern bool IsWindowVisible(IntPtr hWnd);
Zachary Canann
  • 1,131
  • 2
  • 13
  • 23
1

Besides the Process.MainWindowHandle, you can improve your solution by reading the PE header of the process main module to detect the process module subsystem. If the subsystem is IMAGE_SUBSYSTEM_WINDOWS_GUI, then the process can have a graphical interface.

Use this class to read the PE header of the process main module: https://gist.github.com/ahmedosama007/bfdb8198fe6690d17e7c3db398f6d725

Use the following code to detect the process module subsystem:

Dim peReader = New PEHeaderReader("C:\Windows\notepad.exe")

Dim subsystem As PEHeaderReader.ImageSubSystem

If peReader.Is32BitHeader Then '32-bit
    subsystem = peReader.OptionalHeader32.Subsystem
Else '64-bit
    subsystem = peReader.OptionalHeader64.Subsystem
End If

'https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header32

If subsystem = PEHeaderReader.ImageSubSystem.IMAGE_SUBSYSTEM_WINDOWS_GUI Then
    Console.WriteLine("GUI")
ElseIf subsystem = PEHeaderReader.ImageSubSystem.IMAGE_SUBSYSTEM_WINDOWS_CUI Then
    Console.WriteLine("Console")
Else
    Console.WriteLine("Other Subsystem")
End If

Console.ReadLine()
Ahmed Osama
  • 348
  • 1
  • 9