Inno-Setup-issrc/Projects/Src/Setup.Uninstall.pas
2025-05-20 13:35:14 +02:00

821 lines
28 KiB
ObjectPascal

unit Setup.Uninstall;
{
Inno Setup
Copyright (C) 1997-2025 Jordan Russell
Portions by Martijn Laan
For conditions of distribution and use, see LICENSE.TXT.
Uninstaller
}
interface
procedure RunUninstaller;
procedure HandleUninstallerEndSession;
implementation
uses
Windows, SysUtils, Messages, Forms, PathFunc, Shared.CommonFunc.Vcl,
Shared.CommonFunc, Setup.UninstallLog, SetupLdrAndSetup.Messages,
Shared.SetupMessageIDs, SetupLdrAndSetup.InstFunc, Setup.InstFunc, Shared.Struct,
Shared.SetupEntFunc, Setup.UninstallProgressForm, Setup.UninstallSharedFileForm,
Shared.FileClass, Setup.ScriptRunner, Setup.DebugClient, Shared.SetupSteps,
Setup.LoggingFunc, Setup.MainFunc, Setup.SpawnServer;
type
TExtUninstallLog = class(TUninstallLog)
private
FLastUpdateTime: DWORD;
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
UninstallExitCode: DWORD = 1;
UninstExeFilename, UninstDataFilename, UninstMsgFilename: String;
UninstDataFile: TFile;
UninstLog: TExtUninstallLog = nil;
Title: String;
DidRespawn, SecondPhase: Boolean;
EnableLogging, Silent, VerySilent, NoRestart: Boolean;
LogFilename: String;
InitialProcessWnd, FirstPhaseWnd, DebugServerWnd: HWND;
OldWindowProc: Pointer;
procedure ShowExceptionMsg;
var
Msg: String;
begin
if ExceptObject is EAbort then
Exit;
Msg := GetExceptMessage;
Log('Exception message:');
LoggedAppMessageBox(PChar(Msg), Pointer(SetupMessages[msgErrorTitle]),
MB_OK or MB_ICONSTOP, True, IDOK);
{ ^ 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 InitializeUninstallProgressForm;
begin
UninstallProgressForm := AppCreateForm(TUninstallProgressForm) as TUninstallProgressForm;
UninstallProgressForm.Initialize(Title, UninstLog.AppName, ufModernStyle in UninstLog.Flags);
if CodeRunner <> nil then begin
try
CodeRunner.RunProcedures('InitializeUninstallProgressForm', [''], False);
except
Log('InitializeUninstallProgressForm raised an exception (fatal).');
raise;
end;
end;
if not VerySilent then begin
UninstallProgressForm.Show;
{ Ensure the form is fully painted now in case
CurUninstallStepChanged(usUninstall) take a long time to return }
UninstallProgressForm.Update;
end;
end;
procedure TExtUninstallLog.StatusUpdate(StartingCount, CurCount: Integer);
var
NowTime: DWORD;
begin
{ Only update the progress bar if it's at the beginning or end, or if
30 ms has passed since the last update (so that updating the progress
bar doesn't slow down the actual uninstallation process). }
NowTime := GetTickCount;
if (Cardinal(NowTime - FLastUpdateTime) >= Cardinal(30)) or
(StartingCount = CurCount) or (CurCount = 0) then begin
FLastUpdateTime := NowTime;
UninstallProgressForm.UpdateProgress(StartingCount - CurCount, StartingCount);
end;
Application.ProcessMessages;
end;
function LoggedMessageBoxFmt1(const ID: TSetupMessageID; const Arg1: String;
const Title: String; const Flags: UINT; const Suppressible: Boolean;
const Default: Integer): Integer;
begin
Result := LoggedAppMessageBox(PChar(FmtSetupMessage1(ID, Arg1)), PChar(Title),
Flags, Suppressible, Default);
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 := '"' + 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. }
UninstallExitCode := 0;
end;
else
Result := CallWindowProc(OldWindowProc, Wnd, Msg, wParam, lParam);
end;
end;
procedure DeleteUninstallDataFiles;
var
ProcessWnd: HWND;
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
UninstDataFile.Seek(0);
UninstDataFile.Truncate;
except
{ ignore any exceptions, just in case }
end;
FreeAndNil(UninstDataFile);
{ Delete the .dat and .msg files }
DeleteFile(UninstDataFilename);
DeleteFile(UninstMsgFilename);
{ Tell the first phase to terminate, then delete its .exe }
if FirstPhaseWnd <> 0 then begin
if InitialProcessWnd <> 0 then
{ If the first phase respawned, wait on the initial process }
ProcessWnd := InitialProcessWnd
else
ProcessWnd := FirstPhaseWnd;
ProcessID := 0;
if GetWindowThreadProcessId(ProcessWnd, @ProcessID) <> 0 then
Process := OpenProcess(SYNCHRONIZE, False, ProcessID)
else
Process := 0; { shouldn't get here }
SendNotifyMessage(FirstPhaseWnd, WM_KillFirstPhase, 0, 0);
if Process <> 0 then begin
WaitForSingleObject(Process, INFINITE);
CloseHandle(Process);
end;
{ 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. }
if not Debugging then
Sleep(500);
end;
UninstallExitCode := 0;
DelayDeleteFile(False, UninstExeFilename, 13, 50, 250);
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 ProcessCommandLine;
var
WantToSuppressMsgBoxes, ParamIsAutomaticInternal: Boolean;
I: Integer;
ParamName, ParamValue: String;
begin
WantToSuppressMsgBoxes := False;
{ NewParamsForCode will hold all params except automatic internal ones like /SECONDPHASE= and /DEBUGWND=
Actually currently only needed in the second phase, but setting always anyway
Also see Main.InitializeSetup }
NewParamsForCode.Add(NewParamStr(0));
for I := 1 to NewParamCount do begin
SplitNewParamStr(I, ParamName, ParamValue);
ParamIsAutomaticInternal := False;
if CompareText(ParamName, '/Log') = 0 then begin
EnableLogging := True;
LogFilename := '';
end else if CompareText(ParamName, '/Log=') = 0 then begin
EnableLogging := True;
LogFilename := ParamValue;
end else if CompareText(ParamName, '/INITPROCWND=') = 0 then begin
ParamIsAutomaticInternal := True;
DidRespawn := True;
InitialProcessWnd := StrToInt(ParamValue);
end else if CompareText(ParamName, '/SECONDPHASE=') = 0 then begin
ParamIsAutomaticInternal := True;
SecondPhase := True;
UninstExeFilename := ParamValue;
end else if CompareText(ParamName, '/FIRSTPHASEWND=') = 0 then begin
ParamIsAutomaticInternal := True;
FirstPhaseWnd := StrToInt(ParamValue)
end else if CompareText(ParamName, '/SILENT') = 0 then
Silent := True
else if CompareText(ParamName, '/VERYSILENT') = 0 then
VerySilent := True
else if CompareText(ParamName, '/NoRestart') = 0 then
NoRestart := True
else if CompareText(ParamName, '/SuppressMsgBoxes') = 0 then
WantToSuppressMsgBoxes := True
else if CompareText(ParamName, '/DEBUGWND=') = 0 then begin
ParamIsAutomaticInternal := True;
DebugServerWnd := StrToInt(ParamValue);
end;
if not ParamIsAutomaticInternal then
NewParamsForCode.Add(NewParamStr(I));
end;
if WantToSuppressMsgBoxes and (Silent or VerySilent) then
InitSuppressMsgBoxes := True;
end;
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep; HandleException: Boolean);
begin
if CodeRunner <> nil then begin
try
CodeRunner.RunProcedures('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;
function OpenUninstDataFile(const AAccess: TFileAccess): TFile;
begin
Result := nil; { avoid warning }
try
Result := TFile.Create(UninstDataFilename, fdOpenExisting, AAccess, fsNone);
except
on E: EFileError do begin
SetLastError(E.ErrorCode);
RaiseLastError(FmtSetupMessage1(msgUninstallOpenError,
UninstDataFilename));
end;
end;
end;
function RespawnFirstPhaseIfNeeded: Boolean;
var
F: TFile;
Flags: TUninstallLogFlags;
RequireAdmin: Boolean;
begin
Result := False;
if DidRespawn then
Exit;
F := OpenUninstDataFile(faRead);
try
Flags := ReadUninstallLogFlags(F, UninstDataFilename);
finally
F.Free;
end;
RequireAdmin := (ufAdminInstalled in Flags) or (ufPowerUserInstalled in Flags);
if NeedToRespawnSelfElevated(RequireAdmin, False) then begin
RespawnSelfElevated(UninstExeFilename,
Format('/INITPROCWND=$%x ', [Application.Handle]) + GetCmdTail,
UninstallExitCode);
Result := True;
end;
end;
procedure RunFirstPhase;
var
TempDir, TempFile: String;
TempDirExisted: Boolean;
Wnd: HWND;
ProcessHandle: THandle;
begin
{ Copy self to a subdirectory of the 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. }
TempDirExisted := GenerateNonRandomUniqueTempDir(IsAdmin, GetTempDir, TempDir);
TempFile := AddBackslash(TempDir) + '_unins.tmp';
if not TempDirExisted then
try
RestartReplace(False, TempFile, '');
RestartReplace(False, TempDir, '');
except
{ ignore exceptions }
end;
if not CopyFile(PChar(UninstExeFilename), 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
{ Execute the copy of itself ("second phase") }
ProcessHandle := Exec(TempFile, Format('/SECONDPHASE="%s" /FIRSTPHASEWND=$%x ',
[NewParamStr(0), Wnd]) + GetCmdTail);
{ Wait till the second phase process unexpectedly dies or is ready
for the first phase to terminate. }
repeat until ProcessMsgs or (MsgWaitForMultipleObjects(1,
ProcessHandle, False, INFINITE, QS_ALLINPUT) <> WAIT_OBJECT_0+1);
CloseHandle(ProcessHandle);
finally
DestroyWindow(Wnd);
end;
end;
procedure AssignCustomMessages(AData: Pointer; ADataSize: Cardinal);
procedure Corrupted;
begin
InternalError('Custom message data corrupted');
end;
procedure Read(var Buf; const Count: Cardinal);
begin
if Count > ADataSize then
Corrupted;
Move(AData^, Buf, Count);
Dec(ADataSize, Count);
Inc(Cardinal(AData), Count);
end;
procedure ReadString(var S: String);
var
N: Integer;
begin
Read(N, SizeOf(N));
if (N < 0) or (N > $FFFFF) then { sanity check }
Corrupted;
SetString(S, nil, N);
if N <> 0 then
Read(Pointer(S)^, N * SizeOf(S[1]));
end;
var
Count, I: Integer;
CustomMessageEntry: PSetupCustomMessageEntry;
begin
Read(Count, SizeOf(Count));
Entries[seCustomMessage].Capacity := Count;
for I := 0 to Count-1 do begin
CustomMessageEntry := AllocMem(SizeOf(TSetupCustomMessageEntry));
try
ReadString(CustomMessageEntry.Name);
ReadString(CustomMessageEntry.Value);
CustomMessageEntry.LangIndex := -1;
except
SEFreeRec(CustomMessageEntry, SetupCustomMessageEntryStrings, SetupCustomMessageEntryAnsiStrings);
raise;
end;
Entries[seCustomMessage].Add(CustomMessageEntry);
end;
end;
function ExtractCompiledCodeText(S: String): AnsiString;
begin
SetString(Result, PAnsiChar(Pointer(S)), Length(S)*SizeOf(S[1]));
end;
procedure RunSecondPhase;
const
RemovedMsgs: array[Boolean] of TSetupMessageID =
(msgUninstalledMost, msgUninstalledAll);
var
RestartSystem: Boolean;
CompiledCodeData: array[0..6] of String;
CompiledCodeText: AnsiString;
Res, RemovedAll, UninstallNeedsRestart: Boolean;
StartTime: DWORD;
begin
RestartSystem := False;
AllowUninstallerShutdown := True;
try
if DebugServerWnd <> 0 then
SetDebugServerWnd(DebugServerWnd, True);
if EnableLogging then begin
try
if LogFilename = '' then
StartLogging('Uninstall')
else
StartLoggingWithFixedFilename(LogFilename);
except
on E: Exception do begin
E.Message := 'Error creating log file:' + SNewLine2 + E.Message;
raise;
end;
end;
end;
Log('Setup version: ' + SetupTitle + ' version ' + SetupVersion);
Log('Original Uninstall EXE: ' + UninstExeFilename);
Log('Uninstall DAT: ' + UninstDataFilename);
Log('Uninstall command line: ' + GetCmdTail);
LogWindowsVersion;
{ Open the .dat file for read access }
UninstDataFile := OpenUninstDataFile(faRead);
{ Load contents of the .dat file }
UninstLog := TExtUninstallLog.Create;
UninstLog.Load(UninstDataFile, UninstDataFilename);
Title := FmtSetupMessage1(msgUninstallAppFullTitle, UninstLog.AppName);
{ If install was done in Win64, verify that we're still running Win64.
This test shouldn't fail unless the user somehow downgraded their
Windows version, or they're running an uninstaller from another machine
(which they definitely shouldn't be doing). }
if (ufWin64 in UninstLog.Flags) and not IsWin64 then begin
LoggedAppMessageBox(PChar(SetupMessages[msgUninstallOnlyOnWin64]), PChar(Title),
MB_OK or MB_ICONEXCLAMATION, True, IDOK);
Abort;
end;
{ Check if admin privileges are needed to uninstall }
if (ufAdminInstalled in UninstLog.Flags) and not IsAdmin then begin
LoggedAppMessageBox(PChar(SetupMessages[msgOnlyAdminCanUninstall]), PChar(Title),
MB_OK or MB_ICONEXCLAMATION, True, IDOK);
Abort;
end;
{ Reopen the .dat file for exclusive, read/write access and keep it
open for the duration of the uninstall process to prevent a second
instance of the same uninstaller from running. }
FreeAndNil(UninstDataFile);
UninstDataFile := OpenUninstDataFile(faReadWrite);
if not UninstLog.ExtractLatestRecData(utCompiledCode,
SetupBinVersion or Longint($80000000), CompiledCodeData) then
InternalError('Cannot find utCompiledCode record for this version of the uninstaller');
if DebugServerWnd <> 0 then
CompiledCodeText := DebugClientCompiledCodeText
else
CompiledCodeText := ExtractCompiledCodeText(CompiledCodeData[0]);
InitializeAdminInstallMode(ufAdminInstallMode in UninstLog.Flags);
{ Initialize install mode }
if UninstLog.InstallMode64Bit then begin
{ Sanity check: InstallMode64Bit should never be set without ufWin64 }
if not IsWin64 then
InternalError('Install was done in 64-bit mode but not running 64-bit Windows now');
Initialize64BitInstallMode(True);
end
else
Initialize64BitInstallMode(False);
{ Create temporary directory and extract 64-bit helper EXE if necessary }
CreateTempInstallDirAndExtract64BitHelper;
if CompiledCodeText <> '' then begin
{ Setup some global variables which are accessible to [Code] }
InitMainNonSHFolderConsts;
LoadSHFolderDLL;
UninstallExeFilename := UninstExeFilename;
UninstallExpandedAppId := UninstLog.AppId;
UninstallSilent := Silent or VerySilent;
UninstallExpandedApp := CompiledCodeData[2];
UninstallExpandedGroup := CompiledCodeData[3];
UninstallExpandedGroupName := CompiledCodeData[4];
UninstallExpandedLanguage := CompiledCodeData[5];
AssignCustomMessages(Pointer(CompiledCodeData[6]), Length(CompiledCodeData[6])*SizeOf(CompiledCodeData[6][1]));
CodeRunner := TScriptRunner.Create();
CodeRunner.NamingAttribute := CodeRunnerNamingAttribute;
CodeRunner.OnLog := CodeRunnerOnLog;
CodeRunner.OnLogFmt := CodeRunnerOnLogFmt;
CodeRunner.OnDllImport := CodeRunnerOnDllImport;
CodeRunner.OnDebug := CodeRunnerOnDebug;
CodeRunner.OnDebugIntermediate := CodeRunnerOnDebugIntermediate;
CodeRunner.OnException := CodeRunnerOnException;
CodeRunner.LoadScript(CompiledCodeText, DebugClientCompiledCodeDebugInfo);
end;
try
try
if CodeRunner <> nil then begin
try
Res := CodeRunner.RunBooleanFunctions('InitializeUninstall', [''], bcFalse, False, True);
except
Log('InitializeUninstall raised an exception (fatal).');
raise;
end;
if not Res then begin
Log('InitializeUninstall returned False; aborting.');
Abort;
end;
end;
{ Confirm uninstall }
if not Silent and not VerySilent then begin
if LoggedMessageBoxFmt1(msgConfirmUninstall, UninstLog.AppName, Title,
MB_ICONQUESTION or MB_YESNO or MB_DEFBUTTON2, True, IDYES) <> IDYES then
Abort;
end;
CurUninstallStepChanged(usAppMutexCheck, False);
{ Is the app running? }
while UninstLog.CheckMutexes do
{ Yes, tell user to close it }
if LoggedMessageBoxFmt1(msgUninstallAppRunningError, UninstLog.AppName, Title,
MB_OKCANCEL or MB_ICONEXCLAMATION, True, IDCANCEL) <> IDOK then
Abort;
{ Check for active WM_QUERYENDSESSION/WM_ENDSESSION }
while AcceptedQueryEndSessionInProgress do begin
Sleep(10);
Application.ProcessMessages;
end;
{ Disable Uninstall shutdown }
AllowUninstallerShutdown := False;
ShutdownBlockReasonCreate(Application.Handle,
FmtSetupMessage1(msgShutdownBlockReasonUninstallingApp, UninstLog.AppName));
{ Create and show the progress form }
InitializeUninstallProgressForm;
CurUninstallStepChanged(usUninstall, False);
{ Start the actual uninstall process }
StartTime := GetTickCount;
UninstLog.FLastUpdateTime := StartTime;
RemovedAll := UninstLog.PerformUninstall(True, DeleteUninstallDataFiles);
LogFmt('Removed all? %s', [SYesNo[RemovedAll]]);
UninstallNeedsRestart := UninstLog.NeedRestart or (ufAlwaysRestart in UninstLog.Flags);
if (CodeRunner <> nil) and CodeRunner.FunctionExists('UninstallNeedRestart', True) then begin
if not UninstallNeedsRestart then begin
try
if CodeRunner.RunBooleanFunctions('UninstallNeedRestart', [''], bcTrue, False, False) then begin
UninstallNeedsRestart := True;
Log('Will restart because UninstallNeedRestart returned True.');
end;
except
Log('UninstallNeedRestart raised an exception.');
ShowExceptionMsg;
end;
end
else
Log('Not calling UninstallNeedRestart because a restart has already been deemed necessary.');
end;
LogFmt('Need to restart Windows? %s', [SYesNo[UninstallNeedsRestart]]);
{ Ensure at least 1 second has passed since the uninstall process
began, then destroy the form }
if not VerySilent then begin
while True do begin
Application.ProcessMessages;
if Cardinal(GetTickCount - StartTime) >= Cardinal(1000) then
Break;
{ Delay for 10 ms, or until a message arrives }
MsgWaitForMultipleObjects(0, THandle(nil^), False, 10, QS_ALLINPUT);
end;
end;
FreeAndNil(UninstallProgressForm);
CurUninstallStepChanged(usPostUninstall, True);
if not UninstallNeedsRestart then begin
if not Silent and not VerySilent then
LoggedMessageBoxFmt1(RemovedMsgs[RemovedAll], UninstLog.AppName,
Title, MB_ICONINFORMATION or MB_OK or MB_SETFOREGROUND, True, IDOK);
end
else begin
if not NoRestart then begin
if VerySilent or
(LoggedMessageBoxFmt1(msgUninstalledAndNeedsRestart, UninstLog.AppName,
Title, MB_ICONQUESTION or MB_YESNO or MB_SETFOREGROUND, True, IDYES) = IDYES) then
RestartSystem := True;
end;
if not RestartSystem then
Log('Will not restart Windows automatically.');
end;
CurUninstallStepChanged(usDone, True);
except
{ Show any pending exception here *before* DeinitializeUninstall
is called, which could display an exception message of its own }
ShowExceptionMsg;
Abort;
end;
finally
{ Free the form here, too, in case an exception occurred above }
try
FreeAndNil(UninstallProgressForm);
except
ShowExceptionMsg;
end;
if CodeRunner <> nil then begin
try
CodeRunner.RunProcedures('DeinitializeUninstall', [''], False);
except
Log('DeinitializeUninstall raised an exception.');
ShowExceptionMsg;
end;
end;
ShutdownBlockReasonDestroy(Application.Handle);
end;
finally
FreeAndNil(CodeRunner);
UnloadSHFolderDLL;
RemoveTempInstallDir;
UninstLog.Free;
FreeAndNil(UninstDataFile);
end;
if RestartSystem then begin
if not Debugging then begin
Log('Restarting Windows.');
RestartInitiatedByThisProcess := True;
if not RestartComputer then begin
LoggedAppMessageBox(PChar(SetupMessages[msgErrorRestartingComputer]),
PChar(SetupMessages[msgErrorTitle]), MB_OK or MB_ICONEXCLAMATION,
True, IDOK);
end;
end
else
Log('Not restarting Windows because Uninstall is being run from the debugger.');
end;
end;
procedure RunUninstaller;
var
F: TFile;
UninstallerMsgTail: TUninstallerMsgTail;
begin
{ Set default title; it's set again below after the messages are read }
Application.Title := 'Uninstall';
Application.MainFormOnTaskBar := True;
try
InitializeCommonVars;
SetCurrentDir(GetSystemDir);
UninstExeFilename := NewParamStr(0);
ProcessCommandLine; { note: may change UninstExeFile }
UninstDataFilename := PathChangeExt(UninstExeFilename, '.dat');
UninstMsgFilename := PathChangeExt(UninstExeFilename, '.msg');
{ Initialize messages }
F := TFile.Create(UninstExeFilename, fdOpenExisting, faRead, fsRead);
try
F.Seek(F.Size.Lo - SizeOf(UninstallerMsgTail));
F.ReadBuffer(UninstallerMsgTail, SizeOf(UninstallerMsgTail));
if UninstallerMsgTail.ID <> UninstallerMsgTailID then begin
{ No valid UninstallerMsgTail record found at the end of the EXE;
load messages from an external .msg file. }
LoadSetupMessages(UninstMsgFilename, 0, True);
end
else
LoadSetupMessages(UninstExeFilename, UninstallerMsgTail.Offset, True);
finally
F.Free;
end;
LangOptions.DialogFontName := MessagesLangOptions.DialogFontName;
LangOptions.DialogFontSize := MessagesLangOptions.DialogFontSize;
LangOptions.RightToLeft := lfRightToLeft in MessagesLangOptions.Flags;
SetMessageBoxRightToLeft(LangOptions.RightToLeft);
SetMessageBoxCaption(mbInformation, PChar(SetupMessages[msgInformationTitle]));
SetMessageBoxCaption(mbConfirmation, PChar(SetupMessages[msgConfirmTitle]));
SetMessageBoxCaption(mbError, PChar(SetupMessages[msgErrorTitle]));
SetMessageBoxCaption(mbCriticalError, PChar(SetupMessages[msgErrorTitle]));
Application.Title := SetupMessages[msgUninstallAppTitle];
{ Verify that uninstall data file exists }
if not NewFileExists(UninstDataFilename) then begin
LoggedMessageBoxFmt1(msgUninstallNotFound, UninstDataFilename,
SetupMessages[msgUninstallAppTitle], MB_ICONSTOP or MB_OK, True, IDOK);
Abort;
end;
if not SecondPhase then begin
if not RespawnFirstPhaseIfNeeded then
RunFirstPhase;
end
else
RunSecondPhase;
except
ShowExceptionMsg;
end;
{ Call EndDebug after all exception messages have been shown and logged in
the IDE's Debug Output }
EndDebug;
Halt(UninstallExitCode);
end;
procedure HandleUninstallerEndSession;
begin
{ If second phase, remove the temp dir. The self copy made by the first phase will be
deleted on restart. }
if SecondPhase then begin
Log('Detected restart. Removing temporary directory.');
try
UnloadSHFolderDLL;
RemoveTempInstallDir;
except
ShowExceptionMsg;
end;
EndDebug;
{ Don't use Halt. See Setup.dpr }
TerminateProcess(GetCurrentProcess, UninstallExitCode);
end;
end;
end.