Закройте установленную версию программы перед установкой обновления (Inno Setup)

Это должно быть просто, мне нужно остановить запуск любой предыдущей версии моей программы при запуске установщика.

Большинство людей предложили сделать exe который делает это и вызывает его до запуска Inno Setup. Я создал exe используя AutoIt, который убивает все процессы моей программы. Проблема в том, что я не знаю, как заставить Inno Setup вызывать его до того, как он установит что-либо.

Как мне вызвать исполняемый файл перед установкой файлов?

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

9 ответов

Решение

Если в приложении есть Mutex, вы можете добавить AppMutex значение в установщике Inno Setup, и он будет отображать сообщение, говорящее пользователю, чтобы остановить программу. Вы можете найти Mutex (если он есть), используя SysInternals Process Explorer, выбрав программу / процесс и посмотрев на дескрипторы (CTRL-H) на нижней панели.

Вот ссылка на статью базы знаний, в которой упоминается несколько методов:
http://www.vincenzo.net/isxkb/index.php?title=Detect_if_an_application_is_running

В качестве альтернативы, вы можете попробовать этот (НЕПРОВЕРЕННЫЙ) код в InitializeSetup:

[Setup]
;If the application has  Mutex, uncomment the line below, comment the InitializeSetup function out, and use the AppMutex.
;AppMutex=MyApplicationMutex

[Code]
const
  WM_CLOSE = 16;

function InitializeSetup : Boolean;
var winHwnd: Longint;
    retVal : Boolean;
    strProg: string;
begin
  Result := True;
  try
    //Either use FindWindowByClassName. ClassName can be found with Spy++ included with Visual C++. 
    strProg := 'Notepad';
    winHwnd := FindWindowByClassName(strProg);
    //Or FindWindowByWindowName.  If using by Name, the name must be exact and is case sensitive.
    strProg := 'Untitled - Notepad';
    winHwnd := FindWindowByWindowName(strProg);
    Log('winHwnd: ' + IntToStr(winHwnd));
    if winHwnd <> 0 then
      Result := PostMessage(winHwnd,WM_CLOSE,0,0);
  except
  end;
end;

В версии 5.5.0 (выпущено в мае 2012 г.) Inno Setup добавлена ​​поддержка API-интерфейса Restart Manager в Windows Vista и более поздних версиях.

Цитата из MSDN связанной документации (выделено мое):

Основная причина, по которой установка и обновление программного обеспечения требуют перезапуска системы, заключается в том, что некоторые обновляемые файлы в настоящее время используются запущенным приложением или службой. Restart Manager позволяет отключать и перезапускать все, кроме критически важных приложений и служб. Это освобождает используемые файлы и позволяет завершить операции установки. Это также может устранить или уменьшить количество перезапусков системы, необходимых для завершения установки или обновления.

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

Если вы хотите, чтобы ваше приложение перезапустилось после завершения обновления, вы должны позвонить в RegisterApplicationRestart функция из вашего приложения в первую очередь.

Значения по умолчанию для новых директив закрывают все файлы.exe,.dll и.chm, содержащиеся в [Files] раздел установщика.

Связанные с этим изменения (из заметок о выпуске):

  • Добавлен новый [Setup] директива раздела: CloseApplications, который по умолчанию yes, Если установлено значение "да" и программа установки не работает в режиме без вывода сообщений, программа установки теперь приостановит работу на странице мастера подготовки к установке, если обнаружит, что приложения используют файлы, которые необходимо обновить с помощью [Files] или же [InstallDelete] раздел, показывающий приложения и спрашивающий пользователя, должна ли программа установки автоматически закрывать приложения и перезапускать их после завершения установки. Если установлено значение "да" и программа установки работает без вывода сообщений, программа установки всегда будет закрывать и перезапускать такие приложения, если в командной строке не будет указано (см. Ниже).
  • Добавлен новый [Setup] директива раздела: CloseApplicationsFilter, который по умолчанию *.exe,*.dll,*.chm, Управляет тем, какие файлы программа установки проверит на предмет использования. Установка этого в *.* может обеспечить лучшую проверку за счет скорости.
  • Добавлен новый [Setup] директива раздела: RestartApplications, который по умолчанию yes, Примечание. Чтобы программа установки могла перезапустить приложение после завершения установки, приложение должно использовать Windows. RegisterApplicationRestart API-функция.
  • Добавлены новые параметры командной строки, поддерживаемые программой установки: /NOCLOSEAPPLICATIONS а также /NORESTARTAPPLICATIONS, Они могут быть использованы для переопределения нового CloseApplications а также RestartApplications директивы.
  • Добавлен новый [Code] функция поддержки: RmSessionStarted,
  • TWizardForm: Добавлено новое PreparingMemo имущество.

Я попытался использовать принятый ответ (и последующие действия jachguate), но это не убило бы мое заявление. Похоже, что одна из причин была в том, что у окна моего приложения не было текста, связанного с ним, но какова бы ни была реальная причина, я использовал команду оболочки, чтобы убить его, и это сработало. В разделе [code] вы хотите добавить следующую функцию. Он вызывается непосредственно перед копированием установочных файлов.

function PrepareToInstall(var NeedsRestart: Boolean): String;
var
ErrorCode: Integer;
begin
      ShellExec('open',  'taskkill.exe', '/f /im MyProg.exe','',SW_HIDE,ewNoWait,ErrorCode);
end;

Если вы используете InnoSetup, вы можете получить программу установки InnoSetup для Windows SendBroadcastMessage и заставить ваше приложение прослушивать это сообщение. Когда ваше приложение получает сообщение, оно должно отключиться.

Я сделал это сам с помощью установщика InnoSetup, и он работает очень хорошо.

Вот ссылка на скрипт Inno Setup, который предлагает пользователю закрыть целевую программу, если он обнаруживает, что программа запущена. После того, как пользователь закрывает программу, он может нажать кнопку "Повторить", чтобы продолжить установку:

http://www.domador.net/extras/code-samples/inno-setup-close-a-program-before-reinstalling-it/

Этот сценарий основан на более простом сценарии, который можно найти в базе знаний Inno Setup Extensions:

http://www.vincenzo.net/isxkb/index.php?title=Call_psvince.dll_on_install_and_uninstall

Я имел успех, используя WMIC:

procedure CurStepChanged(CurStep: TSetupStep);
var
    ResultCode: Integer;
    wmicommand: string;
begin
    // before installing any file
    if CurStep = ssInstall then
    begin
        wmicommand := ExpandConstant('PROCESS WHERE "ExecutablePath like ''{app}\%%''" DELETE');

        // WMIC "like" expects escaped backslashes
        StringChangeEx(wmicommand, '\', '\\', True);

        // you can/should add an "if" around this and check the ResultCode
        Exec('WMIC', wmicommand, '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
    end;
end;

Вы также можете сделать это в InitializeSetup но если вы это сделаете, имейте в виду, что у вас еще нет доступа к {app} постоянная. Моя программа не запрашивает путь установки, но ваша может.

Добавьте CloseApplications=true в раздел [Настройка].

Если установлено значение "да" или "принудительно", и программа установки не работает в автоматическом режиме, программа установки приостанавливает работу на странице мастера подготовки к установке, если обнаруживает приложения, использующие файлы, которые необходимо обновить в разделе [Файлы] или [InstallDelete], показывая приложения и спрашивая пользователю, если программа установки должна автоматически закрывать приложения и перезапускать их после завершения установки.

Если вы готовы написать свою собственную DLL, вы можете использовать API справки инструмента для TlHelp32.pas, чтобы определить, какие приложения работают, а затем получить дескриптор окна для них с помощью EnumWindows, а затем отправить WM_CLOSE в дескриптор окна.

Это немного больно, но это должно сработать: у меня есть несколько классов-утилит, которые я разработал с другом некоторое время назад. Не могу вспомнить, основывали ли мы это на чужом коде.

TWindows.ProcessISRunning и TWindows.StopProcess могут помочь.

interface

uses
  Classes,
  Windows,
  SysUtils,
  Contnrs,
  Messages;

type


TProcess = class(TObject)
  public
    ID: Cardinal;
    Name: string;
end;

TWindow = class(TObject)
  private
    FProcessID: Cardinal;
    FProcessName: string;
    FHandle: THandle;
    FProcessHandle : THandle;
    function GetProcessHandle: THandle;
    function GetProcessID: Cardinal;
    function GetProcessName: string;
  public
    property Handle : THandle read FHandle;
    property ProcessName : string read GetProcessName;
    property ProcessID : Cardinal read GetProcessID;
    property ProcessHandle : THandle read GetProcessHandle;
end;

TWindowList = class(TObjectList)
  private
    function GetWindow(AIndex: Integer): TWindow;
  protected

  public
    function Add(AWindow: TWindow): Integer; reintroduce;
    property Window[AIndex: Integer]: TWindow read GetWindow; default;
end;

TProcessList = class(TObjectList)
  protected
    function GetProcess(AIndex: Integer): TProcess;
  public
    function Add(AProcess: TProcess): Integer; reintroduce;
    property Process[AIndex: Integer]: TProcess read GetProcess; default;
end;

TWindows = class(TObject)
  protected
  public
    class function GetHWNDFromProcessID(ProcessID: Cardinal; BuildList: Boolean = True): THandle;
    class function GetProcessList: TProcessList;
    class procedure KillProcess(ProcessName: string);
    class procedure StopProcess(ProcessName: string);
    class function ExeIsRunning(ExeName: string): Boolean;
    class function ProcessIsRunning(PID: Cardinal): Boolean;
end;

implementation

uses
  Forms,
  Math,
  PSAPI,
  TlHelp32;

const
  cRSPUNREGISTERSERVICE = 0;
  cRSPSIMPLESERVICE = 1;

type

TProcessToHWND = class(TObject)
  public
    ProcessID: Cardinal;
    HWND: Cardinal;
end;

function RegisterServiceProcess(dwProcessID, dwType: DWord): DWord; stdcall; external 'KERNEL32.DLL';
function GetDiskFreeSpaceEx(lpDirectoryName: PChar;
  var lpFreeBytesAvailableToCaller, lpTotalNumberOfBytes: TLargeInteger;
  lpTotalNumberOfFreeBytes: PLargeInteger): Boolean; stdcall;external 'KERNEL32.DLL' name 'GetDiskFreeSpaceExA'

var
  GProcessToHWNDList: TObjectList = nil;

function EnumerateWindowsProc(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
var
  proc: TProcessToHWND;
begin
  if Assigned(GProcessToHWNDList) then
  begin
    proc := TProcessToHWND.Create;
    proc.HWND := hwnd;
    GetWindowThreadProcessID(hwnd, proc.ProcessID);
    GProcessToHWNDList.Add(proc);
    Result := True;
  end
  else
    Result := False; // stop enumeration
end;

{ TWindows }

class function TWindows.ExeIsRunning(ExeName: string): Boolean;
var
  processList: TProcessList;
  i: Integer;
begin
  Result := False;

  processList := GetProcessList;
  try
    for i := 0 to processList.Count - 1 do
    begin
      if (UpperCase(ExeName) = UpperCase(processList[i].Name)) or
          (UpperCase(ExeName) = UpperCase(ExtractFileName(processList[i].Name))) then
      begin
        Result := True;
        Break;
      end;
    end;
  finally
    processList.Free;
  end;
end;

class function TWindows.GetHWNDFromProcessID(
  ProcessID: Cardinal; BuildList: Boolean): THandle;
var
  i: Integer;
begin
  Result := 0;

  if BuildList or (not Assigned(GProcessToHWNDList)) then
  begin
    GProcessToHWNDList.Free;
    GProcessToHWNDList := TObjectList.Create;
    EnumWindows(@EnumerateWindowsProc, 0);
  end;

  for i := 0 to GProcessToHWNDList.Count - 1 do
  begin
    if TProcessToHWND(GProcessToHWNDList[i]).ProcessID = ProcessID then
    begin
      Result := TProcessToHWND(GProcessToHWNDList[i]).HWND;
      Break;
    end;
  end;
end;


class function TWindows.GetProcessList: TProcessList;
var
  handle: THandle;
  pe: TProcessEntry32;
  process: TProcess;
begin
  Result := TProcessList.Create;

  handle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  pe.dwSize := Sizeof(pe);
  if Process32First(handle, pe) then
  begin
    while True do
    begin
      process := TProcess.Create;
      process.Name := pe.szExeFile;
      process.ID := pe.th32ProcessID;
      Result.Add(process);
      if not Process32Next(handle, pe) then
        Break;
    end;
  end;
  CloseHandle(handle);
end;

function EnumWindowsProc(Ahwnd : HWND;      // handle to parent window
  ALParam : Integer) : BOOL;stdcall;
var
  List : TWindowList;
  Wnd : TWindow;
begin
  Result := True;
  List := TWindowList(ALParam);
  Wnd := TWindow.Create;
  List.Add(Wnd);
  Wnd.FHandle := Ahwnd;
end;


class procedure TWindows.KillProcess(ProcessName: string);
var
  handle: THandle;
  pe: TProcessEntry32;
begin
  // Warning: will kill all process with ProcessName
  // NB won't work on NT 4 as Tool Help API is not supported on NT

  handle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  try
    pe.dwSize := Sizeof(pe);

    if Process32First(handle, pe) then
    begin
      while True do begin
        if (UpperCase(ExtractFileName(pe.szExeFile)) = UpperCase(ExtractFileName(ProcessName))) or
           (UpperCase(pe.szExeFile) = UpperCase(ProcessName)) then
        begin
          if not TerminateProcess(OpenProcess(PROCESS_TERMINATE, False,
                                    pe.th32ProcessID), 0) then
          begin
            raise Exception.Create('Unable to stop process ' + ProcessName + ': Error Code ' + IntToStr(GetLastError));
          end;
        end;
        if not Process32Next(handle, pe) then
          Break;
      end;
    end;
  finally
    CloseHandle(handle);
  end;
end;

class function TWindows.ProcessIsRunning(PID: Cardinal): Boolean;
var
  processList: TProcessList;
  i: Integer;
begin
  Result := False;

  processList := GetProcessList;
  try
    for i := 0 to processList.Count - 1 do
    begin
      if processList[i].ID = PID then
      begin
        Result := True;
        Break;
      end;
    end;
  finally
    processList.Free;
  end;
end;

class procedure TWindows.StopProcess(ProcessName: string);
var
  processList: TProcessList;
  i: Integer;
  hwnd: THandle;
begin
  // Warning: will attempt to stop all process with ProcessName
  if not Assigned(GProcessToHWNDList) then
    GProcessToHWNDList := TObjectList.Create
  else
    GProcessToHWNDList.Clear;

  // get list of all current processes
  processList := GetProcessList;
  // enumerate windows only once to determine the window handle for the processes
  if EnumWindows(@EnumerateWindowsProc, 0) then
  begin
    for i := 0 to processList.Count - 1 do
    begin
      if UpperCase(ExtractFileName(processList[i].Name)) = UpperCase(ExtractFileName(ProcessName)) then
      begin
        hwnd := GetHWNDFromProcessID(processList[i].ID, False);
        SendMessage(hwnd, WM_CLOSE, 0, 0);
      end;
    end;
  end;
end;


{ TProcessList }

function TProcessList.Add(AProcess: TProcess): Integer;
begin
  Result := inherited Add(AProcess);
end;

function TProcessList.GetProcess(AIndex: Integer): TProcess;
begin
  Result := TProcess(Items[AIndex]);
end;

{ TWindowList }

function TWindowList.Add(AWindow: TWindow): Integer;
begin
  Result := inherited Add(AWindow);
end;

function TWindowList.GetWindow(AIndex: Integer): TWindow;
begin
  Result := TWindow(Items[AIndex]);
end;

{ TWindow }

function TWindow.GetProcessHandle: THandle;
begin
  if FProcessHandle = 0 then
    FProcessHandle := OpenProcess(Windows.SYNCHRONIZE or Windows.PROCESS_TERMINATE,
     True, FProcessID);
  Result := FProcessHandle;
end;

function TWindow.GetProcessID: Cardinal;
var
  Pid : Cardinal;
begin
  if FProcessID = 0 then
  begin
    Pid := 1;
    GetWindowThreadProcessId(Handle, Pid);
    FProcessID := Pid;
  end;
  Result := FProcessID;
end;


function TWindow.GetProcessName: string;
var
  Buffer : packed array [1..1024] of char;
  len : LongWord;
begin
  FillChar(Buffer, SizeOf(Buffer), 0);
  if FProcessName = '' then
  begin
    len := GetWindowModuleFileName(Handle, @Buffer[1], 1023);
    FProcessName := Copy(Buffer, 1, Len);
  end;
  Result := FProcessName;
end;

end.

InnoSetup позволяет вам присоединять сценарии Pascal к различным местам процесса сборки. Попробуйте подключить скрипт, который вызывает ShellExecute. (Который вам, возможно, придется импортировать в механизм сценариев, если у него его еще нет.)

Ну, я думаю, что более простой способ сделать это может быть создание DLL в Delphi, которая определяет, работает ли ваша программа, и просит пользователя закрыть ее, вставляет эту DLL в вашу настройку и использует флаг "dontcopy" (проверьте в http://www.jrsoftware.org/ishelp/ разделе Pascal Scripting \ Использование DLL для примера).

Кстати, в следующий раз используйте мьютексы, Inno Setup также поддерживает это, и это намного проще.

РЕДАКТИРОВАТЬ: и для извлечения файла (если вы хотите использовать тот.exe, который вы упомянули), просто используйте ExtractTeilitaryFile().

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