2

I'm running an automated test of an application that has insufficient dependencies (missing dll), and got this error message:

The program can't start because CUDAPhysicalSystem.dll is missing from your computer. Try reinstalling the program to fix this problem.

When the test runner kills the application, the above message box isn't disappearing. Apparently it's owned by csrss.exe, which can't be killed.

My problem is that the only way I can get this message box to close is by manually logging in to to the machine and clicking the X on that message window. Is there another way?

Note that I can easily fix the dependency error, but I would like a robust way of terminating the application under test with all its messages when such errors are encountered.

liorda
  • 1,552
  • 2
  • 15
  • 38

2 Answers2

3

Try avoiding the dialog in the first place: Call SetErrorMode(SEM_FAILCRITICALERRORS) in your test runner, this will cause calls to CreateProcess to fail silently if the loader cannot resolve every import.

If you want to create a query function so you can tell if the process can load, add the CREATE_SUSPENDED flag when calling CreateProcess and call TerminateProcess if it succeeded.

Anders
  • 97,548
  • 12
  • 110
  • 164
  • yes, the `SetErrorMode(SEM_FAILCRITICALERRORS)` is good solution. but use `CREATE_SUSPENDED` here no sense - in any case, if application path correct and point to valid exe file - `CreateProcess` return *TRUE* even if some dependencies dlls missing. for determinate this - we can wait process exit and call `GetExitCodeProcess` - in case dll missing will be `STATUS_DLL_NOT_FOUND` exit code. of course this exit code can be and in some another case too. – RbMm Jun 11 '17 at 14:20
  • Debugging the process is probably better if you don't actually want to execute the application, if it gets to the entrypoint function or TLS callback everything is OK. – Anders Jun 11 '17 at 14:52
  • Debugging - yes, this is also possible and not hard. even until process exit. with this also can log different errors. however most simply solution for suppress hard error - how you suggest - `SetErrorMode(SEM_FAILCRITICALERRORS)` – RbMm Jun 11 '17 at 14:55
1

you can do next:

1) get csrss.exe process id in your session

2) enumerate windows via EnumWindows, for every window query it process id (GetWindowThreadProcessId), compare it with csrss.exe process id. if equal - get window class name (GetClassName ) and if it is L"#32770" - post WM_CLOSE message to this window

3) if you want be more precise - after you found L"#32770" window class, query the window caption text via GetWindowText and ensure that it begin with "module.exe - " or how your exe is named ?

struct FIND_WND_CONTEXT 
{
    PCWSTR szCaptionBegin;
    ULONG lenCaptionBegin;
    ULONG dwProcessId;
};

BOOL CALLBACK EnumWndProc(HWND hwnd, FIND_WND_CONTEXT& fwc)
{
    ULONG dwProcessId;
    if (GetWindowThreadProcessId(hwnd, &dwProcessId) && dwProcessId == fwc.dwProcessId)
    {
        PWSTR Name = (PWSTR)alloca( max(fwc.lenCaptionBegin * sizeof(WCHAR), sizeof(L"#32770") + sizeof(WCHAR)) );

        if (GetClassNameW(hwnd, Name, sizeof(L"#32770") + sizeof(WCHAR)) && !wcscmp(Name, L"#32770"))
        {
            if (GetWindowText(hwnd, Name, fwc.lenCaptionBegin))
            {
                _wcslwr(Name);
                if (!wcscmp(Name, fwc.szCaptionBegin))
                {
                    PostMessage(hwnd, WM_CLOSE, 0, 0);
                }
            }
        }
    }

    return TRUE;
}

void CloseTest()
{
    const WCHAR module_exe[] = L"module.exe - ";
    FIND_WND_CONTEXT fwc = { module_exe, RTL_NUMBER_OF(module_exe) };

    if (fwc.dwProcessId = GetMySessionCsrssId())
    {
        EnumWindows((WNDENUMPROC)EnumWndProc, (LPARAM)&fwc);
    }
}

ULONG GetMySessionCsrssId()
{
    ULONG cb = 0, rcb = 0x10000;
    static volatile UCHAR guz;
    PVOID stack = alloca(guz);

    union {
        PVOID buf;
        PBYTE pb;
        PSYSTEM_PROCESS_INFORMATION pspi;
    };

    ULONG SessionId;
    ProcessIdToSessionId(GetCurrentProcessId(), &SessionId);

    NTSTATUS status;
    do 
    {
        if (cb < rcb)
        {
            cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
        }

        if (0 <= (status = ZwQuerySystemInformation(SystemProcessInformation, buf, cb, &rcb)))
        {
            ULONG NextEntryOffset = 0;
            do 
            {
                pb += NextEntryOffset;

                STATIC_UNICODE_STRING(csrss, "csrss.exe");

                if (pspi->SessionId == SessionId && RtlEqualUnicodeString(&pspi->ImageName, &csrss, TRUE))
                {
                    return PtrToUlong(pspi->UniqueProcessId);
                }

            } while (NextEntryOffset = pspi->NextEntryOffset);

            return 0;
        }

    } while (status == STATUS_INFO_LENGTH_MISMATCH);

    return 0;
}

somebody of course ask - why use "undocumented", ("no longer available" (this is direct lie in msdn - available from win200 to latest win10 1703), "unsupported", etc) ZwQuerySystemInformation with SystemProcessInformation instead CreateToolhelp32Snapshot + Process32First + Process32Next ? because we need got SessionId information for process. the SYSTEM_PROCESS_INFORMATION containing ULONG SessionId member, while psapi shell over this function by unknown reason drop this member - PROCESSENTRY32 - no here SessionId - it dropped. of course we can retrieve SessionId by addition call to ProcessIdToSessionId, but this function internally open process by Id, for query it SessionId. but for open csrss.exe you need SeDebugPriviledge enabled in your toke (+ 3 extra call to kernel - open process, query information, close handle)

RbMm
  • 31,280
  • 3
  • 35
  • 56