0

When you try to run a program on Windows, and the loader can't find all of the required DLLs, the default behavior is to pop up a dialog box that describes the problem, including both the name of the program and the name of (one of the) missing DLLs. The process then hangs until someone clicks OK, and then exits with an error code. Here's an example of this dialog box:

Example of the dialog box that Windows shows by default when a required DLL is not available. The dialog box title reads "cl.exe - System Error" and the message in the box reads "The program can't start because mspdb80.dll is missing from your computer.  Try reinstalling the program to fix this problem."

Now suppose you're scripting some automated process that might fail for this reason, e.g. running CI tests after installation, where part of the point is to make sure the installer installs all the DLLs. You don't want your build workers to hang waiting for someone to click on a dialog box that's being displayed on a monitor that the computer physically does not have because it's in a server rack somewhere. You want the test cycle to stop immediately and the details to get written to the log.

Your build driver can disable this dialog box for itself and all its child processes (assuming nobody uses CREATE_DEFAULT_ERROR_MODE) by calling SetErrorMode:

SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
hProc = CreateProcess(...);

However, this only solves half of the problem. The offending process will terminate with an exit status of 0xC0000135 (STATUS_DLL_NOT_FOUND), but the name of the problem executable and the name of the missing DLL are not reported anywhere that I can find it.

So here's the actual question: From code running in the build driver, how do I get the name of the problem executable and the name of the missing DLL, so that I can write them to my CI build logs? Anything goes, except that the code of the problem executable itself and its DLLs cannot be modified (because this is supposed to be a general solution) and an approach that does not require elevated privileges is strongly preferred.

(This is a follow-up question for Suppress "The program can't start because X.dll is missing" error popup . I've been vaguely meaning to write it for years now.)

zwol
  • 135,547
  • 38
  • 252
  • 361
  • Short answer - you can't . That information is simply not available to the process that calls CreateProcess. Unless it monitors the file system, like SysInternals ProcessMonitor does, to see every attempt to load every file and can then see errors like "not found". – Remy Lebeau Dec 11 '19 at 15:29
  • @RemyLebeau Are you _certain_ that there is no way to get the `LoadLibrary` implementation to, for instance, take the information that it would have put into the dialog box, and instead write it to the standard error handle or the system logs or something like that? And also no way to intercept the dialog box using, I dunno, the accessibility API maybe, scrape out its contents and then dismiss it? – zwol Dec 11 '19 at 16:35
  • `LoadLibrary()` is not a factor. The error dialog is from from the OS Loader itself while loading the EXE into memory and resolving its DLL dependencies, before the spawned process begins running. If you want to intercept the dialog, you could try installing a global hook via `SetWindowsHookEx()` before calling `CreateProcess()`, but then you are responsible for scraping the contents of the dialog yourself, and dismissing it. There is no API to directly deliver to you the info that you seek. You will have to hunt for it. – Remy Lebeau Dec 11 '19 at 18:28
  • Possible duplicate of [CreateProcess STATUS_DLL_NOT_FOUND - which dll?](https://stackoverflow.com/questions/18366433/) – Remy Lebeau Dec 11 '19 at 18:28
  • You need to enable the [show loader snaps](https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/show-loader-snaps) GFlags option, and run your process under a debugger. I'm sure there is at least one debugger that will allow you to dump its console output to a file. In case I'm wrong you'd have to implement your own. – IInspectable Dec 11 '19 at 21:05
  • CDB and NTSD allow you to specify a log file for output (see [CDB Command-Line Options](https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/cdb-command-line-options)). – IInspectable Dec 11 '19 at 21:22
  • @IInspectable That sounds like the bones of an answer, but there isn't just one program of concern; there could be dozens, and they aren't necessarily all started directly by the build driver. I would need a way to ensure that every process started as a descendant of the build driver is run under the same instrumentation, and all debugger actions would need to be fully automated, and the entire thing would need to not interfere with normal execution of any of the programs involved. – zwol Dec 12 '19 at 15:52
  • @IInspectable Also, if I'm reading the MSDN page you linked to correctly, your suggestion involves setting flags in the PE header of each executable that needs monitoring? That's not going to work, because I don't necessarily know which executables they are, and also the build-and-test process may intentionally not have write privileges on installed executables. – zwol Dec 12 '19 at 15:54
  • @RemyLebeau Yes, I agree that this is the same question as that. – zwol Dec 12 '19 at 18:26
  • GFlags are set in the system; you don't need to touch any of your PE images for this to work. [Debugging LoadLibrary Failures](https://blogs.msdn.microsoft.com/junfeng/2006/11/20/debugging-loadlibrary-failures/) has a bit of information on how to enable it, as well as showing sample output, incidentally using CDB. I don't know, how loader snaps (once enabled) are delivered. If you are concerned with a debugger interfering, try if [DebugView](https://learn.microsoft.com/en-us/sysinternals/downloads/debugview) can capture loader snaps traces as well. – IInspectable Dec 12 '19 at 18:52

1 Answers1

0

Even though it's a little overkill, you could write a program/script that parses the PE format of each executable, walks its import table to enumerate imported modules, and checks to see if each module exists on the system (i.e. is it in \Windows\System32, the current directory, etc).

Then, if the executable in question is missing an import, log the executable and the missing import somewhere (and don't run the executable, of course).

If you want to do this in Python, the pefile module is excellent (https://pypi.org/project/pefile/).

(Update: This won't work for DLLs that are imported via LoadLibrary)

danielehmig
  • 116
  • 2