Reputation: 93
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?
Upvotes: 9
Views: 455
Reputation: 21252
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.
Upvotes: 1
Reputation: 2461
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.
Upvotes: 8
Reputation: 16045
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
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
Upvotes: 0