installer - tutorial - Cierre la versión en ejecución del programa antes de instalar la actualización(Inno Setup)
jrsoftware (9)
Esto debería ser simple, debo detener la ejecución de cualquier versión anterior de mi programa cuando se inicie el instalador.
La mayoría de las personas sugirieron hacer un exe
que haga esto y llamarlo antes de que se inicie Inno Setup. exe
un exe
usando AutoIt que mata todos los procesos de mi programa. El problema es que no sé cómo hacer que Inno Setup lo llame antes de instalar nada.
¿Cómo llamo a un ejecutable antes de instalar archivos?
Alternativamente, si puedo detectar si un programa se está ejecutando y decirle al usuario que lo cierre, también funcionaría.
Aquí hay un enlace a una secuencia de comandos Inno Setup que le pide a un usuario que cierre el programa de destino, si detecta que el programa se está ejecutando. Después de que el usuario cierre el programa, puede hacer clic en el botón "Reintentar" para continuar con la instalación:
http://www.domador.net/extras/code-samples/inno-setup-close-a-program-before-reinstalling-it/
Esta secuencia de comandos se basa en una secuencia de comandos más simple, que se encuentra en la Base de conocimientos de Inno Setup Extensions:
http://www.vincenzo.net/isxkb/index.php?title=Call_psvince.dll_on_install_and_uninstall
Bueno, creo que la forma más fácil de realizar esto es crear una DLL en Delphi que detecte si su programa se está ejecutando y pedirle al usuario que lo cierre, ponga esa DLL en su configuración y use la bandera "dontcopy" (ver http://www.jrsoftware.org/ishelp/ bajo Pascal Scripting / Using DLLs para un ejemplo).
Por cierto, la próxima vez use mutexes, Inno Setup también admite eso y es mucho más fácil.
EDITAR: y para extraer un archivo (si desea usar ese .exe que menciona), simplemente use ExtractTemporaryFile ().
En la versión 5.5.0 (lanzada en mayo de 2012), Inno Setup agregó soporte para la API del Administrador de reinicio en Windows Vista y más reciente.
Cita de la documentación vinculada a MSDN (énfasis mío):
La razón principal por la que la instalación y las actualizaciones del software requieren un reinicio del sistema es que algunos de los archivos que se están actualizando están siendo utilizados actualmente por una aplicación o servicio en ejecución. Restart Manager permite que todas las aplicaciones y servicios, excepto los críticos, se cierren y reinicien . Esto libera los archivos que están en uso y permite que se completen las operaciones de instalación. También puede eliminar o reducir la cantidad de reinicios del sistema que se requieren para completar una instalación o actualización.
Lo bueno es que no necesita escribir código personalizado en el instalador o en su aplicación para pedirle al usuario que lo cierre o lo cierre automáticamente.
Si desea que su aplicación se reinicie después de que se complete la actualización, primero debe llamar a la función RegisterApplicationRestart
desde su aplicación.
Los valores predeterminados para las nuevas directivas cierran todos los archivos .exe, .dll y .chm contenidos en la sección [Files]
del instalador.
Los cambios relacionados con él son (de las notas de la versión):
- Se agregó una nueva directiva de la sección
[Setup]
:CloseApplications
, que por defecto esyes
. Si se configura en sí y el programa de instalación no se ejecuta de forma silenciosa, el programa de instalación ahora se detendrá en la página del asistente de preparación para la instalación si detecta aplicaciones que utilizan archivos que deben actualizarse mediante la sección[Files]
o[InstallDelete]
, mostrando las aplicaciones y preguntando al usuario si el programa de instalación debería cerrar automáticamente las aplicaciones y reiniciarlas una vez que se haya completado la instalación. Si se configura en sí y el programa de instalación se ejecuta de forma silenciosa, el programa de instalación siempre cerrará y reiniciará dichas aplicaciones, a menos que se le indique que no lo haga a través de la línea de comandos (ver más abajo).- Se agregó una nueva directiva de la sección
[Setup]
:CloseApplicationsFilter
, que por defecto es*.exe,*.dll,*.chm
. Controla qué archivos de configuración comprobará que estén en uso. Establecer esto en*.*
Puede proporcionar una mejor comprobación a expensas de la velocidad.- Se agregó una nueva directiva de la sección
[Setup]
:RestartApplications
, que por defecto esyes
. Nota: para que la instalación pueda reiniciar una aplicación después de que se haya completado la instalación, la aplicación debe estar usando la función de APIRegisterApplicationRestart
Windows.- Se agregaron nuevos parámetros de línea de comandos compatibles con la Configuración:
/NOCLOSEAPPLICATIONS
y/NORESTARTAPPLICATIONS
. Se pueden usar para anular las nuevas directivasCloseApplications
yRestartApplications
.- Agregada nueva función de soporte
[Code]
:RmSessionStarted
.TWizardForm
: Nueva propiedadPreparingMemo
agregada.
He tenido éxito usando 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;
También puede hacerlo en el InitializeSetup
pero si lo hace, tenga en cuenta que todavía no tiene acceso a la {app}
constante. Mi programa no solicita la ruta de instalación, pero la tuya podría.
InnoSetup le permite adjuntar scripts de Pascal a varios lugares en el proceso de construcción. Intente adjuntar un script que llame a ShellExecute. (Es posible que tenga que importar al motor de script si aún no lo tiene).
Intenté usar la respuesta aceptada (y el seguimiento por jachguate) pero no mataría mi solicitud. Parece que parte de la razón fue que la ventana de mi aplicación no tenía texto asociado, pero sea cual sea la razón real, usé el comando de shell para matarlo y eso funcionó. En la sección [código], desea agregar la siguiente función. Se llama justo antes de que se copien los archivos de configuración.
function PrepareToInstall(var NeedsRestart: Boolean): String;
var
ErrorCode: Integer;
begin
ShellExec(''open'', ''taskkill.exe'', ''/f /im MyProg.exe'','''',SW_HIDE,ewNoWait,ErrorCode);
end;
Si está feliz de escribir su propia DLL, puede usar la herramienta de ayuda de la API para TlHelp32.pas para determinar qué aplicaciones se están ejecutando, y luego obtener un identificador de ventana para ellas usando EnumWindows, luego enviar un WM_CLOSE al identificador de ventana.
Es un poco molesto, pero debería funcionar: Tengo algunas clases de envoltorios de utilidades que desarrollé con un amigo hace un tiempo. No puedo recordar si nos basamos en el código de otra persona.
TWindows.ProcessISRunning y TWindows.StopProcess pueden ayudar.
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.
Si está utilizando InnoSetup, podría intentar que su instalador InnoSetup realice un mensaje SendBroadcast de Windows y que su aplicación escuche ese mensaje. Cuando su aplicación recibe el mensaje, debería cerrarse.
Lo he hecho yo mismo con un instalador InnoSetup, y funciona muy bien.
Si la aplicación tiene un Mutex, puede agregar un valor de AppMutex
en su instalador Inno Setup y mostrará un mensaje que le indica al usuario que detenga el programa. Es posible que pueda encontrar el Mutex (si lo tiene) utilizando SysInternals Process Explorer y seleccionando el programa / proceso y mirando los Mangos (CTRL-H) en el Panel Inferior.
Aquí hay un enlace al artículo de KB que menciona varios métodos:
http://www.vincenzo.net/isxkb/index.php?title=Detect_if_an_application_is_running
Alternativamente, puede probar este código (SIN PRUEBA) en el 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;