これは簡単なはずです。インストーラーの起動時に、以前のバージョンのプログラムの実行を停止する必要があります。
ほとんどの人は、これを行うexe
を作成し、InnoSetupが開始する前に呼び出すことを提案しました。 AutoItを使用してexe
を作成しました。これにより、プログラムのすべてのプロセスが強制終了されます。問題は、何かをインストールする前にInnoSetupがそれを呼び出す方法がわからないことです。
ファイルをインストールする前に実行可能ファイルを呼び出すにはどうすればよいですか?
あるいは、プログラムが実行されているかどうかを検出して、ユーザーにプログラムを閉じるように指示できれば、それも機能します。
アプリケーションにミューテックスがある場合は、InnoSetupインストーラーにAppMutex
値を追加すると、プログラムを停止するようにユーザーに通知するメッセージが表示されます。 SysInternals Process Explorerを使用してプログラム/プロセスを選択し、下部ペインのハンドル(CTRL-H)を確認することで、ミューテックス(ある場合)を見つけることができる場合があります。
いくつかの方法について言及しているKB記事へのリンクは次のとおりです。
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;
バージョン 5.5. (2012年5月にリリース)Inno Setupは、Windows Vista以降で Restart Manager APIのサポートを追加しました。
MSDNのリンクされたドキュメントからの引用(私の強調):
ソフトウェアのインストールと更新でシステムの再起動が必要になる主な理由は、更新中のファイルの一部が現在実行中のアプリケーションまたはサービスによって使用されているためです。 Restart Managerを使用すると、重要なアプリケーションとサービスを除くすべてをシャットダウンして再起動できます。これにより、使用中のファイルが解放され、インストール操作を完了できるようになります。また、インストールまたは更新を完了するために必要なシステムの再起動の数を排除または削減することもできます。
良い点は、インストーラーやアプリケーションでカスタムコードを記述して、ユーザーに閉じるように依頼したり、自動的に閉じたりする必要がないことです。
更新の完了後にアプリケーションを再起動する場合は、最初にアプリケーションから RegisterApplicationRestart
関数を呼び出す必要があります。
新しいディレクティブのデフォルト値は、インストーラーの[Files]
セクションに含まれるすべての.exe、.dll、および.chmファイルを閉じます。
これに関連する変更は(リリースノートから):
- 新しい
[Setup]
セクションディレクティブを追加しました:CloseApplications
。デフォルトはyes
です。 yesに設定され、セットアップがサイレントに実行されていない場合、[Files]
または[InstallDelete]
セクションで更新する必要のあるファイルを使用しているアプリケーションを検出すると、セットアップはインストールの準備ウィザードページで一時停止します。アプリケーションと、セットアップがアプリケーションを自動的に閉じて、インストールの完了後に再起動する必要があるかどうかをユーザーに尋ねます。 yesに設定され、セットアップがサイレントに実行されている場合、コマンドラインから指示されない限り、セットアップは常にそのようなアプリケーションを閉じて再起動します(以下を参照)。- 新しい
[Setup]
セクションディレクティブを追加しました:CloseApplicationsFilter
。デフォルトは*.exe,*.dll,*.chm
です。セットアップが使用中であるかどうかをチェックするファイルを制御します。これを*.*
に設定すると、速度を犠牲にしてより良いチェックを提供できます。- 新しい
[Setup]
セクションディレクティブを追加しました:RestartApplications
。デフォルトはyes
です。注:セットアップがインストールの完了後にアプリケーションを再起動できるようにするには、アプリケーションがWindowsRegisterApplicationRestart
API関数を使用している必要があります。- セットアップでサポートされる新しいコマンドラインパラメーター
/NOCLOSEAPPLICATIONS
および/NORESTARTAPPLICATIONS
が追加されました。これらを使用して、新しいCloseApplications
およびRestartApplications
ディレクティブをオーバーライドできます。- 新しい
[Code]
サポート関数を追加しました:RmSessionStarted
。TWizardForm
:新しいPreparingMemo
プロパティを追加しました。
受け入れられた回答(およびjachguateによるフォローアップ)を使用してみましたが、アプリケーションが強制終了されませんでした。理由の一部は、アプリケーションウィンドウにテキストが関連付けられていなかったためと思われますが、本当の理由が何であれ、シェルコマンドを使用してウィンドウを強制終了しました。 [コード]セクションに、次の関数を追加します。セットアップファイルがコピーされる直前に呼び出されます。
function PrepareToInstall(var NeedsRestart: Boolean): String;
var
ErrorCode: Integer;
begin
ShellExec('open', 'taskkill.exe', '/f /im MyProg.exe','',SW_HIDE,ewNoWait,ErrorCode);
end;
InnoSetupを使用している場合は、InnoSetupインストーラーにWindows SendBroadcastMessageを実行させ、アプリケーションにそのメッセージをリッスンさせることを検討できます。アプリケーションがメッセージを受信すると、アプリケーションは自動的にシャットダウンする必要があります。
私はこれをInnoSetupインストーラーで自分で行いましたが、非常にうまく機能します。
プログラムが実行中であることを検出した場合に、ターゲットプログラムを閉じるようにユーザーに促すInnoSetupスクリプトへのリンクを次に示します。ユーザーがプログラムを閉じた後、「再試行」ボタンをクリックしてインストールを続行できます。
http://www.domador.net/extras/code-samples/inno-setup-close-a-program-before-reinstalling-it/
このスクリプトは、Inno SetupExtensionsナレッジベースにあるより単純なスクリプトに基づいています。
http://www.vincenzo.net/isxkb/index.php?title=Call_psvince.dll_on_install_and_uninstall
独自の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.
[〜#〜] 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}
定数にまだアクセスできないことに注意してください。私のプログラムはインストールパスを要求しませんが、あなたのプログラムはそうするかもしれません。
InnoSetupを使用すると、ビルドプロセスのさまざまな場所にPascalスクリプトを添付できます。 ShellExecuteを呼び出すスクリプトを添付してみてください。 (スクリプトエンジンがまだない場合は、スクリプトエンジンにインポートする必要があるかもしれません。)
これを実行する簡単な方法は、DelphiでDLLを作成して、プログラムが実行されているかどうかを検出し、ユーザーにプログラムを閉じるように依頼することです。DLLセットアップで、フラグ「dontcopy」を使用します(例として、Pascal Scripting\Using DLLsの下の http://www.jrsoftware.org/ishelp/ をチェックインします)。
ところで、次回ミューテックスを使用するときは、Inno Setupもそれをサポートしており、はるかに簡単です。
編集:そしてファイルを抽出するために(あなたが言及したその.exeを使用したい場合)、ExtractTemporaryFile()を使用するだけです。