6

I am aware that this may on first glance look like a question you have seen before: Knowing when an external process' window shows

But this is slightly different.

I have an C# asp.net web application, for helping people create an installer for their programs. (The developers here are mostly mechanical engineers scripting equations in some calculation tools, they are not software people, so we don't want them spending time learning wix, debugging the installers, maintaing GUID's between releases, and so on..)

The serverside will be running the console application "heat.exe" (a tool that is shipped with the wix tools), to harvest information on how to register dll's etc., if and only if they have a dll in their repository..

I do it like this:

    public int runHeat(string filePath, string outputFile, ref string response)
    {
        response += "run heat.exe to harvest file data" + '\r' + '\n';

        string args = "file " + '"' + filePath + '"' + " -srd -out" + '"' + outputFile + '"';
        string command = Path.Combine(WixBinariesPath, "heat.exe");
        string workPath = Path.GetDirectoryName(filePath);

        StringBuilder outputBuilder;
        ProcessStartInfo processStartInfo;
        Process process;

        outputBuilder = new StringBuilder();

        processStartInfo = new ProcessStartInfo();
        processStartInfo.CreateNoWindow = true;
        processStartInfo.RedirectStandardOutput = true;
        processStartInfo.RedirectStandardInput = true;
        processStartInfo.UseShellExecute = false;
        processStartInfo.WorkingDirectory = workPath;
        processStartInfo.Arguments = args;
        processStartInfo.FileName = command;
        processStartInfo.ErrorDialog = false;

        //create the process handler
        process = new Process();
        process.StartInfo = processStartInfo;

        // enable raising events because Process does not raise events by default
        process.EnableRaisingEvents = true;

        // attach the event handler for OutputDataReceived before starting the process
        process.OutputDataReceived += new DataReceivedEventHandler
        (
            delegate(object sender, DataReceivedEventArgs e)
            {
                // append the new data to the data already read-in
                outputBuilder.AppendLine(e.Data);
            }
        );

        // start the process
        // then begin asynchronously reading the output
        // then wait for the process to exit
        // then cancel asynchronously reading the output
        process.Start();
        process.BeginOutputReadLine();
        process.WaitForExit();

        // use the output
        response += outputBuilder.ToString();

        if (process.ExitCode != 0)
            response += '\r' + '\n' + "heat.exe exited with code: " + process.ExitCode;

        process.CancelOutputRead();
        return process.ExitCode;
    }

I thought this worked.. It passed tests, it's been running for a while without problems, and then suddenly, a developer called, that the webtool I made, no longer produces wix xml for him..

When I logged into the server, I found this dialog:

The runtime error message

and then clicked [OK] - the web application then continued, and produced the xml, and stuff worked..

I have now found the dll that, makes heat throw this error. It doesn't really need registering (typical right?). So I could probably just write a timeout thing, to kill heat.exe if it takes to long, and thus unlock the waiting script, (and basicly fix the issue untill it happens again with a dll that actually needs registering) But that is not really detecting the error, that is just detecting that stuff takes time...

On this error, I would like to continue the script, but present a warning to the user, that heat.exe failed to run on that particular file. But to do this I need my asp.net application to know that this error was invoked, and dispose it, so that the script can continue..

how the *? do I get information that this runtime error occurred, so I can handle it from the server script?

Have you tried using the -sreg command line option to heat?

I now have, and as a result, heat.exe no longer chrashes, but this is not a solution, as heat also avoids harvesting the registry information that I need for autoregistering the dll's shipped with the code in question.

Community
  • 1
  • 1
Henrik
  • 2,180
  • 16
  • 29
  • Use [overload of WaitForExit](https://msdn.microsoft.com/en-us/library/ty0d8k56.aspx) to specify timeout. – Sinatr Apr 13 '15 at 09:06
  • That is in fact a better way to do timeout handling, than what I wrote in the question, but.. Should I kill the process in case of timeouts? – Henrik Apr 13 '15 at 09:15
  • Timeouts, does not excactly solve the issue, as this leaves the error dialog open, for each attempt to run heat with errors. That is a lot of idalogs to accumulate between my .. irregular visits to rdp.. – Henrik Apr 17 '15 at 10:36
  • 1
    Have you tried setting ProcessStartInfo.ErrorDialog = false explicitly? – ChriPf Apr 17 '15 at 10:54
  • I had not, - in fact, I never noticed that setting before?. - Well now I tried it, but it didn't work :/ – Henrik Apr 17 '15 at 11:10
  • A few years ago I worked on a server application that suppressed MessageBoxExA/W and MessageBoxIndirectA/W calls using API / message hooks. Admittedly this was a lot easier from C++, but p/Invokes of SetWindowHookEx, VirtualAlloc, WriteProcessMemory et. al. might help you out. A proces termination like the one you showed will also appear in the NT Event log which, while hacky, would give you something you could monitor for new occurrences if hooking didn't pan out. – ScheuNZ Apr 19 '15 at 03:35
  • 1
    Have you tried using the -sreg command line option to heat? – Matt Johnson Apr 19 '15 at 07:07
  • Is this the same question? http://stackoverflow.com/questions/2959165/detecting-a-message-box-opened-in-another-application – PhilDW Apr 19 '15 at 18:56
  • It should not be, as a message box is invoked from a windows application, and thus should have a handle to it, that can bed tracked by application children, this is a console program, so it has no handle to this, it is an OS error message.. – Henrik Apr 20 '15 at 07:43
  • I have not tried using -sreg (Suppress registry harvesting) - as I thought this would kind of defeat the purpose of using heat to determine what I need to register for auto registering dll's.. (However just did, to prove a point, and.. apparently it still finds the information for the registry entries for the dll's, and now instead of the error message, the code will instead lock the files after generating output.. I'll now look into this, and determine if there is an error in my code..) – Henrik Apr 20 '15 at 07:51

1 Answers1

1

Working with external "uncooperative" executables often requires some trickery. I'd try the following:

  • Start the program on the command line, and check if there is any output when the error occurs. Probably it writes to standard error, and you could use RedirectStandardError, read the stream and hopefully get a clue when the error occurs.

  • Check if there is any logging-possibility in heat.exe that you could enable, and use this to detect the error-case. Maybe a verbose setting, or a log-file...

  • If none of the above worked, I'd use process monitor (e.g. https://technet.microsoft.com/de-at/sysinternals/bb896645.aspx). Start process monitor and then your application and bring it to the point of error. Filter the enormous output in process monitor to just your application (which is still quite a lot) and search at the end, whether there is any access where the programm might log the error. Maybe some log-files, or a logging-service. You could check this file after your timeout.

  • But something the would work in any case is the hack you already suggested in your question. To detect whether the dialog opened. There are also possibilities to browse through the content of the dialog box, so you could also read the text and check which kind of error it is. I used this once in production code to get the progress of an external program, which was written in a text field inside the form. I used spy++ (bundled with Visual Studio) to get the name/id of the text field, and accessed it using the (native) windows API. An ugly hack, but worked fine unless the external program's UI is changed. In your case it is a standard Error Dialog, so it should stay quite consistent.

azt
  • 2,100
  • 16
  • 25
  • Thank you for the pointers for debugging this issue. I'll consider releasing the bounty, if nothing more concrete appears, as there is in fact a lot of use-full data in this answer. Still I was hoping for a more "implemented" answer, like.. an example code that catches this type of error window.. – Henrik Apr 20 '15 at 08:27
  • As posted above, there are many options on how you can get the information back, strongly depending on the external application. If you could narrow it down, we could surely work out a more "implemented" answer. – azt Apr 20 '15 at 08:33
  • program has been run from the command line, there is no output when the error occurs - not in standard output, nor in standard error :/ – Henrik Apr 20 '15 at 08:37
  • According to http://wixtoolset.org/documentation/manual/v3/overview/heat.html there is a verbose switch (-v). You could try that. – azt Apr 20 '15 at 08:39
  • Another thought: You mention that heat.exe always crashes at your customer's. Maybe heat.exe is not installed correctly on that box, or there is some DLL war going on. See: https://msdn.microsoft.com/en-us/library/ms235560%28vs.80%29.aspx – azt Apr 20 '15 at 08:45
  • just tried that, still no output for the files in question ;) – Henrik Apr 20 '15 at 08:48
  • Oh, heck... you get the bounty, I'm gonna do the hack, and get some coffee to help swallow my pride... can you throw me a bone, and upload a snippet with the mentioned hack please? – Henrik Apr 20 '15 at 08:59
  • And what if we stop the dialog from appearing? Maybe this helps: http://stackoverflow.com/questions/735170/can-the-application-error-dialog-box-be-disabled – azt Apr 20 '15 at 09:00
  • I am not sure that running `drwtsn32.exe -i` is the way to go on this webserver. An without control of the code, I cannot do it from within heat :/ - otherwise good idea – Henrik Apr 20 '15 at 09:06
  • 1
    You could poll the process.MainWindowHandle and see if it gets different from 0, when the dialog shows up. If it doesn't work, you could try it with a windows hook like they did here: http://stackoverflow.com/questions/11285928/detecting-a-modal-dialog-box-of-another-process Edit: Maybe better to first check it with Spy++, to make sure the Dialog belongs to the heat-process and not to any other windows process. – azt Apr 20 '15 at 09:11