Невыпущенная программа запускает повышенное обновление, программа обновления должна ждать завершения программы

У меня есть 2 приложения, program.exe и updater.exe, оба написанные на Delphi5. Программа запускается без прав администратора (и без манифеста), программа обновления имеет манифест с "requireAdministrator", потому что он должен иметь возможность писать в Program-Folder для обновления program.exe.

Проблема в том, чтобы запустить программу обновления и дать ему подождать, пока программа не закроется. В Интернете я нашел разные способы, но ни один из них не работает (в большинстве случаев первое приложение запускает второе приложение и ожидает окончания второго приложения, в моем случае второе приложение должно ожидать окончания первого приложения).

Обновление должно подождать, это просто
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

Способ 1
Запуск обновления с 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

Ничего не делает, работает только тогда, когда у программы обновления нет манифеста с requireAdministrator в. Если я запускаю программу с explizit admin-правами, она тоже работает.

Способ 2 Запуск обновления с 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

Не работает, MyHandle имеет другое значение каждый раз, когда я запускаю эту процедуру (без перезапуска программы), поэтому программа обновления не может с ней работать.

Поэтому я понятия не имею, как запустить Updater.exe и записать дескриптор программы.exe в файл.

Я не очень знаком с этими частями программирования... у кого-нибудь есть идея для моего решения?

3 ответа

Решение

Ваш код не работает, потому что таблица дескрипторов для каждого процесса, что означает, что второй процесс может иметь такой же дескриптор, указывающий на другой объект ядра. Ниже представлено одно из многих возможных решений:

При создании процесса 2 передайте PID процесса 1 в качестве параметра:

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;

Внутри Updater, дождитесь завершения process 1:

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;

Вы также можете использовать WaitForSingleObject чтобы избежать опроса:

WaitForSingleObject(AHandle, INFINITE);
//NOTE: MISSING ERROR CHECKING!

Но вам нужен доступ SYNCHRONIZE, чтобы открыть процесс:

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

Примечание: здесь нет проверки ошибок. Вы должны прочитать документы и правильно проверить на наличие ошибок.

Примечание 2: Я хотел бы обратить ваше внимание на тот факт, что вы протекаете ручкой. Когда вы используете SEE_MASK_NOCLOSEPROCESS вызывающий обязан закрыть ручку кейли. В твоем случае, я думаю, тебе вообще не нужна эта маска. Я бы удалил это.

Вот основной пример того, как этого добиться, используя события:

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);

Вы также должны добавить манифест в program.exe (requestedExecutionLevel должно быть level="asInvoker") чтобы избежать виртуализации.

Я вижу там главную проблему в неопределенном порядке двух событий: закрытие программы и запуск основного кода программы обновления.

Один из возможных способов исправить это - использовать Events - https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms686670(v=vs.85).aspx

Программа создает событие (с возможностью наследования его дочерними элементами), затем запускает средство обновления (передавая дескрипторы события и процесса программы как целые числа через командную строку!), Затем замораживается в WaitForSingleObject для события,

Это гарантирует, что программа не завершит работу до того, как программа обновления будет готова к ее мониторингу, поэтому PID не станет недействительным.

Затем программа обновления вызывает OpenProcess для PID программы, полученного из командной строки, затем вызывает SignalAndWait, одновременно выбивая событие (полученное из командной строки) и замораживая дескриптор (полученный из OpenProcess) - https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms686293(v=vs.85).aspx

Программа, которая теперь освобождается от ожидания события, завершается. Завершение процесса сигнализирует об этом, поэтому теперь программа обновления выпускается по очереди и может начать выполнять основную работу.


Другой подход, предложенный на C++, Как определить, запущен ли процесс Windows? запрашивает код завершения программы ProcessID - говорится, что, пока программа еще работает, будет определенный код ошибки, и вы можете Sleep(100), а затем повторите попытку. Любой другой результат означает, что программа уже завершена.

Программа завершает работу сразу после запуска программы обновления, не дожидаясь ее начала.

Это кажется хорошим подходом, за исключением того, что у меня нет никакой гарантии, что значения PID не будут использоваться повторно. Шансы бесконечно малы, но все же не ноль.


Лично я, вероятно, использовал бы файл флага. API CreateFile имеет очень интересный флаг - режим временных файлов. Это означает, что Windows автоматически удалит файл после завершения процесса. Итак, тогда

  1. Программа создает новый GUID, используя Windows API (или новое случайное значение, используя Crypto API).
  2. Программа создает в папке временных файлов файл временного режима с именем на основе GUID или значения Random. Если по какой-то случайности такой файл уже существует - вы просто получаете новый GUID или значение Random.
  3. Программа запускает программу обновления и передает ей имя файла через командную строку
  4. Программа завершает работу сразу после запуска программы обновления, не дожидаясь ее начала.
  5. Средство обновления продолжает проверять, существует ли файл (делая паузы между попытками). Когда файл больше не существует - это означает, что программа завершила работу и Windows автоматически удалила его.

Опять же, существует бесконечно малая вероятность того, что какой-то другой процесс создаст файл флага с точно таким же именем в промежутке между завершением программы и повторной проверкой средства обновления, но на практике это практически невозможно

Другие вопросы по тегам