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

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

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

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

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

Ответы [ 9 ]

30 голосов
/ 18 августа 2010

Если в приложении есть 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;
19 голосов
/ 27 ноября 2012

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

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

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

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

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

Значения по умолчанию для новых директив закрывают все файлы .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собственность.
12 голосов
/ 05 августа 2013

Я пытался использовать принятый ответ (и последующие действия 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;
4 голосов
/ 18 августа 2010

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

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

3 голосов
/ 19 мая 2011

Вот ссылка на скрипт 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

1 голос
/ 06 октября 2016

Я успешно использовал 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}.Моя программа не запрашивает путь установки, но ваш может.

1 голос
/ 18 августа 2010

Если вы счастливы написать свою собственную 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.
0 голосов
/ 18 августа 2010

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

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

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

0 голосов
/ 18 августа 2010

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

...