Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 903
BjoernHH Avatar asked Jun 10 '15 14:06

BjoernHH


People also ask

How can I tell if C++ EXE is running?

hProcessInfo = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pe32. th32ProcessID ); do{ if(strcmp(pe32. szExeFile,"process.exe") == 0) { processfound = true; break; } }while( Process32Next( hProcessSnap, &pe32 ) );

How do you check if a program is running in Windows?

You can start Task Manager by pressing the key combination Ctrl + Shift + Esc. You can also reach it by right-clicking on the task bar and choosing Task Manager. Under Processes>Apps you see the software that is currently open.


2 Answers

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.

like image 53
EProgrammerNotFound Avatar answered Sep 19 '22 02:09

EProgrammerNotFound


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.

like image 28
kobik Avatar answered Sep 18 '22 02:09

kobik