【问题标题】:Close running version of program before installing update (Inno Setup)在安装更新之前关闭程序的运行版本(Inno Setup)
【发布时间】:2011-03-31 06:54:18
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: installation inno-setup


    【解决方案1】:

    如果应用程序有 Mutex,您可以在 Inno Setup 安装程序中添加 AppMutex 值,它将显示一条消息告诉用户停止程序。您可以通过使用 SysInternals Process Explorer 并选择程序/进程并查看下窗格中的句柄 (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;
    

    【讨论】:

    • 感谢 Mirtheil,这正是我所需要的。其他人都提供了合理的答案,但事实证明这是完美的解决方案。
    • 实际上,它对任何东西都不起作用。一旦使用了正确的窗口名称,它就可以在 Windows 7 和其他操作系统上运行。我还添加了 FindWindowByWindowName 的替代方法,即 FindWindowByClassName。如果程序的窗口名称发生变化,FindWindowByClassName 可能是更好的选择。
    • 对于 Inno 5.5.0+ 有一个新的解决方案,正如我在 late response 中对这个问题所解释的那样。
    • 根据documentation,最好使用PrepareToInstall函数而不是InitializeSetup来关闭应用进行更新。
    • 发送WM_CLOSE 并不意味着应用程序将退出。如果它设置了“关闭到系统托盘”怎么办?你能为这种情况提出解决方案吗?我想可以发送自定义WM_USER+X 消息,或者在更糟糕的情况下执行taskkill/tskill
    【解决方案2】:

    在版本 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 属性。

    【讨论】:

    • CloseApplicationsFilter 是许多应用程序的关键。展开过滤器以包含导致问题的文件类型。
    • 卸载时如何要求相同?请帮忙
    • 有人可以帮助我使用RegisterApplicationRestart 的标志吗?我的应用程序在系统重启后已经自动启动(例如它是一个自动启动服务)所以我猜我应该使用RestartNoReboot = 8?同样在 Inno 安装程序安装新版本(更新)的情况下,使用 RestartNoPatch = 4 是否意味着如果更新失败,它不会自动重启应用程序?
    【解决方案3】:

    我尝试使用接受的答案(以及 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;
    

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

      • 这行不通,因为该程序已经发布多年,我也需要关闭旧版本。
      • @Conor 你能告诉我你是怎么做到的吗?
      • @Kainix:我的应用程序是用 Delphi 编写的。谷歌“DefaultHandler windows 消息广播”或类似的,你应该找到自己的方式。
      【解决方案5】:

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

        • 脚本引擎有 Exec() 所以这不是问题。我不知道如何编写帕斯卡代码来提取捆绑的 exe 文件并运行它。
        【解决方案7】:

        如果您愿意编写自己的 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.
        

        【讨论】:

        • 在 InnoSetup 中编译失败,谁能修复它?我以前从未使用过帕斯卡。
        【解决方案8】:

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

        【讨论】:

          【解决方案9】:

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

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

          编辑:对于提取文件(如果您想使用您提到的那个 .exe),只需使用 ExtractTemporaryFile()。

          【讨论】:

            【解决方案10】:

            在 [Setup] 部分添加 CloseApplications=true。

            如果设置为 yes 或 force 并且安装程序未静默运行,如果安装程序检测到应用程序使用需要由 [Files] 或 [InstallDelete] 部分更新的文件,安装程序将在准备安装向导页面暂停,显示应用程序并询问用户安装程序是否应在安装完成后自动关闭应用程序并重新启动它们。

            【讨论】:

            • CloseApplications=true 是默认值。 @jachguate 的答案已经涵盖了这一点。
            猜你喜欢
            • 2013-11-04
            • 1970-01-01
            • 2010-11-06
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-09-15
            • 1970-01-01
            相关资源
            最近更新 更多