2010-08-17 13 views
16

Questo dovrebbe essere semplice, ho bisogno di interrompere qualsiasi versione precedente del mio programma da eseguire all'avvio del programma di installazione.Chiudere la versione di esecuzione del programma prima di installare l'aggiornamento (Inno Setup)

La maggior parte delle persone ha suggerito di creare un exe che esegue questa operazione e lo chiama prima dell'installazione di Inno. Ho creato un exe usando AutoIt che uccide tutti i processi del mio programma. Il problema è che non so come ottenere Inno Setup per chiamarlo prima che installi qualcosa.

Come si chiama un file eseguibile prima di installare i file?

In alternativa, se riesco solo a rilevare se un programma è in esecuzione e dire all'utente di chiuderlo, ciò funzionerebbe anche.

risposta

28

Se l'applicazione ha un Mutex, è possibile aggiungere un valore AppMutex nel programma di installazione di Inno Setup e verrà visualizzato un messaggio che informa l'utente di interrompere il programma. Potresti riuscire a trovare il Mutex (se ne ha uno) usando SysInternals Process Explorer e selezionando il programma/processo e guardando i Manici (CTRL-H) nel riquadro inferiore.

Ecco un link al un articolo KB che cita diversi metodi:
http://www.vincenzo.net/isxkb/index.php?title=Detect_if_an_application_is_running

In alternativa, si potrebbe provare questo codice (non testata) nella 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; 
+0

Grazie Mirtheil, questo è esattamente ciò di cui avevo bisogno. Tutti gli altri hanno fornito risposte ragionevoli ma questa risulta essere la soluzione perfetta. – Daisetsu

+0

non funziona su Windows 7 –

+0

In realtà, non ha funzionato su nulla. Una volta utilizzato il nome corretto della finestra, funziona su Windows 7 e altri sistemi operativi. Ho anche aggiunto un'alternativa a FindWindowByWindowName, che è FindWindowByClassName. FindWindowByClassName potrebbe essere una scelta migliore se il nome della finestra del tuo programma cambia. – mirtheil

0

InnoSetup consente di allegare script Pascal a vari punti nel processo di creazione. Prova ad allegare uno script che chiami ShellExecute. (Che potresti dover importare nel motore di script se non ce l'ha già.)

+0

Il motore di script ha Exec(), quindi non è questo il problema. Non riesco a capire come scrivere il codice Pascal anche se per estrarre il file exe in bundle ed eseguirlo. – Daisetsu

4

Se stai usando InnoSetup, puoi fare in modo che il tuo programma di installazione di InnoSetup esegua un SendBroadcastMessage di Windows e ottieni l'applicazione per ascoltare quel messaggio. Quando la tua applicazione riceve il messaggio, dovrebbe chiudersi.

L'ho fatto io stesso con un programma di installazione di InnoSetup e funziona molto bene.

+0

Non funzionerà perché il programma è stato rilasciato da anni e avrò bisogno di chiudere anche le vecchie versioni. – Daisetsu

0

Bene, penso che il modo più semplice per eseguire ciò potrebbe essere la creazione di una DLL in Delphi che rileva se il programma è in esecuzione e chiede all'utente di chiuderlo, inserire la DLL nella configurazione e utilizzare il flag "dontcopy" (controllare http://www.jrsoftware.org/ishelp/ in Pascal Scripting \ Using DLLs per un esempio).

Btw, la prossima volta utilizzare i mutex, Inno Setup supporta anche questo ed è molto più semplice.

MODIFICA: e per estrarre un file (se si desidera utilizzare quell'exe che si cita), basta usare ExtractTemporaryFile().

1

Se siete felici di scrivere la propria DLL, è possibile utilizzare l'API di aiuto dello strumento per TlHelp32.pas per determinare quali applicazioni sono in esecuzione, quindi ottenere un handle di finestra per esse utilizzando EnumWindows, quindi inviare un WM_CLOSE all'handle della finestra.

E 'un po' di dolore, ma dovrebbe funzionare: devo alcune classi di utilità involucro che ho sviluppato con un amico un po 'indietro. Non ricordo se l'abbiamo basato sul codice di qualcun altro.

TWindows.ProcessISRunning e TWindows.StopProcess possono essere di aiuto.

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

Impossibile compilare in InnoSetup, qualcuno potrebbe risolverlo? Non ho mai usato Pascal prima. –

3

Ecco un link ad uno script Inno Setup che chiede all'utente di chiudere il programma di destinazione, se rileva che il programma è in esecuzione. Dopo che l'utente chiude il programma, è possibile fare clic su un pulsante "Riprova" per procedere con l'installazione:

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

Questo script è basato su uno script semplice, che si trova nella Inno Setup Estensioni Knowledge Base:

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

13

Nella versione 5.5.0 (Rilasciato il maggio 2012) Inno Setup aggiunto il supporto per l'API Restart Manager su Windows Vista e successivi.

Citazione di MSDN legata documentazione (sottolineatura mia):

L'installazione del software motivo principale e gli aggiornamenti richiedono il riavvio del sistema è che alcuni dei file da aggiornare sono attualmente in uso da un'applicazione in esecuzione o servizio. Restart Manager abilita tutte le applicazioni e i servizi critici da arrestare e riavviare. Ciò libera i file che sono in uso e consente il completamento delle operazioni di installazione. Può inoltre eliminare o ridurre il numero di riavvii di sistema richiesti per completare un'installazione o un aggiornamento.

La cosa buona è che non è necessario scrivere codice personalizzato nell'installer o nella propria applicazione per chiedere all'utente di chiuderlo o chiuderlo automaticamente.

Se si desidera riavviare l'applicazione dopo aver completato l'aggiornamento, è necessario chiamare prima la funzione RegisterApplicationRestart dall'applicazione.

I valori predefiniti per le nuove direttive chiudono tutti i file .exe, .dll e .chm contenuti nella sezione [Files] del programma di installazione.

Le modifiche ad esso correlati sono (da note di rilascio):

  • Aggiunto nuova direttiva [Setup] sezione: CloseApplications, che per default è yes. Se impostato su Sì e l'installazione non viene eseguita automaticamente, l'installazione verrà messa in pausa nella pagina Preparazione all'installazione guidata se rileva le applicazioni utilizzando i file che devono essere aggiornati dalla sezione [Files] o [InstallDelete], mostrando le applicazioni e chiedendo all'utente se l'installazione deve chiudere automaticamente le applicazioni e riavviarle al termine dell'installazione. Se impostato su yes e il programma di installazione è in esecuzione in modalità silenziosa, il programma di installazione chiude e riavvia sempre tali applicazioni, a meno che non venga detto di non farlo tramite la riga di comando (vedere di seguito).
  • Aggiunta nuova direttiva [Setup]: CloseApplicationsFilter, che per impostazione predefinita è *.exe,*.dll,*.chm. Controlla quali file verranno verificati per essere in uso. Impostandolo su *.* è possibile fornire un controllo migliore a scapito della velocità.
  • Aggiunta nuova direttiva [Setup]: RestartApplications, che per impostazione predefinita è yes. Nota: affinché l'installazione sia in grado di riavviare un'applicazione dopo che l'installazione è stata completata, l'applicazione deve utilizzare la funzione API di Windows RegisterApplicationRestart.
  • Aggiunti nuovi parametri della riga di comando supportati dall'installazione: /NOCLOSEAPPLICATIONS e /NORESTARTAPPLICATIONS. Questi possono essere usati per sovrascrivere le nuove direttive CloseApplications e RestartApplications.
  • Aggiunta nuova funzione di supporto [Code]: RmSessionStarted.
  • TWizardForm: Aggiunta nuova proprietà PreparingMemo.
+2

ottima risposta, grazie! –

+0

Il 'CloseApplicationsFilter' è la chiave per molte applicazioni. Espandi il filtro per includere i tipi di file che stanno causando problemi. – mafrosis

7

Ho provato ad utilizzare la risposta accettata (e il follow-up da jachguate) ma non avrebbe ucciso la mia domanda. Sembra che parte della ragione fosse che alla mia finestra dell'applicazione non era associato alcun testo, ma qualunque sia la vera ragione, ho usato il comando shell per ucciderlo e questo ha funzionato. Nella sezione [codice], si desidera aggiungere la seguente funzione. Viene chiamato appena prima che i file di installazione vengano copiati.

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

Ho avuto successo con 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; 

Si può anche farlo nel InitializeSetup, ma se lo fai, tenere a mente che non avete ancora accesso alla costante {app}. Il mio programma non richiede il percorso di installazione, ma il tuo potrebbe.

Problemi correlati