Inno Setup:如何自动卸载先前安装的版本?

98

我正在使用Inno Setup创建安装程序。

我希望安装程序能够自动卸载先前安装的版本,而不是覆盖它。我该如何做到这一点?


2
请注意,正如mlaan所说,在使用基于Inno的安装程序时,通常不需要这样做,除非您正在从非Inno版本升级。 - Deanna
7
Deanna说:这取决于具体情况。对于一些具有自动插件系统的程序来说,它们可以读取文件夹中的任何内容,卸载旧版本并删除旧文件是安装新版本的必要步骤,直接运行卸载程序通常是最干净的方法。 - Nyerguds
1
@Nyerguds,但是InnoSetup通过具有在安装开始之前删除某些文件/文件夹的选项(“InstallDelete”标志)来满足这一点,因此您仍然不需要先卸载旧版本。 - NickG
3
@NickG: 取决于具体情况。那当然是理想情况,也是首选情况,但事实上,存在许多非理想情况。其中一个例子是在许多可能的目标版本上注册的dll文件。 - Nyerguds
有关正确解析 UninstallString 的方法,请参见 在 Inno Setup 中执行 UninstallString - Martin Prikryl
13个回答

120

我使用了以下代码。我不确定这是否是最简单的方法,但它能够正常工作。

这里使用了{#emit SetupSetting("AppId")},它依赖于Inno Setup预处理器。如果您不使用预处理器,请直接复制并粘贴您的应用程序ID。

[Code]

{ ///////////////////////////////////////////////////////////////////// }
function GetUninstallString(): String;
var
  sUnInstPath: String;
  sUnInstallString: String;
begin
  sUnInstPath := ExpandConstant('Software\Microsoft\Windows\CurrentVersion\Uninstall\{#emit SetupSetting("AppId")}_is1');
  sUnInstallString := '';
  if not RegQueryStringValue(HKLM, sUnInstPath, 'UninstallString', sUnInstallString) then
    RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString);
  Result := sUnInstallString;
end;


{ ///////////////////////////////////////////////////////////////////// }
function IsUpgrade(): Boolean;
begin
  Result := (GetUninstallString() <> '');
end;


{ ///////////////////////////////////////////////////////////////////// }
function UnInstallOldVersion(): Integer;
var
  sUnInstallString: String;
  iResultCode: Integer;
begin
{ Return Values: }
{ 1 - uninstall string is empty }
{ 2 - error executing the UnInstallString }
{ 3 - successfully executed the UnInstallString }

  { default return value }
  Result := 0;

  { get the uninstall string of the old app }
  sUnInstallString := GetUninstallString();
  if sUnInstallString <> '' then begin
    sUnInstallString := RemoveQuotes(sUnInstallString);
    if Exec(sUnInstallString, '/SILENT /NORESTART /SUPPRESSMSGBOXES','', SW_HIDE, ewWaitUntilTerminated, iResultCode) then
      Result := 3
    else
      Result := 2;
  end else
    Result := 1;
end;

{ ///////////////////////////////////////////////////////////////////// }
procedure CurStepChanged(CurStep: TSetupStep);
begin
  if (CurStep=ssInstall) then
  begin
    if (IsUpgrade()) then
    begin
      UnInstallOldVersion();
    end;
  end;
end;

备选方案

此外,还可以参考这篇博客文章 "Inno Setup脚本样例:版本比较",该文章更进一步地读取先前安装版本的版本号,并将其与当前安装包的版本号进行比较。


3
感谢您参考我的博客文章。该文章的完整示例在此处可用,http://code.google.com/p/lextudio/source/browse/trunk/trunk/setup/CBC2Exe.iss。 - Lex Li
2
很棒的解决方案,运行良好。然而,在安装过程中会打开一个窗口显示“正在卸载[软件名称]”。有没有可能防止这个窗口弹出?因为我的软件安装非常快,安装窗口在卸载窗口之前关闭,看起来很奇怪... - André Santaló
5
请使用/VERYSILENT代替/SILENT。 - Gautam Jain
2
ewWaitUntilTerminated不起作用。卸载程序将自身复制到临时文件夹并从临时文件夹重新启动自身。 - Максим Румянцев
1
@МаксимРумянцев 是的,ewWaitUntilTerminated本身并不能解决问题,这可能会导致安装文件损坏。请参见在Inno Setup中在安装开始时卸载先前版本的产品会导致安装文件损坏 - Martin Prikryl
显示剩余3条评论

30

通过注册表,你应该能够读取卸载的字符串,给定AppId(即您在[Setup]-section中使用的值)。它可以在Software\Microsoft\Windows\CurrentVersion\Uninstall\{AppId}\下找到(可能是HKLMHKCU,所以最好都检查一下),其中{AppId}应替换为实际使用的值。查找UninstallStringQuietUninstallString值,并使用Exec函数从InitializeSetup()事件函数运行它。


甚至不要考虑从InitializeSetup执行此操作。 PrepareToInstall是正确的位置。(但干脆不做这个操作可能是最好的解决方案。) - Miral
使用 AppId,您甚至可以针对多个程序(例如客户端和数据库)进行一次卸载:AppId Inno Setup Help - Bojan Hrnkas
请注意,仅使用 Exec 运行卸载程序将不会等待其完成,然后安装程序继续(即使使用了 ewWaitUntilTerminated),这可能导致损坏的安装。请参见 在 Inno Setup 中在安装开始时卸载产品的先前版本会导致损坏的安装 - Martin Prikryl

10
如果您只是想“删除旧图标”(因为您的图标已更改/更新),则可以使用以下方法:
; attempt to remove previous versions' icons
[InstallDelete]
Type: filesandordirs; Name: {group}\*;

这段代码会在安装的开始时运行,主要是删除旧图标,一旦此过程完成后,新的图标将会被重新安装。

我总是在每次安装时这样做,以防万一图标出现了更改(反正所有的都会被重新安装)。


如果您有图标更新,只需让它们覆盖即可,无需删除。如果您想要删除它们,可以使用此选项。这是正确的方法。无论如何,您与之交谈的那个人(mlaan,Martijn Laan)是Inno Setup的作者,我认为他知道自己在说什么 :-) - TLama
1
是的,当你想要重命名或移动一个图标时,你就需要这个技巧。例如,如果v5有一个名为“run”的图标,而v6已将其重命名为“run basic”,如果用户先安装v5再安装v6,他们将得到2个图标,而实际上你只想要1个(“run basic”)。因此,这个技巧最终是必要的(@mlaan +1,因为改变innosetup的默认行为是删除旧图标而不需要这个...) - rogerdpack

7
在使用Inno Setup时,除非之前的版本是由其他安装程序安装的,否则没有卸载之前版本的必要。否则,升级会自动处理。

7
我们的程序结构有所变更,因此需要卸载旧版本。 - Quan Mai
12
可以在脚本中添加条目来处理更新期间的结构更改,这不会影响它。 - mlaan
1
@mlaan 那些条目是什么? - mythofechelon
1
你可以简单地使用 [InstallDelete] 部分来删除旧的文件/目录。新的文件将在安装过程中放置在正确的位置。 - daiscog
4
如果你升级第三方库(例如DevExpress),该库的DLL名称中包含版本号(如之前安装的15.1和现在的15.2),那么你需要删除旧版本。在我看来,这是一个很好的理由。 - Thomas Weller
显示剩余4条评论

4
以下是基于 Craig McQueen 的答案简化版本:

以下是基于 Craig McQueen 的答案 的简化版:

const
    UninstallRegisterPath = 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + '{#emit SetupSetting("AppName")}' + '_is1';

function GetUninstallerPath(): String;
begin
    result := '';
    if (not RegQueryStringValue(HKLM, UninstallRegisterPath, 'UninstallString', result)) then
        RegQueryStringValue(HKCU, UninstallRegisterPath, 'UninstallString', result);
end;

procedure UninstallOldVersion();
var
    UninstallerPath: String;
    ResultCode: Integer;
begin
    UninstallerPath := GetUninstallerPath();
    if (UninstallerPath <> '') then begin
        Exec(UninstallerPath, '/VERYSILENT /NORESTART /SUPPRESSMSGBOXES', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
    end;
end;

procedure CurStepChanged(CurStep: TSetupStep);
begin
    if (CurStep = ssInstall) then
    begin
        UninstallOldVersion();
    end;
end;

注意:在我的情况下,我使用AppName而不是AppId

2
Craig McQueen提供的答案是完全可行的。不过,我想补充以下评论:
  • {#emit SetupSetting("AppId")}代码对我无效,所以我只添加了我的App ID。
  • 我不想执行卸载程序,因为我有一个存储在AppData/文件夹中的INI配置文件,该文件在卸载程序中被删除,而我不希望在安装新版本时将其删除。因此,我稍微修改了Craig McQueen提供的代码,以删除安装程序所在的目录。

因此,关于Craig McQueen的代码,更改如下:

  • 检索InstallLocation键而不是UninstallString键。
  • 使用DelTree函数而不是Exec(sUnInstallString, ...)

1

如果您在CurStepChanged()中使用上述GetUninstallString()来强制卸载并遇到了磁盘缓存问题,请参见下面的相关解决方案,它实际上会等待一段时间以使卸载程序exe文件被删除!

Inno Setup中的磁盘缓存问题?


1
对于那些有兴趣的人,我为Inno Setup 6及更高版本编写了一个DLL,提供了一种简单的机制来支持自动卸载。该DLL提供了一种检测安装包是否已经安装(通过AppId)并根据已安装的版本决定是否要自动卸载它的方法(例如,如果用户降级,则可能希望自动卸载)。

https://github.com/Bill-Stewart/UninsIS


1
干得好!你的 DLL 是唯一一个对我有用的东西。 - Kostas Markakis
1
@KostasMarkakis 太好了,谢谢你的反馈! - undefined

0

我修改了@Crain Mc-Queen的代码,我认为这个代码更好,因为不需要在不同的项目中进行修改:

[Code]
function GetNumber(var temp: String): Integer;
var
  part: String;
  pos1: Integer;
begin
  if Length(temp) = 0 then
  begin
    Result := -1;
    Exit;
  end;
    pos1 := Pos('.', temp);
    if (pos1 = 0) then
    begin
      Result := StrToInt(temp);
    temp := '';
    end
    else
    begin
    part := Copy(temp, 1, pos1 - 1);
      temp := Copy(temp, pos1 + 1, Length(temp));
      Result := StrToInt(part);
    end;
end;

function CompareInner(var temp1, temp2: String): Integer;
var
  num1, num2: Integer;
begin
    num1 := GetNumber(temp1);
  num2 := GetNumber(temp2);
  if (num1 = -1) or (num2 = -1) then
  begin
    Result := 0;
    Exit;
  end;
      if (num1 > num2) then
      begin
        Result := 1;
      end
      else if (num1 < num2) then
      begin
        Result := -1;
      end
      else
      begin
        Result := CompareInner(temp1, temp2);
      end;
end;

function CompareVersion(str1, str2: String): Integer;
var
  temp1, temp2: String;
begin
    temp1 := str1;
    temp2 := str2;
    Result := CompareInner(temp1, temp2);
end;

function InitializeSetup(): Boolean;
var
  oldVersion: String;
  uninstaller: String;
  ErrorCode: Integer;
  vCurID      :String;
  vCurAppName :String;
begin
  vCurID:= '{#SetupSetting("AppId")}';
  vCurAppName:= '{#SetupSetting("AppName")}';
  //remove first "{" of ID
  vCurID:= Copy(vCurID, 2, Length(vCurID) - 1);
  //
  if RegKeyExists(HKEY_LOCAL_MACHINE,
    'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + vCurID + '_is1') then
  begin
    RegQueryStringValue(HKEY_LOCAL_MACHINE,
      'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + vCurID + '_is1',
      'DisplayVersion', oldVersion);
    if (CompareVersion(oldVersion, '{#SetupSetting("AppVersion")}') < 0) then      
    begin
      if MsgBox('Version ' + oldVersion + ' of ' + vCurAppName + ' is already installed. Continue to use this old version?',
        mbConfirmation, MB_YESNO) = IDYES then
      begin
        Result := False;
      end
      else
      begin
          RegQueryStringValue(HKEY_LOCAL_MACHINE,
            'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + vCurID + '_is1',
            'UninstallString', uninstaller);
          ShellExec('runas', uninstaller, '/SILENT', '', SW_HIDE, ewWaitUntilTerminated, ErrorCode);
          Result := True;
      end;
    end
    else
    begin
      MsgBox('Version ' + oldVersion + ' of ' + vCurAppName + ' is already installed. This installer will exit.',
        mbInformation, MB_OK);
      Result := False;
    end;
  end
  else
  begin
    Result := True;
  end;
end;

0

你可以在 [code] 部分执行卸载程序。你需要找出如何获取现有卸载程序的路径。为了简单起见,当我安装我的应用程序时,我添加一个注册表字符串值,指向包含卸载程序的文件夹,并在 InitializeWizard 回调中执行卸载程序。

请记住,Inno Setup 卸载程序的名称都是以 uninsnnn.exe 的形式命名的,你需要在代码中考虑到这一点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接