Закройте установленную версию программы перед установкой обновления (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().