tags:

views:

345

answers:

2

I'm trying to start another application from my program with elevated rights, and wait for it to terminate before continuing.

I've tried several different solutions on the web, but I can't find one that works exactly right.

The code below is the closest I have to working right. It runs the app with elevated privileges and waits for it to terminate, but it freezes once the external app is terminated. In other words, it doesn't keep processing once the launched app is closed.

How can I accomplish what I'm after here?

procedure TfMain.RunFileAsAdminWait(hWnd: HWND; aFile, aParameters: string);
var
  sei: TShellExecuteInfo;
begin
  FillChar(sei, SizeOf(sei), 0);
  sei.cbSize := SizeOf(sei);
  sei.Wnd := hWnd;
  sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;
  sei.lpVerb := 'runas';
  sei.lpFile := PChar(aFile);
  sei.lpParameters := PChar(aParameters);
  sei.nShow := SW_SHOWNORMAL;

  if not ShellExecuteEx(@sei) then
    RaiseLastOSError
  else
    while WaitForSingleObject(sei.hProcess, 50) <> WAIT_OBJECT_0 do
      Application.ProcessMessages;

  CloseHandle(sei.hProcess);
end;

Update:

I've come up with the following function, but it only works if I have a ShowMessage statement after calling it. So, I have to have:

RunFileAsAdminWait(Handle, ExtractFilePath(Application.Exename) + 'AutoUpdate.exe', '/auto');
ShowMessage('test');

in order to make the function work. How can I make it work without the ShowMessage call?

Here's the updated function:

procedure TfMain.RunFileAsAdminWait(hWnd: HWND; aFile, aParameters: string);
var
  sei: TShellExecuteInfo;
begin
  FillChar(sei, SizeOf(sei), 0);
  sei.cbSize := SizeOf(sei);
  sei.Wnd := hWnd;
  sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;
  sei.lpVerb := 'runas';
  sei.lpFile := PChar(aFile);
  sei.lpParameters := PChar(aParameters);
  sei.nShow := SW_SHOWNORMAL;

  if not ShellExecuteEx(@sei) then
    RaiseLastOSError
  else
    if sei.hProcess <> 0 then
      WaitForSingleObject(sei.hProcess, 50)
    else
      Exit;

  CloseHandle(sei.hProcess);
end;
A: 

Your wait (50 ms is too short), try

WaitForSingleObject(sei.hProcess, INFINITE)

The check for valid process handle (sei.hProcess <> 0) can be left out.

Corrected Answer:

  while MsgWaitForMultipleObjects(1, sei.hProcess, False, INFINITE, QS_ALLINPUT)
    <> WAIT_OBJECT_0 do
  begin
    while PeekMessage(msg, 0, 0, 0, PM_REMOVE) do
    begin
      DispatchMessage(Msg);
    end;
  end;
Remko
That will cause the calling application to appear hung, as it will no longer process messages.
mghie
Sorry I overlooked the part about freezing, I corrected my answer
Remko
+7  A: 

The following code works for me:

procedure RunFileAsAdminWait(hWnd: HWND; aFile, aParameters: string);
var
  sei: TShellExecuteInfo;
begin
  FillChar(sei, SizeOf(sei), 0);
  sei.cbSize := SizeOf(sei);
  sei.Wnd := hWnd;
  sei.fMask := SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS;
  sei.lpVerb := 'runas';
  sei.lpFile := PChar(aFile);
  sei.lpParameters := PChar(aParameters);
  sei.nShow := SW_SHOWNORMAL;

  if not ShellExecuteEx(@sei) then
    RaiseLastOSError;
  if sei.hProcess <> 0 then begin
    while WaitForSingleObject(sei.hProcess, 50) = WAIT_TIMEOUT do
      Application.ProcessMessages;
    CloseHandle(sei.hProcess);
  end;
end;

You have to pass the SEE_MASK_NOCLOSEPROCESS flag to get the process handle to wait for. I also changed the code to loop as long as WaitForSingleObject() returns with timeout.

For more information on the flags see the MSDN page for the SHELLEXECUTEINFO structure.

mghie
Worked perfect! Thank you!
croceldon