123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820 |
- 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 - 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.
|