'Uninstalling previous version of product at the beginning of the installation results in corrupted install in Inno Setup

I wanted to use Inno Setup to remove old possible instances of a program. The program does not have to be installed in specific part of the system to run, but I wanted to make up a standard and remove old instances that were just there by copying the files. Since there have been a few changes and I wanted to remove entries from previous installations from the registry (32 and 64 bit installs were possible, so two entries may exist for the same program if you want both installers), I wrote this in the assumption that the installation would start after the PrepareToInstall portion was done running the uninstaller and removing the files.

{ This is the part that is executed after you started the install }
function PrepareToInstall(var NeedsRestart: Boolean): String;
var
  ResultCode: integer;
  UninstallString: String;
  AppId: String;
begin
  AppId := '{#SetupSetting("AppId")}';
  AppId := Copy(AppId, 2,  Length(AppId) - 1);
  if (IsWin64 And RegQueryStringValue(HKEY_LOCAL_MACHINE_64, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\'+AppId+'_is1', 'UninstallString', UninstallString)) then begin
    Exec(Copy(UninstallString, 2,  Length(UninstallString) - 2), '/VERYSILENT', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)
    if RegKeyExists(HKEY_LOCAL_MACHINE_64, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\'+AppId+'_is1') then begin
      RegDeleteKeyIncludingSubkeys(HKEY_LOCAL_MACHINE_64, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\'+AppId+'_is1')
    end;
  end;
  if (RegQueryStringValue(HKEY_LOCAL_MACHINE_32, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\'+AppId+'_is1', 'UninstallString', UninstallString)) then begin
    Exec(Copy(UninstallString, 2,  Length(UninstallString) - 2), '/VERYSILENT', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)
    if RegKeyExists(HKEY_LOCAL_MACHINE_32, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\'+AppId+'_is1') then begin
      RegDeleteKeyIncludingSubkeys(HKEY_LOCAL_MACHINE_32, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\'+AppId+'_is1')
    end;
  end;

  If (DirIsSysLink(ExpandConstant('{app}')) = True) Then begin
    RenameFile(ExpandConstant('{app}'), ExpandConstant('{app}_link'));
    CreateDir(ExpandConstant('{app}'));
    DirectoryCopy(ExpandConstant('{app}_link'), ExpandConstant('{app}'));
    DelTree(ExpandConstant('{app}_link'), True, True, True);
  End;

  If (IsWin64) Then Begin
    If (DirExists('C:\Program Files (x86)\TargetProgram')) then begin 
      CleanupFolder('C:\Program Files (x86)\TargetProgram', 'X86') 
      If (DirIsSysLink('C:\Program Files (x86)\TargetProgram') = False) Then begin
        DelTree('C:\Program Files (x86)\TargetProgram', True, True, True)
      end
    end;
  End;
  
  If (DirExists('C:\Program Files\TargetProgram')) then begin
    CleanupFolder('C:\Program Files\TargetProgram', 'X64')
  end;
end;

{ Tries to remove existing installations and additional files in the folders that don't belong here before install starts }
function CleanupFolder(Folder: String; Num: String): Boolean;
var
  BolTmpVal: Boolean;
  FindRec: TFindRec;
  FileList: String;
begin
  BolCopyFailed := False;
  If (DirExists(Folder)) then begin
    FileList := '|a.file|b.dll|...|'
    if (FindFirst(Folder+'\*', FindRec)) then begin
      try
        repeat
          if (Pos('|'+LowerCase(FindRec.Name)+'|', FileList) <> 0) then begin//this is a file known to be installed later
            DeleteFile(Folder+'\'+FindRec.Name);
          end Else if ((Pos('unins0', FindRec.Name) = 1) And ((Pos('.exe', FindRec.Name) = 9) OR (Pos('.dat', FindRec.Name) = 9))) Then begin
            {Deleting the uninstall files seems to remove the actual installer itself...}
            //DeleteFile(Folder+'\'+FindRec.Name);
          End Else If ((FindRec.Name = '.') OR (FindRec.Name = '..')  OR (LowerCase(FindRec.Name) = 'backup')) Then Begin
            // do nothing with main directories or backup folder
          end Else begin
            //... some copy and paste backup of possible user-files not involved in the problem
          end;
        until not FindNext(FindRec);
      finally
        FindClose(FindRec);
      end;
    end;
  end;
end;

If I use this to clear up the old installs, the installation sometimes consists only of 8 of 15 files that should be installed (are defined in the [Files] and I can see them being added in the compilers output and usually are installed). So my only guess why I get less than 15 files is that the files that should be removed during pre-installation are removed during the install. So the desired cleanup must remove the install files if I am not mistaken. But I cannot find a reason why this is the case.

I thought that the installation itself puts the content in the target-folder after the preparation part. So was I wrong / how do I modify the code so the install is valid? I know I could test this by checking before the installer ends, but I guess it would not be a god practice...

The previous installation that I'm uninstalling was like:

[Files]
Source: "Input\*"; DestDir: "{app}"; \
    Flags: ignoreversion recursesubdirs createallsubdirs 

[UninstallDelete]
Type: dirifempty; Name: "{app}" Type: dirifempty; Name: "C:\Program Files\A"

[UninstallRun]
Filename: "{sys}\taskkill.exe"; Parameters: "/F /IM a.exe"; Flags:runhidden ; \
    StatusMsg: "Closing process"


Solution 1:[1]

The main uninstaller process only creates a copy of itself in a temporary folder and runs a child process for the actual uninstallation.

Quoting Inno Setup documentation:

Note that at the moment you get an exit code back from the uninstaller, some code related to uninstallation might still be running. Because Windows doesn't allow programs to delete their own EXEs, the uninstaller creates and spawns a copy of itself in the TEMP directory. This "clone" performs the actual uninstallation, and at the end, terminates the original uninstaller EXE (at which point you get an exit code back), deletes it, then displays the "uninstall complete" message box (if it hasn't been suppressed with /SILENT or /VERYSILENT).

So the Exec in your code, despite the ewWaitUntilTerminated, does not actually wait for the uninstallation to complete. So it may happen that the uninstaller and the installer run at the same time. This naturally results in a corrupted installation.

You would have to wait for the child uninstaller process to complete to overcome your problem. Quick and dirty solution would be to look for any process with name containing _iu. See Inno Setup Pascal Script to search for running process.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1