在安装开始时卸载以前版本的产品会导致 Inno Setup 中的安装损坏

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

我想使用 Inno Setup 删除程序的旧可能实例。 该程序不必安装在系统的特定部分到 运行,但我想建立一个标准并通过复制文件删除就在那里的旧实例。因为有一些变化,我想从注册表中删除以前安装的条目(32 位和 64 位安装是可能的,所以如果你想要两个安装程序,同一个程序可能存在两个条目),我在假设中写了这个安装将在 PrepareToInstall 部分完成后开始 运行卸载卸载程序并删除文件。

{ 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;

如果我用它来清理旧安装,安装有时只包含 15 个应该安装的文件中的 8 个(在 [Files] 中定义,我可以看到它们被添加到编译器中输出和通常安装)。所以我唯一的猜测是为什么我得到的文件少于 15 个是在安装过程中删除了预安装期间应该删除的文件。因此,如果我没记错的话,所需的清理工作必须删除安装文件。但是我找不到为什么会这样。

我认为安装本身会将内容放在准备部分之后的目标文件夹中。那么我错了吗/我该如何修改代码以使安装有效?我知道我可以在安装程序结束之前通过检查来测试它,但我想这不是一个很好的做法...

我要卸载的先前安装如下:

[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"

主要卸载程序进程仅在临时文件夹中创建其自身的副本,运行是实际卸载的子进程。

引用 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).

因此,尽管有 ewWaitUntilTerminated,但代码中的 Exec 实际上并未等待卸载完成。所以可能会出现卸载程序和安装程序同时运行的情况。这自然会导致安装损坏。

您必须等待子卸载程序完成才能解决您的问题。快速而肮脏的解决方案是查找名称包含 _iu 的任何进程。参见 Inno Setup Pascal Script to search for running process