Execute and Wait not working sometimes - delphi

I am using this code I found on the Internet and on some devices it waits, but on others it does not. Can someone please explain where I am going wrong. My app loads in Truecrypt and then waits for the user to enter the password. On exiting Truecrypt, it then launches my menu-program.
My Lenovo Miix 2 8" tablet, win8.1 (all up to date) will wait, my Dad's win8.0 (all up to date) will wait, but my friend's ASUS M80TA 8" win8.1 tablet (all up to date) will not. Another friend's win7 laptop (all up to date) does not wait ether.
var
aTSI : TStartupInfo;
aTPI : TProcessInformation;
iRet : Integer;
ExitCode: Cardinal;
begin
FillChar(aTSI, SizeOf(aTSI), #0);
FillChar(aTPI, SizeOf(aTPI), #0);
aTSI.CB:=SizeOf(aTSI);
if not CreateProcess(nil, PChar(sEXE), nil, nil, False,
NORMAL_PRIORITY_CLASS,
nil, nil, aTSI, aTPI) then
RaiseLastWin32Error;
repeat
iRet:=MsgWaitForMultipleObjects(1, aTPI.hProcess,
False, INFINITE, (QS_ALLINPUT));
if iRet <> (WAIT_OBJECT_0) then
Application.ProcessMessages;
until iRet = (WAIT_OBJECT_0); // use this for normal programs
ExitCode:= 0;
if not GetExitCodeProcess(aTPI.hProcess, ExitCode) then
RaiseLastWin32Error;
Result:= ExitCode;
CloseHandle(aTPI.hProcess);
end;

The likely explanation is as follows:
You call CreateProcess which creates a new process and returns a handle to that process.
That first new process in turn starts a different process, and immediately returns. That second process is the one that you see, and believe to be the process that you created.
Your wait on the first process handle returns.
In order to know how to deal with this you'd need to supply some details about the process that you are attempting to start. As to why the code works on some machines and not others, that would likely be down to the implementation details of the target application, the external application that you are starting. Presumably it differs from machine to machine.
Looking at the code, it always leaks the thread handle returned in aTPI.hThread. And it leaks aTPI.hProcess if GetExitCodeProcess fails.
You also need to ensure that the string you pass to the command line argument of CreateProcess is an editable string, and not a literal that is stored in read-only memory.
It is also pointless to initialise ExitCode and then immediately overwrite it. What's more you can remove the ExitCode variable and pass Result directly to GetExitCodeProcess.
Your code also fails to acknowledge an error being returned by the wait function.
I'd probably write it like this:
function ExecAndWait(CommandLine: string): DWORD;
var
si: TStartupInfo;
pi: TProcessInformation;
iRet: Integer;
begin
UniqueString(CommandLine);
si := Default(TStartupInfo);
si.cb := SizeOf(si);
Win32Check(CreateProcess(nil, PChar(CommandLine), nil, nil, False,
NORMAL_PRIORITY_CLASS, nil, nil, si, pi));
CloseHandle(pi.hThread);
try
while True do
begin
iRet := MsgWaitForMultipleObjects(1, pi.hProcess, False, INFINITE, QS_ALLINPUT);
Win32Check(iRet <> WAIT_FAILED);
case iRet of
WAIT_OBJECT_0:
break;
WAIT_OBJECT_0+1:
Application.ProcessMessages;
end;
end;
Win32Check(GetExitCodeProcess(pi.hProcess, Result));
finally
CloseHandle(pi.hProcess);
end;
end;
On my machine, when I pass 'notepad.exe' to this function, the function does not return until the Notepad process is closed.
On the other hand, if I pass 'explorer.exe' to the process, then the function returns immediately. What happens here is that a new explorer process starts, but it detects that one is already running, and asks that process to open a new window. The newly started explorer process immediately terminates.

Related

unelevated program starts an elevated updater, updater should wait for finishing of program

I have 2 apps, program.exe and updater.exe, both written in Delphi5.
Program runs without admin-rights (and without manifest), updater has a manifest with "requireAdministrator" because he must be able to write at Program-Folder to update program.exe.
The problem is to launch updater and let him wait until program is closed.
I've found different ways at the web, but none works (in most cases the 1st app starts 2nd app and wait for ending of 2nd app, in my case 2nd app should wait for ending of 1nd app).
Updater should wait, thats easy
updater.exe
{$R manifest.res}
label.caption:='Wait for program.exe closing';
repeat
sleep(1000);
until File is not open
ProgramHandle := Read Handle from File
WaitForSingleObject(ProgramHandle,INFINITE);
label.caption:='program.exe CLOSED';
Do updates
Way 1
Starting updater with CreateProcess:
program.exe
FillChar(siInfo, SizeOf(siInfo), 0);
siInfo.cb := SizeOf(siInfo);
saProcessAttributes.nLength := SizeOf(saProcessAttributes);
saProcessAttributes.lpSecurityDescriptor := nil;
saProcessAttributes.bInheritHandle := TRUE;
saThreadAttributes.nLength := SizeOf(saThreadAttributes);
saThreadAttributes.lpSecurityDescriptor := nil;
saThreadAttributes.bInheritHandle := True;
if CreateProcess(nil,
PChar('updater.exe'),
#saProcessAttributes,
#saThreadAttributes,
TRUE, NORMAL_PRIORITY_CLASS, nil,
PChar(ExtractFilePath(Application.ExeName)),
siInfo, piInfo) then
begin
DuplicateHandle(GetCurrentProcess, GetCurrentProcess,
piInfo.hProcess, #MyHandle,
PROCESS_QUERY_INFORMATION, TRUE,
DUPLICATE_SAME_ACCESS) then
Write MyHandle in a File
end;
Close program
Doesn't do anything, works only when updater has no manifest with requireAdministrator into.
If i run program with explizit admin-rights, it works too.
Way 2
Starting updater with ShellExecuteEx:
program.exe
FillChar(Info, SizeOf(Info), Chr(0));
Info.cbSize := SizeOf(Info);
Info.fMask := SEE_MASK_NOCLOSEPROCESS;
Info.lpVerb := PChar('runas');
Info.lpFile := PChar('update.exe');
Info.lpDirectory := nil;
Info.nShow := SW_RESTORE;
ShellExecuteEx(#Info);
MyHandle:=OpenProcess(PROCESS_ALL_ACCESS, False, GetCurrentProcessId())));
Write MyHandle in a File
Close program
Doesnt' work, MyHandle has a different value each time i run this procedure (without restarting the program), so updater can't work with it.
So i have no idea how to start updater.exe and write the handle of program.exe in the file.
Im not very familiar with these parts of programing ... does somebody has an idea for my proplem?
Your code is not working because the handle table is per process, which means that the second process could have the same handle pointing to another kernel object. Below, there is one of many possible solutions:
When creating the process 2, pass the PID of the process 1 as parameter:
procedure CreateUpdater;
var
Info: TShellExecuteInfo;
begin
FillChar(Info, SizeOf(TShellExecuteInfo), 0);
Info.cbSize := SizeOf(TShellExecuteInfo);
Info.fMask := SEE_MASK_NOCLOSEPROCESS;
Info.lpVerb := PChar('runas');
Info.lpFile := PChar('Update.exe');
Info.lpParameters := PChar(IntToStr(GetCurrentProcessId));
Info.lpDirectory := nil;
Info.nShow := SW_RESTORE;
ShellExecuteEx(#Info);
//NOTE: MISSING ERROR CHECKING!
end;
Inside the Updater, wait for the process1 to terminate:
procedure WaitForAndClose;
var
PID: String;
AHandle: Cardinal;
Ret: longbool;
ExitNumber: DWORD;
begin
PID:= ParamStr(1);
if PID <> '' then
begin
AHandle:= OpenProcess(PROCESS_QUERY_INFORMATION, False, StrToInt(PID));
//NOTE: MISSING ERROR CHECKING!
try
repeat
Ret:= GetExitCodeProcess(AHandle, ExitNumber);
//NOTE: MISSING ERROR CHECKING!
Sleep(1000); //define a time to poolling
until (ExitNumber <> STILL_ACTIVE);
finally
CloseHandle(AHandle);
end;
//Terminate the process;
Application.Terminate;
end;
end;
You can also use WaitForSingleObject to avoid polling:
WaitForSingleObject(AHandle, INFINITE);
//NOTE: MISSING ERROR CHECKING!
But you need the SYNCHRONIZE access to open the process:
AHandle:= OpenProcess(SYNCHRONIZE, False, StrToInt(PID));
//NOTE: MISSING ERROR CHECKING!
Note: There is no error checking here. You should read the docs and properly check for errors.
Note 2: I would like to get your attention to the fact you are leaking a handle.
When you use SEE_MASK_NOCLOSEPROCESS the caller is responsible to close the handle of the calee. In your case I think you don't need that mask at all. I would remove it.
Here is a basic example of how to achieve this using events:
program.exe:
// manual-reset event, non-signaled
Event := CreateEvent(nil, True, False, 'MyUniqueName');
ExecuteUpdater; // via ShellExecuteEx with runas
// synchronize - wait for the event to be signaled
WaitForSingleObject(Event, INFINITE);
// WAIT_OBJECT_0 = The state of the specified object is signaled.
CloseHandle(Event);
updater.exe:
Event := CreateEvent(nil, True, False, 'MyUniqueName');
if Event = 0 then RaiseLastWin32Error;
SetEvent(Event); // sets the event object to the signaled state
CloseHandle(Event);
You should also add a manifest to program.exe (requestedExecutionLevel should be level="asInvoker") to avoid virtualization.
I see the main problem there in indeterminate order of two events: closing of the program and starting of the updater main code.
One possible way to fix it would be using Events - https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms686670(v=vs.85).aspx
The program creates the Event (with an option for the children to inherit it), then launches the updater (passing handles of both the Event and the program's process as integers via the command line to it !), then freezes in WaitForSingleObject on the Event.
This ensures the program would not exit before the updater would be ready to monitor it, so PID would not get invalid.
The updater then calls OpenProcess on the program's PID gained from the command line, then calls SignalAndWait both knocking the Event (gained from the command line) and freezing upon the handle (gained from OpenProcess) - https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms686293(v=vs.85).aspx
The program, now being released from waiting upon Event, terminates.
The termination of the process is signalling it, so now the updater gets released in turn and can start doing the main work.
Another approach suggested at C++, How to determine if a Windows Process is running? is querying the exit code of the program ProcessID - it is said that while the program is still running there would be a specific error code and you can Sleep(100) then try again. Any other result means the program already had finished.
The program exits immediately after launching the updater without waiting for it to starting monitoring.
This seems nice approach except that I do not now any warranty that PID values would not be reused. Chances are infinitesimal, but still not zero.
Personally I would probably use a flag file. The CreateFile API has a very interesting flag - the temporary-file mode. It means, Windows would automatically delete the file after process ends. So then
The program creates a new GUID using Windows API (or a new Random Value using Crypto API).
The program creates in the temporary files folder the temporary-mode file with the name based upon the GUID or the Random value. If by any wild luck such a file already exist - you just obtain a new GUID or Random value.
The program launches the updater and passes the filename to it via the command line
The program exits immediately after launching the updater without waiting for it to starting monitoring.
The updater keeps checking if the file exists (making pauses between attempts). When the file does not more exist - that means the program had finished and Windows auto-deleted it.
Again, there is an infinitesimal chance that some other process would create the flag file with exactly the same name in-between the program terminates and the updater checks again, but that is next to impossible in practice

Delphi service always runs at 15% processor load

I just encountered a problem with a service I wrote some time ago. This service is meant to translate various programs by calling a multilizer application via CreateProcess. The problem is that once the checks of the given directories cleared and the actual translation method is called the cpu load will rise to about 15-20% and remain at this level even if all files have been processed. The OnStart-event of the service creates a non delayed thread which has an execute-method that looks like this:
procedure TTranslateThread.Execute;
var count : integer;
begin
sleep(ServiceObject.hInterval);
while not Terminated do
begin
Inc(Count);
if Count>= 10 then
begin
Count :=0;
if ServiceObject.CheckDirCount>0 then
begin
ServiceObject.TranslateService;
sleep(ServiceObject.hInterval);
end;
end;
end;
However I suppose the main cause of the problem lies in the way I have to call the multilizer. That is because the service has to wait for the multilizer to finish translating. I used WaitForSingleObject to wait for the multilizer to finish although I know it's kind of a bad idea. This is the method that calls the multilizer:
procedure WaitForML7(hName: string);
var
si: TStartupInfo;
pi: TProcessInformation;
hCreateOK: Boolean;
AParameterFinal,AFileName: String;
begin
AFileName := hMultilizerPath+'Ml7Build.exe';
AParameterFinal := 'b '+hName+'.exe.m7p';
FillChar(si,SizeOf(TStartupInfo),#0);
FillChar(pi,SizeOf(TProcessInformation),#0);
si.cb := SizeOf(TStartupInfo);
AParameterFinal := Format ('"%s" %s', [AFilename, TrimRight(AParameterFinal)]);
slog.Info('CreateProcess wird versucht');
hCReateOK := CreateProcess(nil,PChar(AParameterFinal), nil, nil, false, CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, nil,
PChar(hMultilizerPath) ,si,pi);
if hCreateOK then
begin
slog.Error('Multilizeraufruf war erfolgreich für Prg: '+hName);
WaitForSingleObject(pi.hProcess,INFINITE);
end
else
begin
slog.Error('Aufruf war nicht erfolgreich -> keine Uebersetzung');
end;
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
end;
I don't really understand why the processor load remains high even there is nothing more to do. Thanks in advance.
Your thread runs a busy loop.
while not Terminated do
begin
Inc(Count);
if Count>= 10 then
begin
Count :=0;
if ServiceObject.CheckDirCount>0 then
begin
ServiceObject.TranslateService;
sleep(ServiceObject.hInterval);
end;
end;
end;
Note that I've added the missing end from your code. I hope that's the only error you made, because obviously when you post code that is not the real code, it's plausible that the problem lies in the real code rather than the code you posted.
Anyway, suppose that CheckDirCount always evaluates to 0, then the loop looks like this:
while not Terminated do
begin
Inc(Count);
if Count>= 10 then
begin
Count :=0;
ServiceObject.CheckDirCount;
end;
end;
That is a busy loop. When you run a busy loop, the processor gets hot.
I don't really want to propose solutions to this because I don't know any of the details of what your program is doing. I'm just trying to answer the question of why your thread consumes CPU.
Your wait loop will run without waiting within a Sleep(), so will burn some CPU until Sleep() is reached. This is not a good implementation pattern.
A common solution is to use semaphores to notify the thread to wake up and handle the pending tasks. It won't use any CPU until the semaphore (e.g. TEvent) is triggered.
Take a look at TEvent documentation.
For more complex multi-thread content, consider using a dedicated library like OmniThreadLibrary. It features high-level lists and parallel processing, tuned and stable. Using such a library will save you plenty of time and money. Multi-threading programming can be very difficult to stabilize, so OmniThreadLibrary is worth a look.
In addition to David's answer:
your main execute block can be simplified, doing Sleep unconditionally to prevent a busy loop:
procedure TTranslateThread.Execute;
begin
while not Terminated do
begin
sleep(ServiceObject.hInterval);
if ServiceObject.CheckDirCount>0 then
ServiceObject.TranslateService;
end;
end;
Please note that you can avoid the sleep loop by using Shell notification events.

How do I wait to delete a file until after the program I started has finished using it?

I have been looking for a way to open a file saved to my computer via a Delphi app with its appropriate application. The file is stored in a Varbinary field in a SQL database, and is loaded into a memory stream and then saved via the TMemoryStream's SavetoFile method. What I would like to accomplish is to open the saved file in its appropriate application without knowing the filepath to that application's executable. I have had some success using ShellExecuteEx, but certain applications don't return an HProcess (Windows Live Photo Gallery, for example), so I can't (or at least don't know how to) wait for the application to close before moving on when a handle isn't returned. Is there a way to ensure I receive a handle when calling ShellExecuteEx? If this is not the best way how should I go about doing this?
I only need to know the external app's status because I plan on deleting the file after it closes, and I only need to write it because I'm fairly certain I can't load the file stored in the SQL table into memory (by way of a MemoryStream, FileStream, etc.) and launch its associated program directly from my Delphi app. (I've asked about that separately.)
Trying to detect that the displaying process has closed is brittle and fraught with problems, as you learnt in your previous question. Often times, it's hard to find the process that is used to view the file, and even if you can, there's no certainty the closing the view of the file will close the process. The process may be used to view other files which the user leaves open. I think the lesson that you should take from that is that the system does not want you to do what you are trying to do.
So, what's the better way to solve the problem? I think the best you can do is to create the temporary files in the temporary directory and not attempt to delete them when the user has finished with them. You could:
Remember the files you created and when you create, say the 21st file, delete the first one you made. Then delete the 2nd when you create the 22nd and so on.
Or, delete all temporary files on startup. This would remove files from a previous session.
Or run a separate tidy up thread that, every ten minutes, say, deleted files that were created more than an hour ago.
You get the idea. The point is that it is an intractable problem to detect when the viewer has finished with the file, in full generality. So you need to think creatively. Find a different way around the road block.
Hers a snip from a unit I use for a similar purpose. I found these functions online somewhere over the the years so I take no credit and make no promises.
I personally use the WaitExec() function to launch a pdf (retrieved from a database) in Acrobat for editing and then re-save it to our database when done.
I have used the two other functions at other times as well so I know they all work to one degree or another but I think WaitExec() worked best in an interactive mode, while Launch() worked better from a thread or non-interactive mode.
The IsFileInUse function can tell you if the file you created is in use by any other processes and may be a viable option as well.
uses SysUtils, Windows, ShellAPI, Forms, Registry, Classes, Messages, Printers,
PSAPI, TlHelp32, SHFolder;
function IsFileInUse(fName: string): boolean;
var
HFileRes: HFILE;
begin
Result := False;
if not FileExists(fName) then
Exit;
HFileRes := CreateFile(pchar(fName), GENERIC_READ or GENERIC_WRITE,
0 {this is the trick!}, nil, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, 0);
Result := (HFileRes = INVALID_HANDLE_VALUE);
if not Result then
CloseHandle(HFileRes);
end;
function Launch(sCommandLine: string; bWait: Boolean; AppHandle: HWND): Boolean;
var
SEI: TShellExecuteInfo;
Mask: Longint;
begin
Mask := SEE_MASK_NOCLOSEPROCESS;
FillChar(SEI, Sizeof(SEI), #0);
SEI.cbsize := Sizeof(SEI);
SEI.wnd := AppHandle;
SEI.fmask := Mask;
//if FExeStyleString<>'' then SEI.LPVERB:=pchar(FExeStyleString);
SEI.LPFile := pchar(sCommandline);
//SEI.LPParameters := pchar(FExeParameters);
//SEI.LPDirectory := pchar(FExepath);
SEI.nshow := SW_SHOWNORMAL; // SW_SHOWMINIMIZED, SW_SHOWMAXIMIZED
ShellexecuteEx(#SEI);
if bWait then
WaitforSingleObject(SEI.hProcess, INFINITE);
Result := True;
end;
function WaitExec(const CmdLine:AnsiString;const DisplayMode:Integer):Integer;
{Execute an app, wait for it to terminate then return exit code. Returns -1
if execution fails. DisplayMode is usually either sw_ShowNormal or sw_Hide.}
var
S:TStartupInfo;
P:TProcessInformation;
M:TMsg;
R:DWord;
begin
FillChar(P,SizeOf(P),#0);
FillChar(S,Sizeof(S),#0);
S.cb := Sizeof(S);
S.dwFlags := STARTF_USESHOWWINDOW;
S.wShowWindow := DisplayMode;
if not CreateProcess(nil,
PChar(CmdLine), { pointer to command line string }
nil, { pointer to process security attributes }
nil, { pointer to thread security attributes }
False, { handle inheritance flag }
CREATE_NEW_CONSOLE or { creation flags }
NORMAL_PRIORITY_CLASS,
nil, { pointer to new environment block }
nil, { pointer to current directory name }
S, { pointer to STARTUPINFO }
P) { pointer to PROCESS_INF }
then begin
ShowMessage('Create Process failed. Save this message for IT: ' + CmdLine);
Result:=-1
end
else begin
// WaitforSingleObject(P.hProcess,INFINITE);
// The following replacement better satisfies DDE requirements
repeat
R := MsgWaitForMultipleObjects(1, // One event to wait for
P.hProcess, // The array of events
FALSE, // Wait for 1 event
INFINITE, // Timeout value
QS_ALLINPUT); // Any message wakes up
if R>WAIT_OBJECT_0 then begin
M.Message := 0;
while PeekMessage(M,0,0,0,PM_REMOVE) do begin
TranslateMessage(M);
DispatchMessage(M);
end
end;
until R=WAIT_OBJECT_0;
// put value into Result.... non zero = success
GetExitCodeProcess(P.hProcess,DWord(Result));
CloseHandle(P.hProcess);
CloseHandle(P.hThread);
P.hProcess:=0;
P.hThread:=0;
end;
end;

CreateProcess and get the handle

I'm using CreateProcess API to integrate RealVNC with my exe... I just need to process handle for the created vnc client, but I'm unsuccess so far. The code is pretty simple:
procedure TForm1.VncAuth;
var
StartInfo: TStartupInfo;
ProcInfo: TProcessInformation;
CmdLine: string;
title: string;
ProcHandle: THandle;
begin
FillChar(StartInfo,SizeOf(TStartupInfo),#0);
FillChar(ProcInfo,SizeOf(TProcessInformation),#0);
StartInfo.cb := SizeOf(TStartupInfo);
CmdLine:= 'vnc.exe';
UniqueString(CmdLine);
CreateProcess(NIL ,PChar(CmdLine), NIL, NIL, False, CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS
, NIL, NIL, StartInfo, ProcInfo);
ProcHandle:= ProcInfo.hProcess;
GetWindowText(ProcHandle, PChar(title), 255);
ShowMessage(title);
end;
Nothing is returned in title var... the GetWindowText function is just a test to see if I have the right handle, if Yes I should see the vnc client title's right?
Thank you!
Window handles and process handles are not the same thing. For GetWindowText you need a window handle.
After creating the process call WaitForInputIdle to allow the process to start up and create its main window.
Call EnumWindows to enumerate the top level windows.
For each top level window, call GetWindowThreadProcessId to find out the process ID of the process that created that window. The process ID of the process you created is ProcInfo.dwProcessId.
When you have find a window with process ID that matches that of the process you just created, that window is your guy!

Start long running background process and check the status

I want to start a potentially long running background process from Delphi. I want to leave the process running independently, but first I want to check that the process started OK.
If anything went wrong on startup, I want to capture any output written to standardErr and log it. If the background process starts OK, my program needs to be able to exit and leave the spawned process running.
The psuedo code would be something like this:
process:=RunProgramInBackground('someCommand.exe');
sleep(1000); // Wait a bit to see if the program started OK
if process.Finished and process.ExitCode=FAIL then
Raise Exception.Create(process.ErrorStream);
process.Dispose; // Close any connection we may still have to the running process
Program.Exit; // Background process keeps running
I've looked at a few things (WinExec, CreateProcess, ShellExecute, JclMiscel) but can't find any examples for what I'm trying to do. What is the best way to do this?
I'm using Delphi 2010
The background process is a 3rd party program I don't have the source to.
Check out this article. I quote: "Here is an updated and improved version of the code that allows you to choose in code whether the calling application waits until the other program closes before continuing or just carries on leaving the newly started program to its own devices".
procedure ExecNewProcess(ProgramName : String; Wait: Boolean);
var
StartInfo : TStartupInfo;
ProcInfo : TProcessInformation;
CreateOK : Boolean;
begin
{ fill with known state }
FillChar(StartInfo,SizeOf(TStartupInfo),#0);
FillChar(ProcInfo,SizeOf(TProcessInformation),#0);
StartInfo.cb := SizeOf(TStartupInfo);
CreateOK := CreateProcess(nil, PChar(ProgramName), nil, nil,False,
CREATE_NEW_PROCESS_GROUP+NORMAL_PRIORITY_CLASS,
nil, nil, StartInfo, ProcInfo);
{ check to see if successful }
if CreateOK then
begin
//may or may not be needed. Usually wait for child processes
if Wait then
WaitForSingleObject(ProcInfo.hProcess, INFINITE);
end
else
begin
ShowMessage('Unable to run '+ProgramName);
end;
CloseHandle(ProcInfo.hProcess);
CloseHandle(ProcInfo.hThread);
end;
Edited: after reading your comments, I suggest that you look at this previous question
I ended up using the example No'Am linked to, and adding code to check that the process started OK. This function only checks the exit code of the background process, it doesn't read the StdErr output.
Here is what I did:
/// <summary> Runs a new process in the background. Waits for a short period, then checks that the process started succesfully.
/// If the process has already finished, checks the exit status. Otherwise, leaves it to run. </summary>
/// <param> ProgramName The executable name, including any parameters.</param>
/// <param> TimeOut Milliseconds to wait before checking the process has executed correctly</param>
/// <param> Directory The full path of the working directory</param>
/// <exception> Exception If the process was not started correctly or if the process was started but returned
/// an error before the timeout.</exception>
procedure ExecBackgroundProcess(ProgramName : String; TimeOut: Integer; Directory:string);
var
StartInfo : TStartupInfo;
ProcInfo : TProcessInformation;
CreateOK : Boolean;
status: Cardinal;
theExitCode: Cardinal;
begin
FillChar(StartInfo,SizeOf(TStartupInfo),#0);
FillChar(ProcInfo,SizeOf(TProcessInformation),#0);
StartInfo.cb := SizeOf(TStartupInfo);
UniqueString(ProgramName); // Required if a const string is passed in. Otherwise the program crashes.
CreateOK := CreateProcess(nil, PChar(ProgramName), nil, nil,False,
CREATE_NEW_PROCESS_GROUP+NORMAL_PRIORITY_CLASS+CREATE_NO_WINDOW,
nil, PChar(Directory), StartInfo, ProcInfo);
if CreateOK then
begin
status:=WaitForSingleObject(ProcInfo.hProcess, TimeOut);
if status<> WAIT_TIMEOUT then
begin
// Program has exited. Get exit code.
GetExitCodeProcess(ProcInfo.hProcess, theExitCode);
if theExitCode<>0 then raise Exception.Create('Program '''+ProgramName+''' failed with exit code '+IntToStr(theExitCode));
end
end
else
Raise Exception.Create('Unable to run '+ProgramName+' in directory '+Directory);
CloseHandle(ProcInfo.hProcess);
CloseHandle(ProcInfo.hThread);
end;

Resources