A non-elevated program launches an elevated update program, updater must wait for the program to complete

I have 2 applications, program.exe and updater.exe written in Delphi5. The program works without administrator rights (and without a manifest), updater has a manifest with "requireAdministrator", because it must be able to write in Program-Folder to update program.exe.

The problem is to run the update program and let it wait until the program is closed. I found different ways on the Internet, but no one works (in most cases the first application launches the second application and waits for the second application to finish, in my case the 2nd application should wait for the 1st application to finish).

Updater must 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 

Path 1
Launching the updater using 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 

It does nothing, it works only when the updater does not have a manifest with requireAdministrator. If I run the program with administrator privileges explizit, it also works.

Path 2 Run the updater using 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 

It doesn’t work, MyHandle has a different meaning every time I run this procedure (without restarting the program), so the updater cannot work with it.

So, I do not know how to run updater.exe and write the program.exe descriptor in the file.

I am not very familiar with these pieces of programming ... does anyone have an idea for my product?

+8
delphi createprocess handle shellexecuteex waitforsingleobject
source share
3 answers

Your code does not work because there is a descriptor table for each process, which means that the second process can have the same descriptor pointing to a different kernel object. Below is one of many possible solutions:

When creating process 2, pass the PID of process 1 as a 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 Updater, wait for process1 to complete:

 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 SYNCHRONIZE access to open the process:

 AHandle:= OpenProcess(SYNCHRONIZE, False, StrToInt(PID)); //NOTE: MISSING ERROR CHECKING! 

Note. There are no errors. You must read the documents and correctly check for errors.

Note 2: I would like to draw your attention to the fact that you miss the pen. When you use SEE_MASK_NOCLOSEPROCESS , the caller is responsible for closing the calee descriptor. In your case, I think that you do not need this mask at all. I would delete it.

+7
source share

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 must be level="asInvoker" ) to avoid virtualization.

+1
source share

I see the main problem there in an unspecified order of two events: closing the program and the beginning of the main update code.

One possible way to fix this would be to use events - https://msdn.microsoft.com/en-us/library/windows/desktop/ms686670(v=vs.85).aspx

The program creates an event (with the ability for children to inherit it), and then runs the update program (passing it both the event and the program process as integers through the command line to it!), And then hangs in WaitForSingleObject at the event.

This ensures that the program does not exit before the update program is ready to control it, so the PID will not become invalid.

Then, the updater calls OpenProcess on the program PID received from the command line, and then calls SignalAndWait both the detonation of the event (obtained from the command line) and the hang of the descriptor (obtained from OpenProcess) - https://msdn.microsoft.com/en- com / library / windows / desktop / ms686293 (v = vs. 85) .aspx

A program that is now freed from waiting for an event ends. The termination of the process signals this, so now the updater enters the queue and can begin to perform the main work.


Another approach suggested in C ++ is how to determine if a Windows process is running? asks for the exit code of the ProcessID program - they say that although the program is still running, there will be a special error code and you can sleep (100), and then try again. Any other result means that the program has already been completed.

The program exits immediately after starting the update, without waiting for it to start monitoring.

This is a good approach, except that I cannot guarantee that PID values ​​will not be reused. The odds are infinitely small, but still non-zero.


Personally, I would probably use the flag file. CreateFile API has a very interesting flag - temporary file mode. This means that Windows will automatically delete the file after the process is complete. So,

  • The program creates a new GUID using the Windows API (or a new random value using the Crypto API).
  • The program creates a temporary mode file in a temporary folder with a name based on the GUID or Random. If for some wild luck such a file already exists - you just get a new GUID or Random value.
  • The program launches the updater and passes the file name to it through the command line
  • The program exits immediately after starting the update, without waiting for its start.
  • The updater continues to check if the file exists (pauses between attempts). When the file no longer exists, it means that the program was completed and Windows automatically deleted it.

Again, there is an infinitely small likelihood that some other process will create a flag file with exactly the same name between the program terminal and the end of the scan, but in practice this is practically impossible.

0
source share

All Articles