安装更新前关闭运行程序的版本(Inno Setup)

时间:2010-08-17 23:46:41

标签: installer inno-setup

这应该很简单,我需要在安装程序启动时停止运行任何以前版本的程序。

大多数人建议制作exe来执行此操作并在Inno安装程序启动之前调用它。我使用AutoIt创建了一个exe,它可以杀死我程序的所有进程。问题是我不知道如何让Inno Setup在安装任何东西之前调用它。

如何在安装文件之前调用可执行文件?

或者,如果我可以检测一个程序是否正在运行并告诉用户关闭它,那也可以。

10 个答案:

答案 0 :(得分:30)

如果应用程序具有Mutex,您可以在Inno Setup安装程序中添加AppMutex值,它将显示一条消息,告知用户停止该程序。您可以通过使用SysInternals Process Explorer并选择程序/进程并查看下部窗格中的Handles(CTRL-H)来找到Mutex(如果有的话)。

这是一篇知识库文章的链接,提到了几种方法:
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;

答案 1 :(得分:19)

在版本5.5.0(2012年5月发布)中,Inno Setup在Windows Vista及更高版本上添加了对Restart Manager API的支持。

引用MSDN链接文档(强调我的):

  

软件安装和更新需要重新启动系统的主要原因是正在更新的某些文件当前正由正在运行的应用程序或服务使用。 重新启动管理器可以关闭和重新启动除关键应用程序和服务之外的所有应用程序和服务。这将释放正在使用的文件并允许完成安装操作。它还可以消除或减少完成安装或更新所需的系统重启次数。

好处是:您无需在安装程序或应用程序中编写自定义代码,要求用户关闭它,或自动关闭它。

如果您希望在更新完成后重新启动应用程序,则必须先从应用程序中调用RegisterApplicationRestart函数。

新指令的默认值会关闭安装程序[Files]部分中包含的所有.exe,.dll和.chm文件。

与之相关的更改(来自发行说明):

  
      
  • 添加了新的[Setup]部分指令:CloseApplications,默认为yes。如果设置为yes并且安装程序未以静默方式运行,则安装程序现在将在准备安装向导页面上暂停,如果它使用需要由[Files][InstallDelete]部分更新的文件检测到应用程序,则显示应用程序并询问用户安装程序是否应自动关闭应用程序并在安装完成后重新启动它们。如果设置为yes并且安装程序以静默方式运行,则安装程序将始终关闭并重新启动此类应用程序,除非通过命令行告知(请参阅下文)。
  •   
  • 添加了新的[Setup]部分指令:CloseApplicationsFilter,默认为*.exe,*.dll,*.chm。控制安装程序将检查正在使用的文件。将其设置为*.*可以提供更好的检查,但会牺牲速度。
  •   
  • 添加了新的[Setup]部分指令:RestartApplications,默认为yes。注意:要使安装程序能够在安装完成后重新启动应用程序,应用程序需要使用Windows RegisterApplicationRestart API函数。
  •   
  • 添加了安装程序支持的新命令行参数:/NOCLOSEAPPLICATIONS/NORESTARTAPPLICATIONS。这些可用于覆盖新的CloseApplicationsRestartApplications指令。
  •   
  • 添加了新的[Code]支持功能:RmSessionStarted
  •   
  • TWizardForm:添加了新的PreparingMemo属性。
  •   

答案 2 :(得分:11)

我尝试使用接受的答案(以及jachguate的后续操作),但它不会杀死我的应用程序。看起来部分原因是我的应用程序窗口没有与之关联的文本,但无论真正的原因是什么,我使用shell命令来杀死它并且这很有效。在[code]部分中,您要添加以下功能。它在复制安装文件之前调用。

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

答案 3 :(得分:4)

如果您正在使用InnoSetup,您可以考虑让InnoSetup安装程序执行Windows SendBroadcastMessage,并让您的应用程序监听该消息。当您的应用程序收到消息时,它应该自行关闭。

我自己用InnoSetup安装程序完成了这项工作,效果非常好。

答案 4 :(得分:3)

这是一个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

答案 5 :(得分:1)

如果您乐意编写自己的DLL,可以使用TlHelp32.pas的工具帮助API来确定正在运行的应用程序,然后使用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.

答案 6 :(得分:1)

我使用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}常量。我的程序不会要求安装路径,但你的可能会这样。

答案 7 :(得分:0)

InnoSetup允许您将Pascal脚本附加到构建过程中的各个位置。尝试附加调用ShellExecute的脚本。 (如果还没有脚本引擎,则可能必须将其导入脚本引擎。)

答案 8 :(得分:0)

好吧,我认为执行此操作的更简单方法可能是在Delphi中创建一个DLL,用于检测程序是否正在运行并要求用户关闭它,将该DLL放入您的设置并使用“dontcopy”标志(检查在Pascal Scripting下的http://www.jrsoftware.org/ishelp/中使用DLL作为示例。)

顺便说一句,下次使用互斥锁时,Inno Setup也会支持它,并且更加容易。

编辑:并且为了提取文件(如果你想使用你提到的那个.exe),只需使用ExtractTemporaryFile()。

答案 9 :(得分:0)

在[设置]部分中添加CloseApplications = true。

如果设置为yes或force并且安装程序没有以静默方式运行,则安装程序如果检测到使用需要通过[文件]或[安装删除]部分进行更新的文件的应用程序,则安装程序将在“准备安装”向导页面上暂停。应用程序,并询问用户安装程序是否应自动关闭应用程序并在安装完成后重新启动它们。