Browse Source

Merge remote-tracking branch 'origin/restartmanager'

Martijn Laan 13 years ago
parent
commit
d782d0469e

+ 177 - 0
Components/RestartManager.pas

@@ -0,0 +1,177 @@
+unit RestartManager;
+
+{
+  Basic RestartManager API interface Unit for Delphi 2 and higher
+  by Martijn Laan
+
+  $jrsoftware: issrc/Components/RestartManager.pas,v 1.1.2.1 2012/01/04 16:35:19 mlaan Exp $
+}
+
+{$IFNDEF VER90}
+{$IFNDEF VER93}
+  {$DEFINE Delphi3orHigher}
+{$ENDIF}
+{$ENDIF}
+
+interface
+
+uses
+  {$IFNDEF Delphi3orHigher} OLE2, {$ELSE} ActiveX, {$ENDIF}
+  Windows;
+
+procedure FreeRestartManagerLibrary;
+function InitRestartManagerLibrary: Boolean;
+function UseRestartManager: Boolean;
+
+const
+  RM_SESSION_KEY_LEN = SizeOf(TGUID);
+
+  CCH_RM_SESSION_KEY = RM_SESSION_KEY_LEN*2;
+
+  CCH_RM_MAX_APP_NAME = 255;
+
+  CCH_RM_MAX_SVC_NAME = 63;
+
+  RM_INVALID_TS_SESSION = -1;
+
+  RM_INVALID_PROCESS = -1;
+
+type
+  RM_APP_TYPE = DWORD;
+
+const
+  RmUnknownApp = 0;
+  RmMainWindow = 1;
+  RmOtherWindow = 2;
+  RmService = 3;
+  RmExplorer = 4;
+  RmConsole = 5;
+  RmCritical = 1000;
+
+type
+  RM_SHUTDOWN_TYPE = DWORD;
+
+const
+  RmForceShutdown = $01;
+  RmShutdownOnlyRegistered = $10;
+
+  //RM_APP_STATUS
+
+type
+  RM_REBOOT_REASON = DWORD;
+
+const
+  RmRebootReasonNone = $0;
+  RmRebootReasonPermissionDenied = $1;
+  RmRebootReasonSessionMismatch = $2;
+  RmRebootReasonCriticalProcess = $4;
+  RmRebootReasonCriticalService = $8;
+  RmRebootReasonDetectedSelf = $10;
+
+type
+  RM_UNIQUE_PROCESS = record
+    dwProcessId: DWORD;
+    ProcessStartTime: TFileTime;
+  end;
+
+  RM_PROCESS_INFO = record
+    Process: RM_UNIQUE_PROCESS;
+    strAppName: array[0..CCH_RM_MAX_APP_NAME] of WideChar;
+    strServiceShortName: array[0..CCH_RM_MAX_SVC_NAME] of WideChar;
+    ApplicationType: RM_APP_TYPE;
+    AppStatus: ULONG;
+    TSSessionId: DWORD;
+    bRestartable: BOOL;
+  end;
+
+  //RM_FILTER_TRIGGER
+
+  //RM_FILTER_ACTION
+
+  //RM_FILTER_INFO
+
+  //RM_WRITE_STATUS_CALLBACK
+
+var
+  RmStartSession: function (pSessionHandle: LPDWORD; dwSessionFlags: DWORD; strSessionKey: LPWSTR): DWORD; stdcall;
+
+  RmRegisterResources: function (dwSessionHandle: DWORD; nFiles: UINT; rgsFilenames: Pointer; nApplications: UINT; rgApplications: Pointer; nServices: UINT; rgsServiceNames: Pointer): DWORD; stdcall;
+
+  RmGetList: function (dwSessionHandle: DWORD; pnProcInfoNeeded, pnProcInfo: PUINT; rgAffectedApps: Pointer; lpdwRebootReasons: LPDWORD): DWORD; stdcall;
+
+  RmShutdown: function (dwSessionHandle: DWORD; lActionFlags: ULONG; fnStatus: Pointer): DWORD; stdcall;
+  
+  RmRestart: function (dwSessionHandle: DWORD; dwRestartFlags: DWORD; fnStatus: Pointer): DWORD; stdcall;
+
+  RmEndSession: function (dwSessionHandle: DWORD): DWORD; stdcall;
+
+implementation
+
+//----------------------------------------------------------------------------------------------------------------------
+
+const
+  restartmanagerlib = 'Rstrtmgr.dll';
+
+var
+  RestartManagerLibrary: THandle;
+  ReferenceCount: Integer;  // We have to keep track of several load/unload calls.
+
+procedure FreeRestartManagerLibrary;
+begin
+  if ReferenceCount > 0 then
+    Dec(ReferenceCount);
+
+  if (RestartManagerLibrary <> 0) and (ReferenceCount = 0) then
+  begin
+    FreeLibrary(RestartManagerLibrary);
+    RestartManagerLibrary := 0;
+
+    RmStartSession := nil;
+    RmRegisterResources := nil;
+    RmGetList := nil;
+    RmShutdown := nil;
+    RmRestart := nil;
+    RmEndSession := nil;
+  end;
+end;
+
+//----------------------------------------------------------------------------------------------------------------------
+
+function InitRestartManagerLibrary: Boolean;
+begin
+  Inc(ReferenceCount);
+
+  { Only attempt to load rstrtmgr.dll if running Windows Vista or later }
+  if (RestartManagerLibrary = 0) and (Lo(GetVersion) >= 6) then
+  begin
+    RestartManagerLibrary := LoadLibrary(restartmanagerlib);
+    if RestartManagerLibrary <> 0 then
+    begin
+      RmStartSession := GetProcAddress(RestartManagerLibrary, 'RmStartSession');
+      RmRegisterResources := GetProcAddress(RestartManagerLibrary, 'RmRegisterResources');
+      RmGetList := GetProcAddress(RestartManagerLibrary, 'RmGetList');
+      RmShutdown := GetProcAddress(RestartManagerLibrary, 'RmShutdown');
+      RmRestart := GetProcAddress(RestartManagerLibrary, 'RmRestart');
+      RmEndSession := GetProcAddress(RestartManagerLibrary, 'RmEndSession');
+    end;
+  end;
+  Result := RestartManagerLibrary <> 0;
+end;
+
+//----------------------------------------------------------------------------------------------------------------------
+
+function UseRestartManager: Boolean;
+
+begin
+  Result := RestartManagerLibrary <> 0;
+end;
+
+//----------------------------------------------------------------------------------------------------------------------
+
+
+initialization
+finalization
+  while ReferenceCount > 0 do
+    FreeRestartManagerLibrary;
+
+end.

+ 4 - 1
Components/ScintStylerInnoSetup.pas

@@ -8,7 +8,7 @@ unit ScintStylerInnoSetup;
 
   TInnoSetupStyler: styler for Inno Setup scripts
 
-  $jrsoftware: issrc/Components/ScintStylerInnoSetup.pas,v 1.13 2010/12/30 13:00:11 mlaan Exp $
+  $jrsoftware: issrc/Components/ScintStylerInnoSetup.pas,v 1.13.2.2 2012/01/30 14:44:25 mlaan Exp $
 }
 
 interface
@@ -133,6 +133,8 @@ type
     ssBackSolid,
     ssChangesAssociations,
     ssChangesEnvironment,
+    ssCloseApplications,
+    ssCloseApplicationsFilter,
     ssCompression,
     ssCompressionThreads,
     ssCreateAppDir,
@@ -181,6 +183,7 @@ type
     ssPassword,
     ssPrivilegesRequired,
     ssReserveBytes,
+    ssRestartApplications,
     ssRestartIfNeededByRun,
     ssSetupIconFile,
     ssSetupLogging,

+ 7 - 1
Files/Default.isl

@@ -1,4 +1,4 @@
-; *** Inno Setup version 5.1.11+ English messages ***
+; *** Inno Setup version 5.5.0+ English messages ***
 ;
 ; To download user-contributed translations of this file, go to:
 ;   http://www.jrsoftware.org/files/istrans/
@@ -198,6 +198,10 @@ WizardPreparing=Preparing to Install
 PreparingDesc=Setup is preparing to install [name] on your computer.
 PreviousInstallNotCompleted=The installation/removal of a previous program was not completed. You will need to restart your computer to complete that installation.%n%nAfter restarting your computer, run Setup again to complete the installation of [name].
 CannotContinue=Setup cannot continue. Please click Cancel to exit.
+ApplicationsFound=The following applications are using files that need to be updated by Setup. It is recommended that you allow Setup to automatically close these applications.
+ApplicationsFound2=The following applications are using files that need to be updated by Setup. It is recommended that you allow Setup to automatically close these applications. After the installation has completed Setup will attempt to restart the applications.
+CloseApplications=&Automatically close the applications
+DontCloseApplications=&Do not close the applications
 
 ; *** "Installing" wizard page
 WizardInstalling=Installing
@@ -230,6 +234,7 @@ SetupAborted=Setup was not completed.%n%nPlease correct the problem and run Setu
 EntryAbortRetryIgnore=Click Retry to try again, Ignore to proceed anyway, or Abort to cancel installation.
 
 ; *** Installation status messages
+StatusClosingApplications=Closing applications...
 StatusCreateDirs=Creating directories...
 StatusExtractFiles=Extracting files...
 StatusCreateIcons=Creating shortcuts...
@@ -238,6 +243,7 @@ StatusCreateRegistryEntries=Creating registry entries...
 StatusRegisterFiles=Registering files...
 StatusSavingUninstall=Saving uninstall information...
 StatusRunProgram=Finishing installation...
+StatusRestartingApplications=Restarting applications...
 StatusRollback=Rolling back changes...
 
 ; *** Misc. errors

+ 7 - 1
Files/Languages/Dutch.isl

@@ -4,7 +4,7 @@
 ;
 ; Maintained by Martijn Laan ([email protected])
 ;
-; $jrsoftware: issrc/Files/Languages/Dutch.isl,v 1.28 2012/02/05 18:52:19 mlaan Exp $
+; $jrsoftware: issrc/Files/Languages/Dutch.isl,v 1.27.4.1 2012/01/30 14:44:27 mlaan Exp $
                                      
 [LangOptions]                
 LanguageName=Nederlands      
@@ -183,6 +183,10 @@ WizardPreparing=Bezig met het voorbereiden van de installatie
 PreparingDesc=Setup is bezig met het voorbereiden van de installatie van [name].
 PreviousInstallNotCompleted=De installatie/verwijdering van een vorig programma is niet voltooid. U moet uw computer opnieuw opstarten om die installatie te voltooien.%n%nStart [name] Setup nogmaals als uw computer opnieuw is opgestart.
 CannotContinue=Setup kan niet doorgaan. Klik op annuleren om af te sluiten.
+ApplicationsFound=De volgende programma's gebruiken bestanden die moeten worden bijgewerkt door Setup. U wordt aanbevolen Setup toe te staan om automatisch deze programma's af te sluiten.
+ApplicationsFound2=De volgende programma's gebruiken bestanden die moeten worden bijgewerkt door Setup. U wordt aanbevolen Setup toe te staan om automatisch deze programma's af te sluiten. Nadat de installatie is voltooid zal Setup proberen de applicaties opnieuw op te starten.
+CloseApplications=&Programma's automatisch afsluiten
+DontCloseApplications=Programma's &niet afsluiten
 
 ; *** "Installing" wizard page
 WizardInstalling=Bezig met installeren
@@ -213,6 +217,7 @@ SetupAborted=Setup is niet voltooid.%n%nCorrigeer het probleem en voer Setup opn
 EntryAbortRetryIgnore=Klik op Opnieuw om het opnieuw te proberen, op Negeren om toch door te gaan, of op Afbreken om de installatie af te breken.
 
 ; *** Installation status messages
+StatusClosingApplications=Programma's afsluiten...
 StatusCreateDirs=Mappen maken...
 StatusExtractFiles=Bestanden uitpakken...
 StatusCreateIcons=Snelkoppelingen maken...
@@ -221,6 +226,7 @@ StatusCreateRegistryEntries=Registergegevens instellen...
 StatusRegisterFiles=Bestanden registreren...
 StatusSavingUninstall=Verwijderingsinformatie opslaan...
 StatusRunProgram=Installatie voltooien...
+StatusRestartingApplications=Programma's opnieuw starten...
 StatusRollback=Veranderingen ongedaan maken...
 
 ; *** Misc. errors

+ 131 - 1
Projects/CmnFunc.pas

@@ -8,7 +8,7 @@ unit CmnFunc;
 
   Common VCL functions
 
-  $jrsoftware: issrc/Projects/CmnFunc.pas,v 1.24 2010/05/24 19:17:21 jr Exp $
+  $jrsoftware: issrc/Projects/CmnFunc.pas,v 1.24.4.1 2012/01/30 14:44:28 mlaan Exp $
 }
 
 {$B-}
@@ -55,6 +55,8 @@ procedure ReactivateTopWindow;
 procedure SetMessageBoxCaption(const Typ: TMsgBoxType; const NewCaption: PChar);
 procedure SetMessageBoxRightToLeft(const ARightToLeft: Boolean);
 procedure SetMessageBoxCallbackFunc(const AFunc: TMsgBoxCallbackFunc; const AParam: LongInt);
+function StringsToCommaString(const Strings: TStrings): String;
+procedure SetStringsFromCommaString(const Strings: TStrings; const Value: String);
 
 implementation
 
@@ -377,6 +379,134 @@ begin
   end;
 end;
 
+function QuoteStringIfNeeded(const S: String): String;
+{ Used internally by StringsToCommaString. Adds quotes around the string if
+  needed, and doubles any embedded quote characters.
+  Note: No lead byte checking is done since spaces/commas/quotes aren't used
+  as trail bytes in any of the Far East code pages (CJK). }
+var
+  Len, QuoteCount, I: Integer;
+  HasSpecialChars: Boolean;
+  P: PChar;
+begin
+  Len := Length(S);
+  HasSpecialChars := False;
+  QuoteCount := 0;
+  for I := 1 to Len do begin
+    case S[I] of
+      #0..' ', ',': HasSpecialChars := True;
+      '"': Inc(QuoteCount);
+    end;
+  end;
+  if not HasSpecialChars and (QuoteCount = 0) then begin
+    Result := S;
+    Exit;
+  end;
+
+  SetString(Result, nil, Len + QuoteCount + 2);
+  P := Pointer(Result);
+  P^ := '"';
+  Inc(P);
+  for I := 1 to Len do begin
+    if S[I] = '"' then begin
+      P^ := '"';
+      Inc(P);
+    end;
+    P^ := S[I];
+    Inc(P);
+  end;
+  P^ := '"';
+end;
+
+function StringsToCommaString(const Strings: TStrings): String;
+{ Creates a comma-delimited string from Strings.
+  Note: Unlike Delphi 2's TStringList.CommaText property, this function can
+  handle an unlimited number of characters. }
+var
+  I: Integer;
+  S: String;
+begin
+  if (Strings.Count = 1) and (Strings[0] = '') then
+    Result := '""'
+  else begin
+    Result := '';
+    for I := 0 to Strings.Count-1 do begin
+      S := QuoteStringIfNeeded(Strings[I]);
+      if I = 0 then
+        Result := S
+      else
+        Result := Result + ',' + S;
+    end;
+  end;
+end;
+
+procedure SetStringsFromCommaString(const Strings: TStrings; const Value: String);
+{ Replaces Strings with strings from the comma- or space-delimited Value.
+  Note: No lead byte checking is done since spaces/commas/quotes aren't used
+  as trail bytes in any of the Far East code pages (CJK).
+  Also, this isn't bugged like Delphi 3+'s TStringList.CommaText property --
+  SetStringsFromCommaString(..., 'a,') will add two items, not one. }
+var
+  P, PStart, PDest: PChar;
+  CharCount: Integer;
+  S: String;
+begin
+  Strings.BeginUpdate;
+  try
+    Strings.Clear;
+    P := PChar(Value);
+    while CharInSet(P^, [#1..' ']) do
+      Inc(P);
+    if P^ <> #0 then begin
+      while True do begin
+        if P^ = '"' then begin
+          Inc(P);
+          PStart := P;
+          CharCount := 0;
+          while P^ <> #0 do begin
+            if P^ = '"' then begin
+              Inc(P);
+              if P^ <> '"' then Break;
+            end;
+            Inc(CharCount);
+            Inc(P);
+          end;
+          P := PStart;
+          SetString(S, nil, CharCount);
+          PDest := Pointer(S);
+          while P^ <> #0 do begin
+            if P^ = '"' then begin
+              Inc(P);
+              if P^ <> '"' then Break;
+            end;
+            PDest^ := P^;
+            Inc(P);
+            Inc(PDest);
+          end;
+        end
+        else begin
+          PStart := P;
+          while (P^ > ' ') and (P^ <> ',') do
+            Inc(P);
+          SetString(S, PStart, P - PStart);
+        end;
+        Strings.Add(S);
+        while CharInSet(P^, [#1..' ']) do
+          Inc(P);
+        if P^ = #0 then
+          Break;
+        if P^ = ',' then begin
+          repeat
+            Inc(P);
+          until not CharInSet(P^, [#1..' ']);
+        end;
+      end;
+    end;
+  finally
+    Strings.EndUpdate;
+  end;
+end;
+
 { TWindowDisabler }
 
 const

+ 17 - 1
Projects/CmnFunc2.pas

@@ -8,7 +8,7 @@ unit CmnFunc2;
 
   Common non-VCL functions
 
-  $jrsoftware: issrc/Projects/CmnFunc2.pas,v 1.111 2010/10/22 10:33:26 mlaan Exp $
+  $jrsoftware: issrc/Projects/CmnFunc2.pas,v 1.111.2.1 2012/01/16 21:27:03 mlaan Exp $
 }
 
 {$B-,R-}
@@ -83,6 +83,7 @@ function GetShortName(const LongName: String): String;
 function GetWinDir: String;
 function GetSystemDir: String;
 function GetSysWow64Dir: String;
+function GetSysNativeDir(const IsWin64: Boolean): String;
 function GetTempDir: String;
 function StringChange(var S: String; const FromStr, ToStr: String): Integer;
 function StringChangeEx(var S: String; const FromStr, ToStr: String;
@@ -698,6 +699,21 @@ begin
   end;
 end;
 
+function GetSysNativeDir(const IsWin64: Boolean): String;
+{ Returns the special Sysnative alias, without trailing backslash.
+  Returns '' if there is no Sysnative alias. }
+begin
+  { From MSDN: 32-bit applications can access the native system directory by
+    substituting %windir%\Sysnative for %windir%\System32. WOW64 recognizes
+    Sysnative as a special alias used to indicate that the file system should
+    not redirect the access. The Sysnative alias was added starting
+    with Windows Vista. }
+  if IsWin64 and (Lo(GetVersion) >= 6) then
+    Result := AddBackslash(GetWinDir) + 'Sysnative' { Do not localize }
+  else
+    Result := '';
+end;
+
 function GetTempDir: String;
 { Returns fully qualified path of the temporary directory, with trailing
   backslash. This does not use the Win32 function GetTempPath, due to platform

+ 2 - 1
Projects/CompMsgs.pas

@@ -8,7 +8,7 @@ unit CompMsgs;
 
   Compiler Messages
 
-  $jrsoftware: issrc/Projects/CompMsgs.pas,v 1.120 2011/12/20 16:26:07 mlaan Exp $
+  $jrsoftware: issrc/Projects/CompMsgs.pas,v 1.120.2.1 2012/01/30 14:44:29 mlaan Exp $
 
   All language-specific text used by the compiler is in here. If you want to
   translate it into another language, all you need to change is this unit.
@@ -180,6 +180,7 @@ const
   SCompilerMustNotUsePreviousLanguage = 'UsePreviousLanguage must be set to "no" when AppId includes constants';
   SCompilerDirectiveNotUsingDefault = 'The [Setup] section directive "%s" is not assuming a default value because %s includes constants.';
   SCompilerDirectiveNotUsingPreferredDefault = 'The [Setup] section directive "%s" is defaulting to %s because %s includes constants.';
+  SCompilerDirectiveCloseApplicationsFilterTooLong = 'The [Setup] section directive "CloseApplicationsFilter" contains a pattern that is too long';
 
   { Signing }
   SCompilerSignatureNeeded = 'Signed uninstaller mode is enabled. Using ' +

+ 38 - 1
Projects/Compil32.dpr

@@ -8,7 +8,7 @@ program Compil32;
 
   Compiler
 
-  $jrsoftware: issrc/Projects/Compil32.dpr,v 1.32 2011/01/07 03:34:00 jr Exp $
+  $jrsoftware: issrc/Projects/Compil32.dpr,v 1.32.2.3 2012/01/16 21:27:03 mlaan Exp $
 }
 
 uses
@@ -53,6 +53,42 @@ begin
     Func('JR.InnoSetup.IDE.5');
 end;
 
+procedure RegisterApplicationRestart;
+const
+  RESTART_MAX_CMD_LINE = 1024;
+  RESTART_NO_CRASH = $1;
+  RESTART_NO_HANG = $2;
+  RESTART_NO_PATCH = $4;
+  RESTART_NO_REBOOT = $8;
+var
+  Func: function(pwzCommandLine: PWideChar; dwFlags: DWORD): HRESULT; stdcall;
+  CommandLine: WideString;
+begin
+  { Allow Restart Manager to restart us after updates. }
+
+  Func := GetProcAddress(GetModuleHandle('kernel32.dll'),
+    'RegisterApplicationRestart');
+  if Assigned(Func) then begin
+    { Rebuild the command line, can't just use an exact copy since it might contain
+      relative path names but Restart Manager doesn't restore the working
+      directory. }
+    if CommandLineWizard then
+      CommandLine := '/WIZARD'
+    else begin
+      CommandLine := CommandLineFilename;
+      if CommandLine <> '' then
+        CommandLine := '"' + CommandLine + '"';
+      if CommandLineCompile then
+        CommandLine := '/CC ' + CommandLine;
+    end;
+    
+    if Length(CommandLine) > RESTART_MAX_CMD_LINE then
+      CommandLine := '';
+
+    Func(PWideChar(CommandLine), RESTART_NO_CRASH or RESTART_NO_HANG or RESTART_NO_REBOOT);
+  end;
+end;
+
 procedure CreateMutexes;
 { Creates the two mutexes used by the Inno Setup's own installer/uninstaller to
   see if the compiler is still running.
@@ -147,6 +183,7 @@ begin
   CreateMutexes;
   Application.Initialize;
   CheckParams;
+  RegisterApplicationRestart;
 
   { The 'with' is so that the Delphi IDE doesn't mess with these }
   with Application do begin

+ 48 - 15
Projects/Compile.pas

@@ -8,7 +8,7 @@ unit Compile;
 
   Compiler
 
-  $jrsoftware: issrc/Projects/Compile.pas,v 1.522 2011/12/24 16:14:08 mlaan Exp $
+  $jrsoftware: issrc/Projects/Compile.pas,v 1.522.2.2 2012/01/30 14:44:28 mlaan Exp $
 }
 
 {x$DEFINE STATICPREPROC}
@@ -37,7 +37,7 @@ implementation
 
 uses
   CompPreprocInt, Commctrl, Consts, Classes, IniFiles, TypInfo,
-  PathFunc, CmnFunc2, Struct, Int64Em, CompMsgs, SetupEnt,
+  PathFunc, CmnFunc, CmnFunc2, Struct, Int64Em, CompMsgs, SetupEnt,
   FileClass, Compress, CompressZlib, bzlib, LZMA, ArcFour, SHA1,
   MsgIDs, DebugStruct, VerInfo, ResUpdate, CompResUpdate,
 {$IFDEF STATICPREPROC}
@@ -92,6 +92,8 @@ type
     ssBackSolid,
     ssChangesAssociations,
     ssChangesEnvironment,
+    ssCloseApplications,
+    ssCloseApplicationsFilter,
     ssCompression,
     ssCompressionThreads,
     ssCreateAppDir,
@@ -140,6 +142,7 @@ type
     ssPassword,
     ssPrivilegesRequired,
     ssReserveBytes,
+    ssRestartApplications,
     ssRestartIfNeededByRun,
     ssSetupIconFile,
     ssSetupLogging,
@@ -463,6 +466,8 @@ type
     procedure ProcessExpressionParameter(const ParamName,
       ParamData: String; OnEvalIdentifier: TSimpleExpressionOnEvalIdentifier;
       SlashConvert: Boolean; var ProcessedParamData: String);
+    procedure ProcessWildcardsParameter(const ParamData: String;
+      const AWildcards: TStringList; const TooLongMsg: String);
     procedure ReadDefaultMessages;
 {$IFDEF UNICODE}
     procedure ReadMessagesFromFilesPre(const AFiles: String; const ALangIndex: Integer);
@@ -3135,6 +3140,24 @@ begin
   end;
 end;
 
+procedure TSetupCompiler.ProcessWildcardsParameter(const ParamData: String;
+  const AWildcards: TStringList; const TooLongMsg: String);
+var
+  S, AWildcard: String;
+begin
+  S := PathLowercase(ParamData);
+  while True do begin
+    AWildcard := ExtractStr(S, ',');
+    if AWildcard = '' then
+      Break;
+    { Impose a reasonable limit on the length of the string so
+      that WildcardMatch can't overflow the stack }
+    if Length(AWildcard) >= MAX_PATH then
+      AbortCompileOnLine(TooLongMsg);
+    AWildcards.Add(AWildcard);
+  end;
+end;
+
 { Also present in ScriptFunc_R.pas ! }
 function StrToVersionNumbers(const S: String; var VerData: TSetupVersionData;
   const AllowEmpty: Boolean): Boolean;
@@ -3634,6 +3657,7 @@ var
 
 var
   P: Integer;
+  AIncludes: TStringList;
 begin
   SeparateDirective(Line, KeyName, Value);
 
@@ -3768,6 +3792,21 @@ begin
     ssChangesEnvironment: begin
         SetSetupHeaderOption(shChangesEnvironment);
       end;
+    ssCloseApplications: begin
+        SetSetupHeaderOption(shCloseApplications);
+      end;
+    ssCloseApplicationsFilter: begin
+        if Value = '' then
+          Invalid;
+        AIncludes := TStringList.Create;
+        try
+          ProcessWildcardsParameter(Value, AIncludes,
+            SCompilerDirectiveCloseApplicationsFilterTooLong);
+          SetupHeader.CloseApplicationsFilter := StringsToCommaString(AIncludes);
+        finally
+          AIncludes.Free;
+        end;
+      end;
     ssCompression: begin
         Value := Lowercase(Trim(Value));
         if Value = 'none' then begin
@@ -4062,6 +4101,9 @@ begin
         if (I <> 0) or (ReserveBytes < 0) then
           Invalid;
       end;
+    ssRestartApplications: begin
+        SetSetupHeaderOption(shRestartApplications);
+      end;
     ssRestartIfNeededByRun: begin
         SetSetupHeaderOption(shRestartIfNeededByRun);
       end;
@@ -6199,7 +6241,6 @@ type
   end;
 
 var
-  S, AExclude: String;
   FileList, DirList: TList;
   SortFilesByExtension, SortFilesByName: Boolean;
   I: Integer;
@@ -6353,17 +6394,7 @@ begin
                AStrongAssemblyName := Values[paStrongAssemblyName].Data;
 
                { Excludes }
-               S := PathLowercase(Values[paExcludes].Data);
-               while True do begin
-                 AExclude := ExtractStr(S, ',');
-                 if AExclude = '' then
-                   Break;
-                 { Impose a reasonable limit on the length of the string so
-                   that WildcardMatch can't overflow the stack }
-                 if Length(AExclude) >= MAX_PATH then
-                   AbortCompileOnLine(SCompilerFilesExcludeTooLong);
-                 AExcludes.Add(AExclude);
-               end;
+               ProcessWildcardsParameter(Values[paExcludes].Data, AExcludes, SCompilerFilesExcludeTooLong);
 
                { ExternalSize }
                if Values[paExternalSize].Found then begin
@@ -8327,7 +8358,8 @@ begin
       shShowComponentSizes, shUsePreviousTasks, shUpdateUninstallLogAppName,
       shAllowUNCPath, shUsePreviousUserInfo, shRestartIfNeededByRun,
       shAllowCancelDuringInstall, shWizardImageStretch, shAppendDefaultDirName,
-      shAppendDefaultGroupName, shUsePreviousLanguage];
+      shAppendDefaultGroupName, shUsePreviousLanguage, shCloseApplications,
+      shRestartApplications];
     SetupHeader.PrivilegesRequired := prAdmin;
     SetupHeader.UninstallFilesDir := '{app}';
     SetupHeader.DefaultUserInfoName := '{sysuserinfoname}';
@@ -8344,6 +8376,7 @@ begin
     WizardSmallImageFile := 'compiler:WIZMODERNSMALLIMAGE.BMP';
     DefaultDialogFontName := 'Tahoma';
     SignTool := '';
+    SetupHeader.CloseApplicationsFilter := '*.exe,*.dll,*.chm';
 
     { Read [Setup] section }
     EnumIniSection(EnumSetup, 'Setup', 0, True, True, '', False, False);

+ 45 - 7
Projects/InstFunc.pas

@@ -8,7 +8,7 @@ unit InstFunc;
 
   Misc. installation functions
 
-  $jrsoftware: issrc/Projects/InstFunc.pas,v 1.117 2010/09/21 18:21:01 jr Exp $
+  $jrsoftware: issrc/Projects/InstFunc.pas,v 1.117.4.1 2012/01/16 21:27:04 mlaan Exp $
 }
 
 interface
@@ -57,7 +57,7 @@ function DecrementSharedCount(const RegView: TRegView; const Filename: String):
 procedure DelayDeleteFile(const DisableFsRedir: Boolean; const Filename: String;
   const MaxTries, FirstRetryDelayMS, SubsequentRetryDelayMS: Integer);
 function DelTree(const DisableFsRedir: Boolean; const Path: String;
-  const IsDir, DeleteFiles, DeleteSubdirsAlso: Boolean;
+  const IsDir, DeleteFiles, DeleteSubdirsAlso, BreakOnError: Boolean;
   const DeleteDirProc: TDeleteDirProc; const DeleteFileProc: TDeleteFileProc;
   const Param: Pointer): Boolean;
 function DetermineDefaultLanguage(const GetLanguageEntryProc: TGetLanguageEntryProc;
@@ -106,6 +106,7 @@ procedure RaiseFunctionFailedError(const FunctionName: String);
 procedure RaiseOleError(const FunctionName: String; const ResultCode: HRESULT);
 procedure RefreshEnvironment;
 function ReplaceSystemDirWithSysWow64(const Path: String): String;
+function ReplaceSystemDirWithSysNative(Path: String; const IsWin64: Boolean): String;
 procedure UnregisterFont(const FontName, FontFilename: String);
 function RestartComputer: Boolean;
 procedure RestartReplace(const DisableFsRedir: Boolean; TempFile, DestFile: String);
@@ -285,6 +286,41 @@ begin
   Result := Path;
 end;
 
+function ReplaceSystemDirWithSysNative(Path: String; const IsWin64: Boolean): String;
+{ If the user is running 64-bit Windows Vista or newer and Path
+  begins with 'x:\windows\system32\' it replaces it with
+  'x:\windows\sysnative\' and if Path equals 'x:\windows\system32'
+  it replaces it with 'x:\windows\sysnative'. Otherwise, Path is
+  returned unchanged. }
+var
+  SysNativeDir, SysDir: String;
+  L: Integer;
+begin
+  SysNativeDir := GetSysNativeDir(IsWin64);
+  if SysNativeDir <> '' then begin
+    SysDir := GetSystemDir;
+    if PathCompare(Path, SysDir) = 0 then begin
+    { x:\windows\system32 -> x:\windows\sysnative }
+      Result := SysNativeDir;
+      Exit;
+    end else begin
+    { x:\windows\system32\ -> x:\windows\sysnative\
+      x:\windows\system32\filename -> x:\windows\sysnative\filename }
+      SysDir := AddBackslash(SysDir);
+      L := Length(SysDir);
+      if (Length(Path) = L) or
+         ((Length(Path) > L) and not PathCharIsTrailByte(Path, L+1)) then begin
+                                 { ^ avoid splitting a double-byte character }
+        if PathCompare(Copy(Path, 1, L), SysDir) = 0 then begin
+          Result := SysNativeDir + Copy(Path, L, Maxint);
+          Exit;
+        end;
+      end;
+    end;
+  end;
+  Result := Path;
+end;
+
 procedure RestartReplace(const DisableFsRedir: Boolean; TempFile, DestFile: String);
 { Renames TempFile to DestFile the next time Windows is started. If DestFile
   already existed, it will be overwritten. If DestFile is '' then TempFile
@@ -378,12 +414,14 @@ begin
 end;
 
 function DelTree(const DisableFsRedir: Boolean; const Path: String;
-  const IsDir, DeleteFiles, DeleteSubdirsAlso: Boolean;
+  const IsDir, DeleteFiles, DeleteSubdirsAlso, BreakOnError: Boolean;
   const DeleteDirProc: TDeleteDirProc; const DeleteFileProc: TDeleteFileProc;
   const Param: Pointer): Boolean;
 { Deletes the specified directory including all files and subdirectories in
   it (including those with hidden, system, and read-only attributes). Returns
-  True if it was able to successfully remove everything. }
+  True if it was able to successfully remove everything. If BreakOnError is
+  set to True it will stop and return False the first time a delete failed or
+  DeleteDirProc/DeleteFileProc returned False.  }
 var
   BasePath, FindSpec: String;
   H: THandle;
@@ -426,18 +464,18 @@ begin
             end
             else begin
               if DeleteSubdirsAlso then
-                if not DelTree(DisableFsRedir, BasePath + S, True, True, True,
+                if not DelTree(DisableFsRedir, BasePath + S, True, True, True, BreakOnError,
                    DeleteDirProc, DeleteFileProc, Param) then
                   Result := False;
             end;
           end;
-        until not FindNextFile(H, FindData);
+        until (BreakOnError and not Result) or not FindNextFile(H, FindData);
       finally
         Windows.FindClose(H);
       end;
     end;
   end;
-  if IsDir then begin
+  if (not BreakOnError or Result) and IsDir then begin
     if Assigned(DeleteDirProc) then begin
       if not DeleteDirProc(DisableFsRedir, Path, Param) then
         Result := False;

+ 35 - 4
Projects/Install.pas

@@ -8,7 +8,7 @@ unit Install;
 
   Installation procedures
 
-  $jrsoftware: issrc/Projects/Install.pas,v 1.328 2012/01/16 22:27:53 mlaan Exp $
+  $jrsoftware: issrc/Projects/Install.pas,v 1.327.2.3 2012/01/30 14:44:29 mlaan Exp $
 }
 
 interface
@@ -26,7 +26,7 @@ uses
   InstFunc, InstFnc2, SecurityFunc, Msgs, Main, Logging, Extract, FileClass,
   Compress, SHA1, PathFunc, CmnFunc, CmnFunc2, RedirFunc, Int64Em, MsgIDs,
   Wizard, DebugStruct, DebugClient, VerInfo, ScriptRunner, RegDLL, Helper,
-  ResUpdate, LibFusion, TaskbarProgressFunc, NewProgressBar;
+  ResUpdate, LibFusion, TaskbarProgressFunc, NewProgressBar, RestartManager;
 
 type
   TSetupUninstallLog = class(TUninstallLog)
@@ -2443,10 +2443,10 @@ var
           NotifyBeforeInstallEntry(BeforeInstall);
           case DeleteType of
             dfFiles, dfFilesAndOrSubdirs:
-              DelTree(InstallDefaultDisableFsRedir, ExpandConst(Name), False, True, DeleteType = dfFilesAndOrSubdirs,
+              DelTree(InstallDefaultDisableFsRedir, ExpandConst(Name), False, True, DeleteType = dfFilesAndOrSubdirs, False,
                 nil, nil, nil);
             dfDirIfEmpty:
-              DelTree(InstallDefaultDisableFsRedir, ExpandConst(Name), True, False, False, nil, nil, nil);
+              DelTree(InstallDefaultDisableFsRedir, ExpandConst(Name), True, False, False, False, nil, nil, nil);
           end;
           NotifyAfterInstallEntry(AfterInstall);
         end;
@@ -2730,6 +2730,27 @@ var
     end;
   end;
 
+  procedure ShutdownApplications;
+  const
+    ERROR_FAIL_SHUTDOWN = 351;
+  var
+    Error: DWORD;
+  begin
+    Log('Shutting down applications using our files.');
+
+    RmDoRestart := True;
+
+    Error := RmShutdown(RmSessionHandle, 0, nil);
+    if Error = ERROR_FAIL_SHUTDOWN then begin
+      { Not closing the session, still should call RmRestart in this case. }
+      Log('Some applications could not be shut down.');
+    end else if Error <> ERROR_SUCCESS then begin
+      RmEndSession(RmSessionHandle);
+      LogFmt('RmShutdown returned an error: %d', [Error]);
+      RmDoRestart := False;
+    end;
+  end;
+
 var
   Uninstallable, UninstLogCleared: Boolean;
   I: Integer;
@@ -2790,6 +2811,16 @@ begin
       ProcessTasksEntries;
       ProcessEvents;
 
+      { Shutdown applications, if any }
+      if RmSessionStarted and RmFoundApplications then begin
+        if WizardPreparingYesRadio then begin
+          SetStatusLabelText(SetupMessages[msgStatusClosingApplications]);
+          ShutdownApplications;
+          ProcessEvents;
+        end else
+          Log('User chose not to shutdown applications using our files.');
+      end;
+
       { Process InstallDelete entries, if any }
       ProcessInstallDeleteEntries;
       ProcessEvents;

+ 273 - 196
Projects/Main.pas

@@ -8,7 +8,7 @@ unit Main;
 
   Background form
 
-  $jrsoftware: issrc/Projects/Main.pas,v 1.413 2012/02/05 18:52:20 mlaan Exp $
+  $jrsoftware: issrc/Projects/Main.pas,v 1.412.2.5 2012/01/30 14:44:29 mlaan Exp $
 }
 
 interface
@@ -18,7 +18,7 @@ interface
 uses
   Windows, SysUtils, Messages, Classes, Graphics, Controls, Forms, Dialogs,
   SetupForm, StdCtrls, Struct, DebugStruct, Int64Em, CmnFunc, CmnFunc2,
-  SetupTypes, ScriptRunner, BidiUtils;
+  SetupTypes, ScriptRunner, BidiUtils, RestartManager;
 
 type
   TMainForm = class(TSetupForm)
@@ -103,7 +103,8 @@ var
   InitLang: String;
   InitDir, InitProgramGroup: String;
   InitLoadInf, InitSaveInf: String;
-  InitNoIcons, InitSilent, InitVerySilent, InitNoRestart, InitNoCancel: Boolean;
+  InitNoIcons, InitSilent, InitVerySilent, InitNoRestart, InitNoCloseApplications,
+    InitNoRestartApplications, InitNoCancel: Boolean;
   InitSetupType: String;
   InitComponents, InitTasks: TStringList;
   InitComponentsSpecified: Boolean;
@@ -132,12 +133,13 @@ var
   Entries: array[TEntryType] of TList;
   WizardImage: TBitmap;
   WizardSmallImage: TBitmap;
+  CloseApplicationsFilterList: TStringList;
 
   { User options }
   ActiveLanguage: Integer = -1;
   ActiveLicenseText, ActiveInfoBeforeText, ActiveInfoAfterText: AnsiString;
   WizardUserInfoName, WizardUserInfoOrg, WizardUserInfoSerial, WizardDirValue, WizardGroupValue: String;
-  WizardNoIcons: Boolean;
+  WizardNoIcons, WizardPreparingYesRadio: Boolean;
   WizardSetupType: PSetupTypeEntry;
   WizardComponents, WizardDeselectedComponents, WizardTasks, WizardDeselectedTasks: TStringList;
   NeedToAbortInstall: Boolean;
@@ -145,6 +147,12 @@ var
   { Check/BeforeInstall/AfterInstall 'contants' }
   CheckOrInstallCurrentFileName: String;
 
+  { RestartManager API state.
+    Note: the handle and key might change while running, see TWizardForm.QueryRestartManager. }
+  RmSessionStarted, RmFoundApplications, RmDoRestart: Boolean;
+  RmSessionHandle: DWORD;
+  RmSessionKey: array[0..CCH_RM_SESSION_KEY] of WideChar;
+
   { Other }
   ShowLanguageDialog: Boolean;
   InstallMode: (imNormal, imSilent, imVerySilent);
@@ -210,11 +218,11 @@ procedure NotifyAfterInstallEntry(const AfterInstall: String);
 procedure NotifyAfterInstallFileEntry(const FileEntry: PSetupFileEntry);
 procedure NotifyBeforeInstallEntry(const BeforeInstall: String);
 procedure NotifyBeforeInstallFileEntry(const FileEntry: PSetupFileEntry);
-function PreviousInstallNotCompleted: Boolean;
+function PreviousInstallCompleted: Boolean;
+procedure RegisterResourcesWithRestartManager;
 procedure RemoveTempInstallDir;
 procedure SaveResourceToTempFile(const ResName, Filename: String);
 procedure SetActiveLanguage(const I: Integer);
-procedure SetStringsFromCommaString(const Strings: TStrings; const Value: String);
 procedure SetTaskbarButtonVisibility(const AVisible: Boolean);
 function ShouldDisableFsRedirForFileEntry(const FileEntry: PSetupFileEntry): Boolean;
 function ShouldDisableFsRedirForRunEntry(const RunEntry: PSetupRunEntry): Boolean;
@@ -227,7 +235,6 @@ function ShouldProcessIconEntry(const WizardComponents, WizardTasks: TStringList
   const WizardNoIcons: Boolean; const IconEntry: PSetupIconEntry): Boolean;
 function ShouldProcessRunEntry(const WizardComponents, WizardTasks: TStringList;
   const RunEntry: PSetupRunEntry): Boolean;
-function StringsToCommaString(const Strings: TStrings): String;
 function TestPassword(const Password: String): Boolean;
 procedure UnloadSHFolderDLL;
 function WindowsVersionAtLeast(const AMajor, AMinor: Byte): Boolean;
@@ -596,134 +603,6 @@ begin
       Result[I] := '\';
 end;
 
-function QuoteStringIfNeeded(const S: String): String;
-{ Used internally by StringsToCommaString. Adds quotes around the string if
-  needed, and doubles any embedded quote characters.
-  Note: No lead byte checking is done since spaces/commas/quotes aren't used
-  as trail bytes in any of the Far East code pages (CJK). }
-var
-  Len, QuoteCount, I: Integer;
-  HasSpecialChars: Boolean;
-  P: PChar;
-begin
-  Len := Length(S);
-  HasSpecialChars := False;
-  QuoteCount := 0;
-  for I := 1 to Len do begin
-    case S[I] of
-      #0..' ', ',': HasSpecialChars := True;
-      '"': Inc(QuoteCount);
-    end;
-  end;
-  if not HasSpecialChars and (QuoteCount = 0) then begin
-    Result := S;
-    Exit;
-  end;
-
-  SetString(Result, nil, Len + QuoteCount + 2);
-  P := Pointer(Result);
-  P^ := '"';
-  Inc(P);
-  for I := 1 to Len do begin
-    if S[I] = '"' then begin
-      P^ := '"';
-      Inc(P);
-    end;
-    P^ := S[I];
-    Inc(P);
-  end;
-  P^ := '"';
-end;
-
-function StringsToCommaString(const Strings: TStrings): String;
-{ Creates a comma-delimited string from Strings.
-  Note: Unlike Delphi 2's TStringList.CommaText property, this function can
-  handle an unlimited number of characters. }
-var
-  I: Integer;
-  S: String;
-begin
-  if (Strings.Count = 1) and (Strings[0] = '') then
-    Result := '""'
-  else begin
-    Result := '';
-    for I := 0 to Strings.Count-1 do begin
-      S := QuoteStringIfNeeded(Strings[I]);
-      if I = 0 then
-        Result := S
-      else
-        Result := Result + ',' + S;
-    end;
-  end;
-end;
-
-procedure SetStringsFromCommaString(const Strings: TStrings; const Value: String);
-{ Replaces Strings with strings from the comma- or space-delimited Value.
-  Note: No lead byte checking is done since spaces/commas/quotes aren't used
-  as trail bytes in any of the Far East code pages (CJK).
-  Also, this isn't bugged like Delphi 3+'s TStringList.CommaText property --
-  SetStringsFromCommaString(..., 'a,') will add two items, not one. }
-var
-  P, PStart, PDest: PChar;
-  CharCount: Integer;
-  S: String;
-begin
-  Strings.BeginUpdate;
-  try
-    Strings.Clear;
-    P := PChar(Value);
-    while CharInSet(P^, [#1..' ']) do
-      Inc(P);
-    if P^ <> #0 then begin
-      while True do begin
-        if P^ = '"' then begin
-          Inc(P);
-          PStart := P;
-          CharCount := 0;
-          while P^ <> #0 do begin
-            if P^ = '"' then begin
-              Inc(P);
-              if P^ <> '"' then Break;
-            end;
-            Inc(CharCount);
-            Inc(P);
-          end;
-          P := PStart;
-          SetString(S, nil, CharCount);
-          PDest := Pointer(S);
-          while P^ <> #0 do begin
-            if P^ = '"' then begin
-              Inc(P);
-              if P^ <> '"' then Break;
-            end;
-            PDest^ := P^;
-            Inc(P);
-            Inc(PDest);
-          end;
-        end
-        else begin
-          PStart := P;
-          while (P^ > ' ') and (P^ <> ',') do
-            Inc(P);
-          SetString(S, PStart, P - PStart);
-        end;
-        Strings.Add(S);
-        while CharInSet(P^, [#1..' ']) do
-          Inc(P);
-        if P^ = #0 then
-          Break;
-        if P^ = ',' then begin
-          repeat
-            Inc(P);
-          until not CharInSet(P^, [#1..' ']);
-        end;
-      end;
-    end;
-  finally
-    Strings.EndUpdate;
-  end;
-end;
-
 procedure LoadInf(const FileName: String);
 const
   Section = 'Setup';
@@ -750,6 +629,8 @@ begin
   InitSilent := GetIniBool(Section, 'Silent', InitSilent, FileName);
   InitVerySilent := GetIniBool(Section, 'VerySilent', InitVerySilent, FileName);
   InitNoRestart := GetIniBool(Section, 'NoRestart', InitNoRestart, FileName);
+  InitNoCloseApplications := GetIniBool(Section, 'NoCloseApplications', InitNoCloseApplications, FileName);
+  InitNoRestartApplications := GetIniBool(Section, 'NoRestartApplications', InitNoRestartApplications, FileName);
   InitPassword := GetIniString(Section, 'Password', InitPassword, FileName);
   InitRestartExitCode := GetIniInt(Section, 'RestartExitCode', InitRestartExitCode, 0, 0, FileName);
   InitSaveInf := GetIniString(Section, 'SaveInf', InitSaveInf, FileName);
@@ -1512,7 +1393,7 @@ begin
   if TempInstallDir <> '' then begin
     if Debugging then
       DebugNotifyTempDir('');
-    if not DelTree(False, TempInstallDir, True, True, True, nil,
+    if not DelTree(False, TempInstallDir, True, True, True, False, nil,
        TempDeleteFileProc, Pointer(GetTickCount())) then
       Log('Failed to remove temporary directory: ' + TempInstallDir);
   end;
@@ -1774,37 +1655,23 @@ begin
     (StrComp(FindData.cFileName, '..') <> 0);
 end;
 
-procedure EnumProc(const Filename: String; Param: Pointer);
+type
+  TEnumFilesProc = function(const DisableFsRedir: Boolean; const Filename: String;
+    const Param: Pointer): Boolean;
+
+function DummyDeleteDirProc(const DisableFsRedir: Boolean; const Filename: String;
+    const Param: Pointer): Boolean;
 begin
-  TStringList(Param).Add(PathLowercase(Filename));
+  { We don't actually want to delete the dir, so just return success. }
+  Result := True;
 end;
 
-function PreviousInstallNotCompleted: Boolean;
-var
-  SL: TStringList;
-
-  function CheckForFile(const DisableFsRedir: Boolean; Filename: String): Boolean;
-  var
-    J: Integer;
-  begin
-    if IsNT then begin
-      if not DisableFsRedir then
-        Filename := ReplaceSystemDirWithSysWow64(Filename);
-    end
-    else
-      Filename := GetShortName(Filename);
-    Filename := PathLowercase(Filename);
-    for J := 0 to SL.Count-1 do begin
-      if SL[J] = Filename then begin
-        LogFmt('Found pending rename or delete that matches one of our files: %s', [Filename]);
-        Result := True;
-        Exit;
-      end;
-    end;
-    Result := False;
-  end;
+{ Enumerates the files we're going to install and delete. Returns True on success.
+  Likewise EnumFilesProc should return True on success and return False
+  to break the enum and to cause EnumFiles to return False instead of True. }
+function EnumFiles(const EnumFilesProc: TEnumFilesProc; const Param: Pointer): Boolean;
 
-  function RecurseExternalCheckForFile(const DisableFsRedir: Boolean;
+  function RecurseExternalFiles(const DisableFsRedir: Boolean;
     const SearchBaseDir, SearchSubDir, SearchWildcard: String;
     const SourceIsWildcard: Boolean; const CurFile: PSetupFileEntry): Boolean;
   var
@@ -1813,7 +1680,7 @@ var
     FindData: TWin32FindData;
   begin
     SearchFullPath := SearchBaseDir + SearchSubDir + SearchWildcard;
-    Result := False;
+    Result := True;
 
     H := FindFirstFileRedir(DisableFsRedir, SearchFullPath, FindData);
     if H <> INVALID_HANDLE_VALUE then begin
@@ -1830,8 +1697,8 @@ var
               DestName := DestName + SearchSubDir + FindData.cFileName
             else if SearchSubDir <> '' then
               DestName := PathExtractPath(DestName) + SearchSubDir + PathExtractName(DestName);
-            if CheckForFile(DisableFsRedir, DestName) then begin
-              Result := True;
+            if not EnumFilesProc(DisableFsRedir, DestName, Param) then begin
+              Result := False;
               Exit;
             end;
           end;
@@ -1847,10 +1714,10 @@ var
         try
           repeat
             if IsRecurseableDirectory(FindData) then
-              if RecurseExternalCheckForFile(DisableFsRedir, SearchBaseDir,
+              if not RecurseExternalFiles(DisableFsRedir, SearchBaseDir,
                  SearchSubDir + FindData.cFileName + '\', SearchWildcard,
                  SourceIsWildcard, CurFile) then begin
-                Result := True;
+                Result := False;
                 Exit;
               end;
           until not FindNextFile(H, FindData);
@@ -1867,39 +1734,190 @@ var
   DisableFsRedir: Boolean;
   SourceWildcard: String;
 begin
-  Result := False;
+  Result := True;
+
+  { [Files] }
+  for I := 0 to Entries[seFile].Count-1 do begin
+    CurFile := PSetupFileEntry(Entries[seFile][I]);
+    if (CurFile^.FileType = ftUserFile) and
+       ShouldProcessFileEntry(WizardComponents, WizardTasks, CurFile, False) then begin
+      DisableFsRedir := ShouldDisableFsRedirForFileEntry(CurFile);
+      if CurFile^.LocationEntry <> -1 then begin
+        { Non-external file }
+        if not EnumFilesProc(DisableFsRedir, ExpandConst(CurFile^.DestName), Param) then begin
+          Result := False;
+          Exit;
+        end;
+      end
+      else begin
+        { External file }
+        SourceWildcard := ExpandConst(CurFile^.SourceFilename);
+        if not RecurseExternalFiles(DisableFsRedir, PathExtractPath(SourceWildcard), '',
+           PathExtractName(SourceWildcard), IsWildcard(SourceWildcard), CurFile) then begin
+          Result := False;
+          Exit;
+        end;
+      end;
+    end;
+  end;
+
+  { [InstallDelete] }
+    for I := 0 to Entries[seInstallDelete].Count-1 do
+      with PSetupDeleteEntry(Entries[seInstallDelete][I])^ do
+        if ShouldProcessEntry(WizardComponents, WizardTasks, Components, Tasks, Languages, Check) then begin
+          case DeleteType of
+            dfFiles, dfFilesAndOrSubdirs:
+              if not DelTree(InstallDefaultDisableFsRedir, ExpandConst(Name), False, True, DeleteType = dfFilesAndOrSubdirs, True,
+                 DummyDeleteDirProc, EnumFilesProc, nil) then begin
+                Result := False;
+                Exit;
+              end;
+            dfDirIfEmpty:
+              if not DelTree(InstallDefaultDisableFsRedir, ExpandConst(Name), True, False, False, True,
+                 DummyDeleteDirProc, EnumFilesProc, nil) then begin
+                Result := False;
+                Exit;
+              end;
+          end;
+        end;
+end;
+
+procedure EnumProc(const Filename: String; Param: Pointer);
+begin
+  TStringList(Param).Add(PathLowercase(Filename));
+end;
+
+var
+  CheckForFileSL: TStringList;
+
+function CheckForFile(const DisableFsRedir: Boolean; const AFilename: String;
+  const Param: Pointer): Boolean;
+var
+  Filename: String;
+  J: Integer;
+begin
+  Filename := AFilename;
+  if IsNT then begin
+    if not DisableFsRedir then
+      Filename := ReplaceSystemDirWithSysWow64(Filename);
+  end
+  else
+    Filename := GetShortName(Filename);
+  Filename := PathLowercase(Filename);
+  for J := 0 to CheckForFileSL.Count-1 do begin
+    if CheckForFileSL[J] = Filename then begin
+      LogFmt('Found pending rename or delete that matches one of our files: %s', [Filename]);
+      Result := False; { Break the enum, just need to know if any matches }
+      Exit;
+    end;
+  end;
+  Result := True; { Success! }
+end;
+
+{ Checks if no file we're going to install or delete has a pending rename or delete. }
+function PreviousInstallCompleted: Boolean;
+begin
+  Result := True;
   if Entries[seFile].Count = 0 then
     Exit;
-  SL := TStringList.Create;
+  CheckForFileSL := TStringList.Create;
   try
-    EnumFileReplaceOperationsFilenames(EnumProc, SL);
-    if SL.Count = 0 then
+    EnumFileReplaceOperationsFilenames(EnumProc, CheckForFileSL);
+    if CheckForFileSL.Count = 0 then
       Exit;
-    for I := 0 to Entries[seFile].Count-1 do begin
-      CurFile := PSetupFileEntry(Entries[seFile][I]);
-      if (CurFile^.FileType = ftUserFile) and
-         ShouldProcessFileEntry(WizardComponents, WizardTasks, CurFile, False) then begin
-        DisableFsRedir := ShouldDisableFsRedirForFileEntry(CurFile);
-        if CurFile^.LocationEntry <> -1 then begin
-          { Non-external file }
-          if CheckForFile(DisableFsRedir, ExpandConst(CurFile^.DestName)) then begin
-            Result := True;
-            Exit;
-          end;
-        end
-        else begin
-          { External file }
-          SourceWildcard := ExpandConst(CurFile^.SourceFilename);
-          if RecurseExternalCheckForFile(DisableFsRedir, PathExtractPath(SourceWildcard), '',
-             PathExtractName(SourceWildcard), IsWildcard(SourceWildcard), CurFile) then begin
-            Result := True;
-            Exit;
-          end;
-        end;
+    Result := EnumFiles(CheckForFile, nil);
+  finally
+    CheckForFileSL.Free;
+  end;
+end;
+
+type
+  TArrayOfPWideChar = array[0..(MaxInt div SizeOf(PWideChar))-1] of PWideChar;
+  PArrayOfPWideChar = ^TArrayOfPWideChar;
+
+var
+  RegisterFileFilenames: PArrayOfPWideChar;
+  RegisterFileFilenamesMax, RegisterFileFilenamesCount: Integer;
+
+function RegisterFile(const DisableFsRedir: Boolean; const AFilename: String;
+  const Param: Pointer): Boolean;
+var
+  Filename: String;
+  I, Len: Integer;
+  Match: Boolean;
+begin
+  Filename := AFilename;
+
+  { First: check filter. }
+  if Filename <> '' then begin
+    Match := False;
+    for I := 0 to CloseApplicationsFilterList.Count-1 do begin
+      if WildcardMatch(PChar(PathExtractName(Filename)), PChar(CloseApplicationsFilterList[I])) then begin
+        Match := True;
+        Break;
       end;
     end;
+    if not Match then begin
+      Result := True;
+      Exit;
+    end;
+  end;
+
+  { Secondly: check if we need to register this batch, either because the batch is full
+    or because we're done scanning and have leftovers. }
+  if ((Filename <> '') and (RegisterFileFilenamesCount = RegisterFileFilenamesMax)) or
+     ((Filename = '') and (RegisterFileFilenamesCount > 0)) then begin
+    if RmRegisterResources(RmSessionHandle, RegisterFileFilenamesCount, RegisterFileFilenames, 0, nil, 0, nil) = ERROR_SUCCESS then begin
+      for I := 0 to RegisterFileFilenamesCount-1 do
+        FreeMem(RegisterFileFilenames[I]);
+      RegisterFileFilenamesCount := 0;
+    end else begin
+      RmEndSession(RmSessionHandle);
+      RmSessionStarted := False;
+    end;
+  end;
+
+  { Finally: add this file to the batch. }
+  if RmSessionStarted and (FileName <> '') then begin
+    { From MSDN: "Installers should not disable file system redirection before calling
+      the Restart Manager API. This means that a 32-bit installer run on 64-bit Windows
+      is unable register a file in the %windir%\system32 directory." This is incorrect,
+      we can register such files by using the Sysnative alias. Note: the Sysnative alias
+      is only available on Windows Vista and newer, but so is Restart Manager. }
+    if DisableFsRedir then
+      Filename := ReplaceSystemDirWithSysNative(Filename, IsWin64);
+
+    Len := Length(Filename);
+    GetMem(RegisterFileFilenames[RegisterFileFilenamesCount], (Len + 1) * SizeOf(RegisterFileFilenames[RegisterFileFilenamesCount][0]));
+    {$IFNDEF UNICODE}
+      RegisterFileFilenames[RegisterFileFilenamesCount][MultiByteToWideChar(CP_ACP, 0, PChar(Filename), Len, RegisterFileFilenames[RegisterFileFilenamesCount], Len)] := #0;
+    {$ELSE}
+      StrPCopy(RegisterFileFilenames[RegisterFileFilenamesCount], Filename);
+    {$ENDIF}
+    Inc(RegisterFileFilenamesCount);
+  end;
+
+  Result := RmSessionStarted; { Break the enum if there was an error, else continue. }
+end;
+
+{ Register all files we're going to install or delete. Ends RmSession on errors. }
+procedure RegisterResourcesWithRestartManager;
+var
+  I: Integer;
+begin
+  { Note: MSDN says we shouldn't call RmRegisterResources for each file because of speed, but calling
+    it once for all files adds extra memory usage, so calling it in batches. }
+  RegisterFileFilenamesMax := 1000;
+  GetMem(RegisterFileFilenames, RegisterFileFilenamesMax * SizeOf(RegisterFileFilenames[0]));
+  try
+    EnumFiles(RegisterFile, nil);
+    { Don't forget to register leftovers. }
+    if RmSessionStarted then
+      RegisterFile(False, '', nil);
   finally
-    SL.Free;
+    for I := 0 to RegisterFileFilenamesCount-1 do
+      FreeMem(RegisterFileFilenames[I]);
+    FreeMem(RegisterFileFilenames);
   end;
 end;
 
@@ -2701,6 +2719,12 @@ begin
     if CompareText(ParamName, '/NoRestart') = 0 then
       InitNoRestart := True
     else
+    if CompareText(ParamName, '/NoCloseApplications') = 0 then
+      InitNoCloseApplications := True
+    else
+    if CompareText(ParamName, '/NoRestartApplications') = 0 then
+      InitNoRestartApplications := True
+    else
     if CompareText(ParamName, '/NoIcons') = 0 then
       InitNoIcons := True
     else
@@ -3014,6 +3038,15 @@ begin
   if shEncryptionUsed in SetupHeader.Options then
     LoadDecryptDLL;
 
+  { Start RestartManager session }
+  if (shCloseApplications in SetupHeader.Options) and not InitNoCloseApplications then begin
+    InitRestartManagerLibrary;
+    if UseRestartManager and (RmStartSession(@RmSessionHandle, 0, RmSessionKey) = ERROR_SUCCESS) then begin
+      RmSessionStarted := True;
+      SetStringsFromCommaString(CloseApplicationsFilterList, SetupHeader.CloseApplicationsFilter);
+    end;
+  end;
+
   { Set install mode }
   SetupInstallMode;
 
@@ -3231,6 +3264,10 @@ begin
 
   FreeFileExtractor;
 
+  { End RestartManager session }
+  if RmSessionStarted then
+    RmEndSession(RmSessionHandle);
+
   { Free the _iscrypt.dll handle }
   if DecryptDLLHandle <> 0 then
     FreeLibrary(DecryptDLLHandle);
@@ -3699,6 +3736,40 @@ function TMainForm.Install: Boolean;
     end;
   end;
 
+  procedure RestartApplications;
+  const
+    ERROR_FAIL_RESTART = 353;
+  var
+    Error: DWORD;
+    WindowDisabler: TWindowDisabler;
+  begin
+    if not NeedsRestart then begin
+      WizardForm.StatusLabel.Caption := SetupMessages[msgStatusRestartingApplications];
+      WizardForm.StatusLabel.Update;
+
+      Log('Attempting to restart applications.');
+
+      { Disable windows during application restart so that a nice
+        "beep" is produced if the user tries clicking on WizardForm }
+      WindowDisabler := TWindowDisabler.Create;
+      try
+        Error := RmRestart(RmSessionHandle, 0, nil);
+      finally
+        WindowDisabler.Free;
+      end;
+      Application.BringToFront;
+
+      if Error = ERROR_FAIL_RESTART then
+        Log('One or more applications could not be restarted.')
+      else if Error <> ERROR_SUCCESS then begin
+        RmEndSession(RmSessionHandle);
+        RmSessionStarted := False;
+        LogFmt('RmRestart returned an error: %d', [Error]);
+      end;
+    end else
+      Log('Need to restart Windows, not attempting to restart applications');
+  end;
+
 var
   Succeeded: Boolean;
   S: String;
@@ -3715,6 +3786,7 @@ begin
     WizardSetupType := WizardForm.GetSetupType();
     WizardForm.GetComponents(WizardComponents, WizardDeselectedComponents);
     WizardForm.GetTasks(WizardTasks, WizardDeselectedTasks);
+    WizardPreparingYesRadio := WizardForm.PreparingYesRadio.Checked;
     if InitSaveInf <> '' then
       SaveInf(InitSaveInf);
 
@@ -3734,11 +3806,14 @@ begin
       TerminateApp;
       Exit;
     end;
-    { Can't cancel at any point after PerformInstall, so disable the button } 
+    { Can't cancel at any point after PerformInstall, so disable the button }
     WizardForm.CancelButton.Enabled := False;
 
     ProcessRunEntries;
 
+    if RmDoRestart and (shRestartApplications in SetupHeader.Options) and not InitNoRestartApplications then
+      RestartApplications;
+
     SetStep(ssPostInstall, True);
 
     { Notify Windows of assocations/environment changes *after* ssPostInstall
@@ -3927,7 +4002,7 @@ begin
      WizardForm.CancelButton.CanFocus then begin
     case CurStep of
       ssPreInstall:
-        if ConfirmCancel(WizardForm.CurPageID <> wpPreparing) then begin
+        if ConfirmCancel((WizardForm.CurPageID <> wpPreparing) or (WizardForm.PrepareToInstallFailureMessage = '')) then begin
           if WizardForm.CurPageID = wpPreparing then
             SetupExitCode := ecPrepareToInstallFailed
           else
@@ -4203,10 +4278,12 @@ initialization
   CreateEntryLists;
   DeleteFilesAfterInstallList := TStringList.Create;
   DeleteDirsAfterInstallList := TStringList.Create;
+  CloseApplicationsFilterList := TStringList.Create;
 
 finalization
   FreeAndNil(WizardImage);
   FreeAndNil(WizardSmallImage);
+  FreeAndNil(CloseApplicationsFilterList);
   FreeAndNil(DeleteDirsAfterInstallList);
   FreeAndNil(DeleteFilesAfterInstallList);
   FreeEntryLists;

+ 7 - 1
Projects/Msgids.pas

@@ -8,7 +8,7 @@ unit MsgIDs;
 
   Message identifiers
 
-  $jrsoftware: issrc/Projects/MsgIDs.pas,v 1.34 2007/02/22 20:45:40 jr Exp $
+  $jrsoftware: issrc/Projects/MsgIDs.pas,v 1.34.12.2 2012/01/30 14:44:29 mlaan Exp $
 }
 
 interface
@@ -23,6 +23,8 @@ type
     msgAboutSetupNote,
     msgAboutSetupTitle,
     msgAdminPrivilegesRequired,
+    msgApplicationsFound,
+    msgApplicationsFound2,
     msgBadDirName32,
     msgBadGroupName,
     msgBeveledLabel,
@@ -45,6 +47,7 @@ type
     msgChangeDiskTitle,
     msgClickFinish,
     msgClickNext,
+    msgCloseApplications,
     msgCompactInstallation,
     msgComponentSize1,
     msgComponentSize2,
@@ -62,6 +65,7 @@ type
     msgDiskSpaceMBLabel,
     msgDiskSpaceWarning,
     msgDiskSpaceWarningTitle,
+    msgDontCloseApplications,
     msgEntryAbortRetryIgnore,
     msgErrorChangingAttr,
     msgErrorCopying,
@@ -177,12 +181,14 @@ type
     msgShowReadmeCheck,
     msgSourceDoesntExist,
     msgSourceIsCorrupted,
+    msgStatusClosingApplications,
     msgStatusCreateDirs,
     msgStatusCreateIcons,
     msgStatusCreateIniEntries,
     msgStatusCreateRegistryEntries,
     msgStatusExtractFiles,
     msgStatusRegisterFiles,
+    msgStatusRestartingApplications,
     msgStatusRollback,
     msgStatusSavingUninstall,
     msgStatusRunProgram,

+ 2 - 1
Projects/ScriptClasses_C.pas

@@ -8,7 +8,7 @@ unit ScriptClasses_C;
 
   Script support classes (compile time)
 
-  $Id: ScriptClasses_C.pas,v 1.68 2011/05/19 04:15:34 jr Exp $
+  $Id: ScriptClasses_C.pas,v 1.68.2.1 2012/01/04 16:35:20 mlaan Exp $
 }
 
 interface
@@ -380,6 +380,7 @@ begin
     RegisterProperty('SelectStartMenuFolderBrowseLabel', 'TNewStaticText', iptr);
     RegisterProperty('PreparingYesRadio', 'TNewRadioButton', iptr);
     RegisterProperty('PreparingNoRadio', 'TNewRadioButton', iptr);
+    RegisterProperty('PreparingMemo', 'TNewMemo', iptr);
     RegisterProperty('CurPageID', 'Integer', iptr);
     RegisterMethod('function AdjustLabelHeight(ALabel:TNewStaticText):Integer');
     RegisterMethod('procedure IncTopDecHeight(AControl:TControl;Amount:Integer)');

+ 3 - 1
Projects/ScriptClasses_R.pas

@@ -8,7 +8,7 @@ unit ScriptClasses_R;
 
   Script support classes (run time)
 
-  $Id: ScriptClasses_R.pas,v 1.60 2010/02/10 13:10:34 mlaan Exp $
+  $Id: ScriptClasses_R.pas,v 1.60.6.1 2012/01/04 16:35:20 mlaan Exp $
 }
 
 interface
@@ -287,6 +287,7 @@ procedure TWizardFormSelectDirBrowseLabel(Self: TWizardForm; var T: TNewStaticTe
 procedure TWizardFormSelectStartMenuFolderBrowseLabel(Self: TWizardForm; var T: TNewStaticText); begin T := Self.SelectStartMenuFolderBrowseLabel; end;
 procedure TWizardFormPreparingYesRadio_R(Self: TWizardForm; var T: TNewRadioButton); begin T := Self.PreparingYesRadio; end;
 procedure TWizardFormPreparingNoRadio_R(Self: TWizardForm; var T: TNewRadioButton); begin T := Self.PreparingNoRadio; end;
+procedure TWizardFormPreparingMemo_R(Self: TWizardForm; var T: TNewMemo); begin T := Self.PreparingMemo; end;
 procedure TWizardFormPrevAppDir_R(Self: TWizardForm; var T: String); begin T := Self.PrevAppDir; end;
 
 procedure RegisterWizardForm_R(Cl: TPSRuntimeClassImporter);
@@ -373,6 +374,7 @@ begin
     RegisterPropertyHelper(@TWizardFormSelectStartMenuFolderBrowseLabel, nil,'SelectStartMenuFolderBrowseLabel');
     RegisterPropertyHelper(@TWizardFormPreparingYesRadio_R, nil, 'PreparingYesRadio');
     RegisterPropertyHelper(@TWizardFormPreparingNoRadio_R, nil, 'PreparingNoRadio');
+    RegisterPropertyHelper(@TWizardFormPreparingMemo_R, nil, 'PreparingMemo');
     RegisterPropertyHelper(@TWizardFormCurPageID_R, nil, 'CurPageID');
     RegisterMethod(@TWizardForm.AdjustLabelHeight, 'AdjustLabelHeight');
     RegisterMethod(@TWizardForm.IncTopDecHeight, 'IncTopDecHeight');

+ 6 - 4
Projects/ScriptFunc.pas

@@ -8,7 +8,7 @@ unit ScriptFunc;
 
   Script support functions
 
-  $jrsoftware: issrc/Projects/ScriptFunc.pas,v 1.90 2010/02/25 06:16:41 jr Exp $
+  $jrsoftware: issrc/Projects/ScriptFunc.pas,v 1.90.6.2 2012/01/16 21:27:04 mlaan Exp $
 }
 
 interface
@@ -49,7 +49,7 @@ const
   );
 
    { CmnFunc2 }
-  CmnFunc2Table: array [0..54] of AnsiString =
+  CmnFunc2Table: array [0..55] of AnsiString =
   (
     'function FileExists(const Name: String): Boolean;',
     'function DirExists(const Name: String): Boolean;',
@@ -77,6 +77,7 @@ const
     'function GetWinDir: String;',
     'function GetSystemDir: String;',
     'function GetSysWow64Dir: String;',
+    'function GetSysNativeDir: String;',
     'function GetTempDir: String;',
     'function StringChange(var S: String; const FromStr, ToStr: String): Integer;',
     'function StringChangeEx(var S: String; const FromStr, ToStr: String; const SupportDBCS: Boolean): Integer;',
@@ -161,7 +162,7 @@ const
   );
 
   { Main }
-  MainTable: array [0..18] of AnsiString =
+  MainTable: array [0..19] of AnsiString =
   (
     'function WizardForm: TWizardForm;',
     'function MainForm: TMainForm;',
@@ -181,7 +182,8 @@ const
     'function IsWin64: Boolean;',
     'function Is64BitInstallMode: Boolean;',
     'function ProcessorArchitecture: TSetupProcessorArchitecture;',
-    'function CustomMessage(const MsgName: String): String;'
+    'function CustomMessage(const MsgName: String): String;',
+    'function RmSessionStarted: Boolean;'
   );
 
   { Msgs }

+ 6 - 2
Projects/ScriptFunc_R.pas

@@ -8,7 +8,7 @@ unit ScriptFunc_R;
 
   Script support functions (run time)
 
-  $jrsoftware: issrc/Projects/ScriptFunc_R.pas,v 1.172 2011/12/24 16:14:09 mlaan Exp $
+  $jrsoftware: issrc/Projects/ScriptFunc_R.pas,v 1.172.2.2 2012/01/16 21:27:04 mlaan Exp $
 }
 
 interface
@@ -504,6 +504,8 @@ begin
     Stack.SetString(PStart, GetSystemDir());
   end else if Proc.Name = 'GETSYSWOW64DIR' then begin
     Stack.SetString(PStart, GetSysWow64Dir());
+  end else if Proc.Name = 'GETSYSNATIVEDIR' then begin
+    Stack.SetString(PStart, GetSysNativeDir(IsWin64));
   end else if Proc.Name = 'GETTEMPDIR' then begin
     Stack.SetString(PStart, GetTempDir());
   end else if Proc.Name = 'STRINGCHANGE' then begin
@@ -765,7 +767,7 @@ begin
   end else if Proc.Name = 'DELAYDELETEFILE' then begin
     DelayDeleteFile(ScriptFuncDisableFsRedir, Stack.GetString(PStart), Stack.GetInt(PStart-1), 250, 250);
   end else if Proc.Name = 'DELTREE' then begin
-    Stack.SetBool(PStart, DelTree(ScriptFuncDisableFsRedir, Stack.GetString(PStart-1), Stack.GetBool(PStart-2), Stack.GetBool(PStart-3), Stack.GetBool(PStart-4), nil, nil, nil));
+    Stack.SetBool(PStart, DelTree(ScriptFuncDisableFsRedir, Stack.GetString(PStart-1), Stack.GetBool(PStart-2), Stack.GetBool(PStart-3), Stack.GetBool(PStart-4), False, nil, nil, nil));
   end else if Proc.Name = 'GENERATEUNIQUENAME' then begin
     Stack.SetString(PStart, GenerateUniqueName(ScriptFuncDisableFsRedir, Stack.GetString(PStart-1), Stack.GetString(PStart-2)));
   end else if Proc.Name = 'GETCOMPUTERNAMESTRING' then begin
@@ -1080,6 +1082,8 @@ begin
     Stack.SetInt(PStart, Integer(ProcessorArchitecture));
   end else if Proc.Name = 'CUSTOMMESSAGE' then begin
     Stack.SetString(PStart, CustomMessage(Stack.GetString(PStart-1)));
+  end else if Proc.Name = 'RMSESSIONSTARTED' then begin
+    Stack.SetBool(PStart, RmSessionStarted);
   end else
     Result := False;
 end;

+ 10 - 8
Projects/Struct.pas

@@ -9,7 +9,7 @@ unit Struct;
   Various records and other types that are shared by the ISCmplr, Setup,
   SetupLdr, and Uninst projects
 
-  $jrsoftware: issrc/Projects/Struct.pas,v 1.264 2011/12/24 17:51:04 mlaan Exp $
+  $jrsoftware: issrc/Projects/Struct.pas,v 1.264.2.4 2012/02/05 18:43:13 mlaan Exp $
 }
 
 interface
@@ -19,8 +19,8 @@ uses
 
 const
   SetupTitle = 'Inno Setup';
-  SetupVersion = '5.4.3 '{$IFDEF UNICODE}+'(u)'{$ELSE}+'(a)'{$ENDIF};
-  SetupBinVersion = (5 shl 24) + (4 shl 16) + (3 shl 8) + 0;
+  SetupVersion = '5.5.0 '{$IFDEF UNICODE}+'(u)'{$ELSE}+'(a)'{$ENDIF};
+  SetupBinVersion = (5 shl 24) + (5 shl 16) + (0 shl 8) + 0;
 
 type
   TSetupID = array[0..63] of AnsiChar;
@@ -35,10 +35,10 @@ const
     this file it's recommended you change SetupID. Any change will do (like
     changing the letters or numbers), as long as your format is
     unrecognizable by the standard Inno Setup. }
-  SetupID: TSetupID = 'Inno Setup Setup Data (5.4.4)'{$IFDEF UNICODE}+' (u)'{$ENDIF};
+  SetupID: TSetupID = 'Inno Setup Setup Data (5.5.0)'{$IFDEF UNICODE}+' (u)'{$ENDIF};
   UninstallLogID: array[Boolean] of TUninstallLogID =
     ('Inno Setup Uninstall Log (b)', 'Inno Setup Uninstall Log (b) 64-bit');
-  MessagesHdrID: TMessagesHdrID = 'Inno Setup Messages (5.1.11)'{$IFDEF UNICODE}+' (u)'{$ENDIF};
+  MessagesHdrID: TMessagesHdrID = 'Inno Setup Messages (5.5.0)'{$IFDEF UNICODE}+' (u)'{$ENDIF};
   MessagesLangOptionsID: TMessagesLangOptionsID = '!mlo!001';
   ZLIBID: TCompID = 'zlb'#26;
   DiskSliceID: TDiskSliceID = 'idska32'#26;
@@ -66,7 +66,8 @@ type
     shAllowCancelDuringInstall, shWizardImageStretch, shAppendDefaultDirName,
     shAppendDefaultGroupName, shEncryptionUsed, shChangesEnvironment,
     {$IFNDEF UNICODE}shShowUndisplayableLanguages, {$ENDIF}shSetupLogging,
-    shSignedUninstaller, shUsePreviousLanguage, shDisableWelcomePage);
+    shSignedUninstaller, shUsePreviousLanguage, shDisableWelcomePage,
+    shCloseApplications, shRestartApplications);
   TSetupLanguageDetectionMethod = (ldUILanguage, ldLocale, ldNone);
   TSetupCompressMethod = (cmStored, cmZip, cmBzip, cmLZMA, cmLZMA2);
   TSetupSalt = array[0..7] of Byte;
@@ -78,7 +79,7 @@ const
     ('Unknown', 'x86', 'x64', 'Itanium');
 
 const
-  SetupHeaderStrings = 26;
+  SetupHeaderStrings = 27;
   SetupHeaderAnsiStrings = 4;
 type
   TSetupHeader = packed record
@@ -87,7 +88,8 @@ type
       DefaultGroupName, BaseFilename, UninstallFilesDir, UninstallDisplayName,
       UninstallDisplayIcon, AppMutex, DefaultUserInfoName, DefaultUserInfoOrg,
       DefaultUserInfoSerial, AppReadmeFile, AppContact, AppComments,
-      AppModifyPath, CreateUninstallRegKey, Uninstallable: String;
+      AppModifyPath, CreateUninstallRegKey, Uninstallable,
+      CloseApplicationsFilter: String;
     LicenseText, InfoBeforeText, InfoAfterText, CompiledCodeText: AnsiString;
 {$IFNDEF UNICODE}
     LeadBytes: set of AnsiChar;

+ 2 - 2
Projects/Undo.pas

@@ -8,7 +8,7 @@ unit Undo;
 
   Uninstallation Procedures
 
-  $jrsoftware: issrc/Projects/Undo.pas,v 1.127 2010/04/06 16:56:46 mlaan Exp $
+  $jrsoftware: issrc/Projects/Undo.pas,v 1.127.4.1 2012/01/16 21:27:04 mlaan Exp $
 }
 
 { Note: This unit is shared by both the 'Setup' and 'Uninst' projects }
@@ -985,7 +985,7 @@ begin
                  CurRecData[0], CurRec^.ExtraData and utDeleteDirOrFiles_IsDir <> 0,
                  CurRec^.ExtraData and utDeleteDirOrFiles_DeleteFiles <> 0,
                  CurRec^.ExtraData and utDeleteDirOrFiles_DeleteSubdirsAlso <> 0,
-                 DeleteDirProc, DeleteFileProc, @DeleteDirData) then begin
+                 False, DeleteDirProc, DeleteFileProc, @DeleteDirData) then begin
                 if (CurRec^.ExtraData and utDeleteDirOrFiles_IsDir <> 0) and
                    (CurRec^.ExtraData and utDeleteDirOrFiles_CallChangeNotify <> 0) then begin
                   SHChangeNotify(SHCNE_RMDIR, SHCNF_PATH, CurRecDataPChar[0], nil);

BIN
Projects/Wizard.dfm


+ 18 - 4
Projects/Wizard.dfm.txt

@@ -496,23 +496,37 @@ object WizardForm: TWizardForm
           object PreparingYesRadio: TNewRadioButton
             Left = 24
             Top = 28
-            Width = 301
+            Width = 393
             Height = 17
             Caption = '*'
             Checked = True
-            TabOrder = 1
+            TabOrder = 2
             TabStop = True
             Visible = False
           end
           object PreparingNoRadio: TNewRadioButton
             Left = 24
             Top = 56
-            Width = 301
+            Width = 393
             Height = 17
             Caption = '*'
-            TabOrder = 2
+            TabOrder = 3
             Visible = False
           end
+          object PreparingMemo: TNewMemo
+            Left = 24
+            Top = 88
+            Width = 393
+            Height = 145
+            Color = clBtnFace
+            Lines.Strings = (
+              'PreparingMemo')
+            ReadOnly = True
+            ScrollBars = ssBoth
+            TabOrder = 1
+            WantReturns = False
+            WordWrap = False
+          end
         end
         object InstallingPage: TNewNotebookPage
           object FilenameLabel: TNewStaticText

+ 148 - 24
Projects/Wizard.pas

@@ -8,7 +8,7 @@ unit Wizard;
 
   Wizard form
 
-  $jrsoftware: issrc/Projects/Wizard.pas,v 1.225 2012/01/10 09:44:37 mlaan Exp $
+  $jrsoftware: issrc/Projects/Wizard.pas,v 1.224.2.6 2012/02/05 18:43:13 mlaan Exp $
 }
 
 interface
@@ -158,6 +158,7 @@ type
     SelectStartMenuFolderBrowseLabel: TNewStaticText;
     PreparingYesRadio: TNewRadioButton;
     PreparingNoRadio: TNewRadioButton;
+    PreparingMemo: TNewMemo;
     procedure NextButtonClick(Sender: TObject);
     procedure BackButtonClick(Sender: TObject);
     procedure CancelButtonClick(Sender: TObject);
@@ -193,6 +194,7 @@ type
     procedure FindPreviousData;
     function GetPreviousPageID: Integer;
     function PrepareToInstall: String;
+    function QueryRestartManager: String;
     procedure RegisterExistingPage(const ID: Integer;
      const AOuterNotebookPage, AInnerNotebookPage: TNewNotebookPage;
      const ACaption, ADescription: String);
@@ -229,6 +231,7 @@ type
     procedure IncTopDecHeight(const AControl: TControl; const Amount: Integer);
     function PageFromID(const ID: Integer): TWizardPage;
     function PageIndexFromID(const ID: Integer): Integer;
+    procedure UpdateCurPageButtonVisibility;
     procedure SetCurPage(const NewPageID: Integer);
     procedure UpdateRunList(const SelectedComponents, SelectedTasks: TStringList);
     function ValidateDirEdit: Boolean;
@@ -248,7 +251,7 @@ implementation
 
 uses
   ShellApi, ShlObj, Msgs, Main, PathFunc, CmnFunc, CmnFunc2,
-  MD5, InstFunc, SelFolderForm, Extract, Logging;
+  MD5, InstFunc, SelFolderForm, Extract, Logging, RestartManager;
 
 {$R *.DFM}
 
@@ -903,8 +906,6 @@ begin
   { Initialize wpPreparing page }
   RegisterExistingPage(wpPreparing, InnerPage, PreparingPage,
     SetupMessages[msgWizardPreparing], ExpandSetupMessage(msgPreparingDesc));
-  PreparingYesRadio.Caption := SetupMessages[msgYesRadio];
-  PreparingNoRadio.Caption := SetupMessages[msgNoRadio];
 
   { Initialize wpInstalling page }
   RegisterExistingPage(wpInstalling, InnerPage, InstallingPage,
@@ -1616,11 +1617,14 @@ begin
   PreparingLabel.Visible := False;
   PreparingYesRadio.Visible := False;
   PreparingNoRadio.Visible := False;
-  if PreviousInstallNotCompleted then begin
+  PreparingMemo.Visible := False;
+  if not PreviousInstallCompleted then begin
     Result := ExpandSetupMessage(msgPreviousInstallNotCompleted);
     PrepareToInstallNeedsRestart := True;
   end else if (CodeRunner <> nil) and CodeRunner.FunctionExists('PrepareToInstall') then begin
     SetCurPage(wpPreparing);
+    BackButton.Visible := False;
+    NextButton.Visible := False;
     if InstallMode = imSilent then begin
       SetActiveWindow(Application.Handle);  { ensure taskbar button is selected }
       WizardForm.Show;
@@ -1633,6 +1637,7 @@ begin
       PrepareToInstallNeedsRestart := (Result <> '') and CodeNeedsRestart;
     finally
       WindowDisabler.Free;
+      UpdateCurPageButtonVisibility;
     end;
     Application.BringToFront;
   end;
@@ -1649,13 +1654,100 @@ begin
     if PrepareToInstallNeedsRestart then begin
       Y := PreparingLabel.Top + PreparingLabel.Height;
       PreparingYesRadio.Top := Y;
+      PreparingYesRadio.Caption := SetupMessages[msgYesRadio];
       PreparingYesRadio.Visible := True;
       PreparingNoRadio.Top := Y + ScalePixelsY(22);
+      PreparingNoRadio.Caption := SetupMessages[msgNoRadio];
       PreparingNoRadio.Visible := True;
     end;
   end;
 end;
 
+var
+  DidRegisterResources: Boolean;
+
+function TWizardForm.QueryRestartManager: String;
+type
+  TArrayOfProcessInfo = array[0..(MaxInt div SizeOf(RM_PROCESS_INFO))-1] of RM_PROCESS_INFO;
+  PArrayOfProcessInfo = ^TArrayOfProcessInfo;
+var
+  Y, I: Integer;
+  ProcessInfosCount, ProcessInfosCountNeeded, RebootReasons: Integer;
+  ProcessInfos: PArrayofProcessInfo;
+  AppName: String;
+begin
+  { Clear existing registered resources if we get here a second time (user clicked Back after first time). There
+    doesn't seem to be function to do this directly, so restart the session instead. }
+  if DidRegisterResources then begin
+    RmEndSession(RmSessionHandle);
+    if RmStartSession(@RmSessionHandle, 0, RmSessionKey) <> ERROR_SUCCESS then
+      RmSessionStarted := False;
+  end;
+
+  if RmSessionStarted then begin
+    RegisterResourcesWithRestartManager;
+    DidRegisterResources := True;
+  end;
+
+  if RmSessionStarted then begin
+    ProcessInfosCount := 0;
+    ProcessInfosCountNeeded := 5; { Start with 5 to hopefully avoid a realloc }
+    ProcessInfos := nil;
+    try
+      while ProcessInfosCount < ProcessInfosCountNeeded do begin
+        if ProcessInfos <> nil then
+          FreeMem(ProcessInfos);
+        GetMem(ProcessInfos, ProcessInfosCountNeeded * SizeOf(ProcessInfos[0]));
+        ProcessInfosCount := ProcessInfosCountNeeded;
+
+        if not RmGetList(RmSessionHandle, @ProcessInfosCountNeeded, @ProcessInfosCount, ProcessInfos, @RebootReasons) in [ERROR_SUCCESS, ERROR_MORE_DATA] then begin
+          RmEndSession(RmSessionHandle);
+          RmSessionStarted := False;
+          Break;
+        end;
+      end;
+
+      if RmSessionStarted and (ProcessInfosCount > 0) then begin
+        for I := 0 to ProcessInfosCount-1 do begin
+          AppName := WideCharToString(ProcessInfos[I].strAppName);
+          LogFmt('RestartManager found an application using one of our files: %s', [AppName]);
+          if RebootReasons = RmRebootReasonNone then begin
+            if Result <> '' then
+              Result := Result + #13#10;
+            Result := Result + AppName;
+          end;
+        end;
+        LogFmt('Can use RestartManager to avoid reboot? %s (%d)', [SYesNo[RebootReasons = RmRebootReasonNone], RebootReasons]);
+      end;
+    finally
+      if ProcessInfos <> nil then
+        FreeMem(ProcessInfos);
+    end;
+  end;
+
+  if Result <> '' then begin
+    if (shRestartApplications in SetupHeader.Options) and not InitNoRestartApplications then
+      PreparingLabel.Caption := SetupMessages[msgApplicationsFound2]
+    else
+      PreparingLabel.Caption := SetupMessages[msgApplicationsFound];
+    Y := PreparingLabel.Top + PreparingLabel.Height + ScalePixelsY(12);
+    PreparingMemo.Top := Y;
+    IncTopDecHeight(PreparingMemo, AdjustLabelHeight(PreparingLabel));
+    AdjustLabelHeight(PreparingLabel);
+    PreparingErrorBitmapImage.Visible := True;
+    PreparingLabel.Visible := True;
+    PreparingMemo.Text := Result;
+    PreparingMemo.Visible := True;
+    Y := PreparingMemo.Top + PreparingMemo.Height + ScalePixelsY(12);
+    PreparingYesRadio.Top := Y;
+    PreparingYesRadio.Caption := SetupMessages[msgCloseApplications];
+    PreparingYesRadio.Visible := True;
+    PreparingNoRadio.Top := Y + ScalePixelsY(22);
+    PreparingNoRadio.Caption := SetupMessages[msgDontCloseApplications];
+    PreparingNoRadio.Visible := True;
+  end;
+end;
+
 procedure TWizardForm.UpdatePage(const PageID: Integer);
 
   procedure ReadyMemoAppend(const Lines: String);
@@ -1776,8 +1868,10 @@ var
 begin
   if CurPageID = wpReady then
     NewActiveControl := NextButton
-  else if (CurPageID = wpPreparing) and not PrepareToInstallNeedsRestart then
+  else if (CurPageID = wpPreparing) and (PrepareToInstallFailureMessage <> '') and not PrepareToInstallNeedsRestart then
     NewActiveControl := CancelButton
+  else if (CurPageID = wpPreparing) and (PrepareToInstallFailureMessage = '') and PreparingYesRadio.CanFocus then
+    NewActiveControl := PreparingYesRadio
   else
     NewActiveControl := FindNextControl(nil, True, True, False);
   if (NewActiveControl = BackButton) and NextButton.CanFocus then
@@ -1803,35 +1897,21 @@ begin
   Result := -1;
 end;
 
-procedure TWizardForm.SetCurPage(const NewPageID: Integer);
-{ Changes which page is currently visible }
+procedure TWizardForm.UpdateCurPageButtonVisibility;
 var
   PageIndex: Integer;
   Page: TWizardPage;
   Flags: UINT;
 begin
-  PageIndex := PageIndexFromID(NewPageID);
+  PageIndex := PageIndexFromID(CurPageID);
   Page := FPageList[PageIndex];
-  CurPageID := NewPageID;
 
-  { Select the page in the notebooks }
-  if Assigned(Page.InnerNotebookPage) then
-    InnerNotebook.ActivePage := Page.InnerNotebookPage;
-  OuterNotebook.ActivePage := Page.OuterNotebookPage;
-
-  { Set the page description }
-  Page.SyncCaptionAndDescription;
-
-  BeveledLabel.Visible := (SetupMessages[msgBeveledLabel] <> '') and
-    not(CurPageID in [wpWelcome, wpFinished]);
-
-  { Set button visibility and captions }
   if not(psNoButtons in Page.Style) then begin
     BackButton.Visible := (CurPageID <> wpInstalling) and (GetPreviousPageID <> -1);
     NextButton.Visible := CurPageID <> wpInstalling;
     case CurPageID of
       wpLicense: NextButton.Enabled := LicenseAcceptedRadio.Checked;
-      wpPreparing: NextButton.Enabled := PrepareToInstallNeedsRestart;
+      wpPreparing: NextButton.Enabled := (PrepareToInstallFailureMessage = '') or PrepareToInstallNeedsRestart;
     else
       NextButton.Enabled := True;
     end;
@@ -1851,6 +1931,29 @@ begin
   else
     Flags := MF_GRAYED;
   EnableMenuItem(GetSystemMenu(Handle, False), SC_CLOSE, MF_BYCOMMAND or Flags);
+end;
+
+procedure TWizardForm.SetCurPage(const NewPageID: Integer);
+{ Changes which page is currently visible }
+var
+  Page: TWizardPage;
+begin
+  Page := PageFromID(NewPageID);
+  CurPageID := NewPageID;
+
+  { Select the page in the notebooks }
+  if Assigned(Page.InnerNotebookPage) then
+    InnerNotebook.ActivePage := Page.InnerNotebookPage;
+  OuterNotebook.ActivePage := Page.OuterNotebookPage;
+
+  { Set the page description }
+  Page.SyncCaptionAndDescription;
+
+  BeveledLabel.Visible := (SetupMessages[msgBeveledLabel] <> '') and
+    not(CurPageID in [wpWelcome, wpFinished]);
+
+  { Set button visibility and captions }
+  UpdateCurPageButtonVisibility;
 
   BackButton.Caption := SetupMessages[msgButtonBack];
   if CurPageID = wpReady then begin
@@ -2151,6 +2254,22 @@ begin
             LogFmt('PrepareToInstall failed: %s', [PrepareToInstallFailureMessage]);
             LogFmt('Need to restart Windows? %s', [SYesNo[PrepareToInstallNeedsRestart]]);
             Break;  { stop on the page }
+          end else if RmSessionStarted then begin
+            SetCurPage(wpPreparing); { controls are already hidden by PrepareToInstall }
+            BackButton.Visible := False;
+            NextButton.Visible := False;
+            if InstallMode = imSilent then begin
+              SetActiveWindow(Application.Handle);  { ensure taskbar button is selected }
+              WizardForm.Show;
+            end;
+            try
+              WizardForm.Update;
+              RmFoundApplications := QueryRestartManager <> '';
+              if RmFoundApplications then
+                Break;  { stop on the page }
+            finally
+              UpdateCurPageButtonVisibility;
+            end;
           end;
         end;
       wpInstalling: begin
@@ -2570,7 +2689,7 @@ var
   BeforeID: Integer;
 begin
   while True do begin
-    if (CurPageID = wpPreparing) and not (PrepareToInstallNeedsRestart and not InitNoRestart) then begin
+    if (CurPageID = wpPreparing) and (PrepareToInstallFailureMessage <> '') and not (PrepareToInstallNeedsRestart and not InitNoRestart) then begin
       { Special handling needed for wpPreparing since it displays its error
         message inline on the wizard. Since the wizard isn't currently visible,
         we have to display the messsage in a message box if it won't be displayed
@@ -2582,6 +2701,11 @@ begin
       else
         SetupExitCode := ecPrepareToInstallFailed;
       Abort;
+
+      { Note: no special handling if it stops on wpPreparing because of in-use
+        files ((CurPageID = wpPreparing) and (PrepareToInstallFailureMessage = '')),
+        instead it will always choose to close applications when running silently
+        unless /NOCLOSEAPPLICATIONS was used. }
     end;
 
     BeforeID := CurPageID;

+ 2 - 2
setup.iss

@@ -5,12 +5,12 @@
 ;
 ; Setup script
 ;
-; $jrsoftware: issrc/setup.iss,v 1.183 2011/12/15 14:16:46 mlaan Exp $
+; $jrsoftware: issrc/setup.iss,v 1.183.2.1 2012/02/05 18:43:12 mlaan Exp $
 
 [Setup]
 AppName=Inno Setup
 AppId=Inno Setup 5
-AppVersion=5.4.3
+AppVersion=5.5.0
 AppPublisher=Jordan Russell
 AppPublisherURL=http://www.innosetup.com/
 AppSupportURL=http://www.innosetup.com/

+ 20 - 2
whatsnew.htm

@@ -19,7 +19,7 @@
 </head>
 <body>
 
-<!-- $jrsoftware: issrc/whatsnew.htm,v 1.741 2012/02/05 18:52:18 mlaan Exp $ -->
+<!-- $jrsoftware: issrc/whatsnew.htm,v 1.739.2.3 2012/02/05 18:43:12 mlaan Exp $ -->
 
 <div class="bluehead"><span class="head1">Inno Setup 5</span><br /><span class="head2">Revision History</span></div>
 
@@ -28,8 +28,20 @@ Portions Copyright &copy; 2000-2012 Martijn Laan. All rights reserved.<br />
 For conditions of distribution and use, see <a href="http://www.jrsoftware.org/files/is/license.txt">LICENSE.TXT</a>.
 </p>
 
-<p><a name="5.4.4"></a><span class="ver">5.4.4-dev </span><span class="date">(?)</span></p>
+<p><a name="5.5.0"></a><span class="ver">5.5.0-dev </span><span class="date">(?)</span></p>
 <ul>
+<li>On Windows Vista and newer, Setup now supports the Windows <a href="http://msdn.microsoft.com/en-us/library/windows/desktop/aa373524.aspx" target="_blank">Restart Manager</a> API to close and restart applications using files that need to be updated:
+  <ul>
+  <li>Added new [Setup] section directive: <tt>CloseApplications</tt>, which defaults to <tt>yes</tt>. If set to <tt>yes</tt> and Setup is not running silently, Setup will now pause on the <i>Preparing to Install</i> wizard page if it detects applications using files that need to be updated by the [Files] or [InstallDelete] section, showing the applications and asking the user if Setup should automatically close the applications and restart them after the installation has completed. If set to <tt>yes</tt> and Setup is running silently, Setup will always close and restart such applications, unless told not to via the command line (see below).</li>
+  <li>Added new [Setup] section directive: <tt>CloseApplicationsFilter</tt>, which defaults to <tt>*.exe,*.dll,*.chm</tt>.</li>
+  <li>Added new [Setup] section directive: <tt>RestartApplications</tt>, which defaults to <tt>yes</tt>. Note: for Setup to be able to restart an application after the installation has completed, the application needs to be using the Windows API <tt>RegisterApplicationRestart</tt> function.</li>
+  <li>Added new command line parameters supported by Setup: /NOCLOSEAPPLICATIONS and /NORESTARTAPPLICATIONS. These can be used to overide the new <tt>CloseApplications</tt> and <tt>RestartApplications</tt> directives.</li>
+  <li>Added new [Code] support function: <tt>RmSessionStarted</tt>.</li>
+  <li>TWizardForm: Added new <tt>PreparingMemo</tt> property.</li>
+  </ul>
+</li>
+<li>The <i>Preparing to Install</i> wizard page now also checks if one or more files specified in the [InstallDelete] section were queued (by some other installation) to be replaced or deleted on the next restart, making Setup stop on the page if it does. Previously it only checked files specified in the [Files] section.</li>
+<li>Setup now hides the <i>Back</i> and <i>Next</i> buttons while the <tt>PrepareToInstall</tt> [Code] event function is running.</li>
 <li>Windows 7 change:
   <ul>
   <li>Added new [Icons] section flag: <tt>preventpinning</tt>. Prevents a Start menu entry from being pinnable to Taskbar or the Start Menu on Windows 7 (or later). This also makes the entry ineligible for inclusion in the Start menu's Most Frequently Used (MFU) list. Ignored on earlier Windows versions. Contributed by <a href="https://github.com/miniak" target="_blank">Milan Burda</a> via <a href="https://github.com/jrsoftware" target="_blank">GitHub</a>.</li>
@@ -37,6 +49,12 @@ For conditions of distribution and use, see <a href="http://www.jrsoftware.org/f
 </li>
 <li>Improved the "auto-retry" feature of the [Files] section: it now automatically retries even if the <tt>restartreplace</tt> [Files] section flag is used.</li>
 <li>Added 128x128 and 256x256 sizes to the compiler and document icons, created by Motaz.</li>
+<li>Some messages have been added in this version.
+<!--  (<a href="http://cvs.jrsoftware.org/view/issrc/Files/Default.isl?r1=1.68&amp;r2=1.69">View differences in Default.isl</a>) -->
+  <ul>
+    <li><b>New messages:</b> ApplicationsFound, ApplicationsFound2, CloseApplications, DontCloseApplications, StatusClosingApplications, StatusRestartingApplications.</li>
+  </ul>
+</li>
 <li>Minor tweaks.</li>
 </ul>