Закройте запущенную версию программы перед установкой обновления (Inno Setup)
Это должно быть просто, мне нужно остановить любую предыдущую версию моей программы, начиная с запуска установщика.
Большинство людей предложили сделать exe
, который делает это и называет его до запуска Inno Setup. Я создал exe
, используя AutoIt, который убивает все процессы моей программы. Проблема в том, что я не знаю, как заставить Inno Setup вызвать его до того, как он установит что-нибудь.
Как вызвать исполняемый файл перед установкой файлов?
В качестве альтернативы, если я могу просто определить, запущена ли программа и сказать пользователю закрыть ее, это тоже сработает.
Ответы
Ответ 1
Если приложение имеет Mutex, вы можете добавить значение AppMutex
в установщик Inno Setup и отобразит сообщение, в котором пользователь должен остановить программу. Вы можете найти Mutex (если он есть) с помощью SysInternals Process Explorer и выбора программы/процесса и просмотра ручек (CTRL-H) в нижней панели.
Здесь ссылка на статью KB, в которой упоминаются несколько методов:
http://www.vincenzo.net/isxkb/index.php?title=Detect_if_an_application_is_running
В качестве альтернативы вы можете попробовать этот код (UNTESTED) в 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;
Ответ 2
В версии 5.5.0 (выпущен в мае 2012 г.) Inno Setup добавила поддержку Restart Manager API в Windows Vista и новее.
Цитата из связанной с MSDN документации (выделение мое):
Основная причина установки и обновления программного обеспечения требует перезагрузки системы, что некоторые из файлов, которые обновляются, в настоящее время используются запущенным приложением или службой. Restart Manager позволяет отключать и перезапускать все критические приложения и службы. Это освобождает файлы, которые используются, и позволяет завершить установку. Он также может устранить или уменьшить количество перезапусков системы, которые необходимы для завершения установки или обновления.
Хорошо, что вам не нужно писать собственный код в установщике или приложении, чтобы попросить пользователя закрыть его или закрыть его автоматически.
Если вы хотите, чтобы ваше приложение перезапустилось после завершения обновления, вы должны сначала вызвать функцию RegisterApplicationRestart
из вашего приложения.
Значения по умолчанию для новых директив закрывают все файлы .exe,.dll и .chm, содержащиеся в разделе [Files]
установщика.
Связанные с ним изменения (из примечаний к выпуску):
- Добавлена новая директива раздела
[Setup]
: CloseApplications
, которая по умолчанию равна yes
. Если установлено "да", а "Настройка" не работает тихо, программа установки приостанавливается на странице "Подготовка к установке", если она обнаруживает приложения с использованием файлов, которые необходимо обновить секцией [Files]
или [InstallDelete]
, показывая приложения и запрашивая если программа установки должна автоматически закрыть приложения и перезапустить их после завершения установки. Если установлено "да", а "Настройка" работает тихо, программа установки всегда закрывает и перезапускает такие приложения, если не будет сказано через командную строку (см. Ниже). - Добавлена новая директива раздела
[Setup]
: CloseApplicationsFilter
, которая по умолчанию равна *.exe,*.dll,*.chm
. Контролирует, какие файлы Setup будут проверяться на предмет использования. Установка этого параметра на *.*
может обеспечить лучшую проверку за счет скорости. - Добавлена новая директива раздела
[Setup]
: RestartApplications
, которая по умолчанию равна yes
. Примечание. Чтобы программа установки могла перезапустить приложение после завершения установки, приложение должно использовать функцию Windows RegisterApplicationRestart
API. - Добавлены новые параметры командной строки, поддерживаемые установкой:
/NOCLOSEAPPLICATIONS
и /NORESTARTAPPLICATIONS
. Они могут использоваться для переопределения новых директив CloseApplications
и RestartApplications
. - Добавлена новая функция поддержки
[Code]
: RmSessionStarted
. -
TWizardForm
: добавлено новое свойство PreparingMemo
.
Ответ 3
Я пробовал использовать принятый ответ (и следить за 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
Если вы используете InnoSetup, вы можете посмотреть, как установить установщик InnoSetup на Windows SendBroadcastMessage и заставить приложение прослушивать это сообщение. Когда ваше приложение получает сообщение, оно должно закрыться.
Я сделал это сам с установщиком InnoSetup, и он работает очень хорошо.
Ответ 5
Здесь приведена ссылка на Inno Setup script, в которой пользователю предлагается закрыть целевую программу, если она обнаруживает, что программа запущена. После того, как пользователь закроет программу, они могут нажать кнопку "Повторить", чтобы продолжить установку:
http://www.domador.net/extras/code-samples/inno-setup-close-a-program-before-reinstalling-it/
Этот script основан на более простом script, найденном в базе знаний расширения Inno Setup:
http://www.vincenzo.net/isxkb/index.php?title=Call_psvince.dll_on_install_and_uninstall
Ответ 6
Если вы счастливы написать свою собственную 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.
Ответ 7
У меня был успех с использованием 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}
. Моя программа не запрашивает путь установки, но ваш может.
Ответ 8
InnoSetup позволяет присоединять скрипты Pascal к различным местам процесса сборки. Попробуйте подключить script, который вызывает ShellExecute. (Что вам может потребоваться импортировать в движок script, если он еще не имеет его.)
Ответ 9
Ну, я думаю, что более простой способ выполнить это может быть создание DLL в Delphi, которая определяет, запущена ли ваша программа, и попросить пользователя закрыть ее, поместить эту DLL в вашу настройку и использовать флаг "dontcopy" (проверьте в http://www.jrsoftware.org/ishelp/ в разделе Pascal Scripting\Использование DLL для примера).
Btw, в следующий раз, используя мьютексы, Inno Setup также поддерживает это и намного проще.
EDIT: и для извлечения файла (если вы хотите использовать этот .exe, который вы упомянули), просто используйте ExtractTemporaryFile().