📄 uninstall.pas
字号:
unit Uninstall;
{
Inno Setup
Copyright (C) 1997-2004 Jordan Russell
Portions by Martijn Laan
For conditions of distribution and use, see LICENSE.TXT.
Uninstaller
$jrsoftware: issrc/Projects/Uninstall.pas,v 1.29 2004/12/23 19:54:19 jr Exp $
NOTE: This unit was derived from Uninst.dpr rev 1.38.
}
interface
procedure RunUninstaller;
implementation
uses
Windows, SysUtils, Messages, Forms, PathFunc, CmnFunc, CmnFunc2, Undo, Msgs,
MsgIDs, InstFunc, Struct, UninstProgressForm, UninstSharedFileForm,
FileClass, ScriptRunner, DebugClient, SetupTypes, Logging,
Main;
type
TExtUninstallLog = class(TUninstallLog)
private
FNoSharedFileDlgs: Boolean;
FRemoveSharedFiles: Boolean;
protected
procedure HandleException; override;
function ShouldRemoveSharedFile(const Filename: String): Boolean; override;
procedure StatusUpdate(StartingCount, CurCount: Integer); override;
end;
const
WM_KillFirstPhase = WM_USER + 333;
var
ExitCode: Integer = 1;
UninstExeFile, UninstDataFile, UninstMsgFile: String;
UninstLogFile: TFile;
UninstLog: TExtUninstallLog = nil;
Title: String;
SecondPhase, EnableLogging, Silent, VerySilent, NoRestart: Boolean;
LogFilename: String;
FirstPhaseWnd, DebugWnd: HWND;
OldWindowProc: Pointer;
ProgressForm: TUninstProgressForm;
UninstLeadBytes: set of Char;
procedure ShowExceptionMsg;
var
Msg: String;
begin
if ExceptObject is EAbort then
Exit;
Msg := GetExceptMessage;
Log('Exception message:' + SNewLine + Msg);
AppMessageBox(PChar(Msg), Pointer(SetupMessages[msgErrorTitle]),
MB_OK or MB_ICONSTOP);
{ ^ use a Pointer cast instead of a PChar cast so that it will use "nil"
if SetupMessages[msgErrorTitle] is empty due to the messages not being
loaded yet. MessageBox displays 'Error' as the caption if the lpCaption
parameter is nil. }
end;
procedure TExtUninstallLog.HandleException;
begin
ShowExceptionMsg;
end;
function TExtUninstallLog.ShouldRemoveSharedFile(const Filename: String): Boolean;
const
SToAll: array[Boolean] of String = ('', ' to All');
begin
if Silent or VerySilent then
Result := True
else begin
if not FNoSharedFileDlgs then begin
{ FNoSharedFileDlgs will be set to True if a "...to All" button is clicked }
FRemoveSharedFiles := ExecuteRemoveSharedFileDlg(Filename,
FNoSharedFileDlgs);
LogFmt('Remove shared file %s? User chose %s%s', [Filename, SYesNo[FRemoveSharedFiles], SToAll[FNoSharedFileDlgs]]);
end;
Result := FRemoveSharedFiles;
end;
end;
procedure TExtUninstallLog.StatusUpdate(StartingCount, CurCount: Integer);
begin
if not VerySilent then begin
if ProgressForm = nil then begin
ProgressForm := CreateUninstProgressForm(Title, UninstLog.AppName);
Application.BringToFront;
end;
{ Only update the progress bar if it's at the beginning or end, or if
125 msec has passed since the last update (so that updating the progress
bar doesn't slow down the actual uninstallation process). }
if ProgressForm.UpdateTimerExpired or (StartingCount = CurCount) or
(CurCount = 0) then begin
ProgressForm.UpdateTimerExpired := False;
ProgressForm.UpdateProgress(StartingCount - CurCount, StartingCount);
end;
end;
Application.ProcessMessages;
end;
function MessageBoxFmt1(const ID: TSetupMessageID; const Arg1: String;
const Title: String; const Flags: UINT): Integer;
begin
Result := AppMessageBox(PChar(FmtSetupMessage1(ID, Arg1)), PChar(Title), Flags);
end;
procedure RaiseLastError(const S: String);
var
ErrorCode: DWORD;
begin
ErrorCode := GetLastError;
raise Exception.Create(FmtSetupMessage(msgLastErrorMessage,
[S, IntToStr(ErrorCode), Win32ErrorString(ErrorCode)]));
end;
function Exec(const Filename: String; const Parms: String): THandle;
var
CmdLine: String;
StartupInfo: TStartupInfo;
ProcessInfo: TProcessInformation;
begin
CmdLine := AddQuotes(Filename) + ' ' + Parms;
FillChar(StartupInfo, SizeOf(StartupInfo), 0);
StartupInfo.cb := SizeOf(StartupInfo);
if not CreateProcess(nil, PChar(CmdLine), nil, nil, False, 0, nil, nil,
StartupInfo, ProcessInfo) then
RaiseLastError(SetupMessages[msgLdrCannotExecTemp]);
CloseHandle(ProcessInfo.hThread);
Result := ProcessInfo.hProcess;
end;
function ProcessMsgs: Boolean;
var
Msg: TMsg;
begin
Result := False;
while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do begin
if Msg.Message = WM_QUIT then begin
Result := True;
Break;
end;
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end;
function FirstPhaseWindowProc(Wnd: HWND; Msg: UINT; wParam: WPARAM;
lParam: LPARAM): LRESULT; stdcall;
begin
Result := 0;
case Msg of
WM_QUERYENDSESSION: ; { Return zero to deny any shutdown requests }
WM_KillFirstPhase: begin
PostQuitMessage(0);
{ If we got WM_KillFirstPhase, the second phase must have been
successful (up until now, at least). Set an exit code of 0. }
ExitCode := 0;
end;
else
Result := CallWindowProc(OldWindowProc, Wnd, Msg, wParam, lParam);
end;
end;
function SecondPhaseWindowProc(Wnd: HWND; Msg: UINT; wParam: WPARAM;
lParam: LPARAM): LRESULT; stdcall;
begin
if Msg = WM_QUERYENDSESSION then
{ Return zero to deny any shutdown requests }
Result := 0
else
Result := CallWindowProc(OldWindowProc, Wnd, Msg, wParam, lParam);
end;
procedure DeleteUninstallDataFiles;
var
ProcessID: DWORD;
Process: THandle;
begin
Log('Deleting Uninstall data files.');
{ Truncate the .dat file to zero bytes just before relinquishing exclusive
access to it }
try
UninstLogFile.Seek(0);
UninstLogFile.Truncate;
except
{ ignore any exceptions, just in case }
end;
FreeAndNil(UninstLogFile);
{ Delete the .dat and .msg files }
DeleteFile(UninstDataFile);
if UninstMsgFile <> '' then
DeleteFile(UninstMsgFile);
{ Tell the first phase to terminate, then delete its .exe }
if FirstPhaseWnd <> 0 then begin
ProcessID := 0;
GetWindowThreadProcessId(FirstPhaseWnd, @ProcessID);
Process := OpenProcess(STANDARD_RIGHTS_REQUIRED or SYNCHRONIZE, False,
ProcessID);
SendMessage(FirstPhaseWnd, WM_KillFirstPhase, 0, 0);
WaitForSingleObject(Process, INFINITE);
CloseHandle(Process);
{ Sleep for a bit to allow pre-Windows 2000 Add/Remove Programs to finish
bringing itself to the foreground before we take it back below. Also
helps the DelayDeleteFile call succeed on the first try. }
Sleep(500);
end;
ExitCode := 0;
DelayDeleteFile(UninstExeFile, 12);
if Debugging then
DebugNotifyUninstExe('');
{ Pre-Windows 2000 Add/Remove Programs will try to bring itself to the
foreground after the first phase terminates. Take it back. }
Application.BringToFront;
end;
procedure InitializeDialogFont;
begin
LangOptions.DialogFontName := UninstLangOptions.DialogFontName;
LangOptions.DialogFontSize := UninstLangOptions.DialogFontSize;
end;
procedure ProcessCommandLine;
var
C, I: Integer;
S: String;
begin
C := NewParamCount;
I := 1;
while I <= C do begin
S := NewParamStr(I);
if CompareText(S, '/Log') = 0 then begin
EnableLogging := True;
LogFilename := '';
end
else
if CompareText(Copy(S, 1, 5), '/Log=') = 0 then begin
EnableLogging := True;
LogFilename := Copy(S, 6, Maxint);
end
else
if CompareText(Copy(S, 1, 13), '/SECONDPHASE=') = 0 then begin
SecondPhase := True;
UninstExeFile := Copy(S, 14, Maxint);
end
else if CompareText(Copy(S, 1, 15), '/FIRSTPHASEWND=') = 0 then
FirstPhaseWnd := StrToInt(Copy(S, 16, Maxint))
else if CompareText(S, '/SILENT') = 0 then
Silent := True
else if CompareText(S, '/VERYSILENT') = 0 then
VerySilent := True
else if CompareText(S, '/NoRestart') = 0 then
NoRestart := True
else if CompareText(Copy(S, 1, 10), '/DEBUGWND=') = 0 then
DebugWnd := StrToInt(Copy(S, 11, Maxint));
Inc(I);
end;
end;
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep; HandleException: Boolean);
begin
if CodeRunner <> nil then begin
try
CodeRunner.RunProcedure('CurUninstallStepChanged', [Ord(CurUninstallStep)], False);
except
if HandleException then begin
Log('CurUninstallStepChanged raised an exception.');
ShowExceptionMsg;
end
else begin
Log('CurUninstallStepChanged raised an exception (fatal).');
raise;
end;
end;
end;
end;
procedure RunFirstPhase;
var
TempFile: String;
Wnd: HWND;
ProcessHandle: THandle;
begin
{ Copy self to TEMP directory with a name like _iu14D2N.tmp. The
actual uninstallation process must be done from somewhere outside
the application directory since EXE's can't delete themselves while
they are running. }
if not GenerateNonRandomUniqueFilename(GetTempDir, TempFile) then
try
RestartReplace(TempFile, '');
except
{ ignore exceptions }
end;
if not CopyFile(PChar(UninstExeFile), PChar(TempFile), False) then
RaiseLastError(SetupMessages[msgLdrCannotCreateTemp]);
{ Don't want any attribute like read-only transferred }
SetFileAttributes(PChar(TempFile), FILE_ATTRIBUTE_NORMAL);
{ Create first phase window. This window waits for a WM_KillFirstPhase
message from the second phase process, and terminates itself in
response. The reason the first phase doesn't just terminate
immediately is because the Control Panel Add/Remove applet refreshes
its list as soon as the program terminates. So it waits until the
uninstallation is complete before terminating. }
Wnd := CreateWindowEx(0, 'STATIC', '', 0, 0, 0, 0, 0, HWND_DESKTOP, 0,
HInstance, nil);
Longint(OldWindowProc) := SetWindowLong(Wnd, GWL_WNDPROC,
Longint(@FirstPhaseWindowProc));
try
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -