Inno Setup:在升级时禁用已安装的组件

问题描述:

实际上,我们有一个安装程序,在安装过程中有很多组件可供选择,我们想在客户重新安装我们的软件时禁用已安装的组件(或使其变为灰色/固定).

We have actually a setup who has many components choice during install and we want to disable component already installed (or make it grey / fixed) when customer reinstall our software.

例如,在首次安装期间,我们有3个组件,如下所示:

For example during first install we have 3 component like this:

Component
* International
* French
* German

在首次安装期间,可以选择所有组件.

During first install, all components can be selected.

认识到选择了德语"包,当用户重新安装产品(使用相同的安装程序)以获取新的语言包时,我们希望具有这样的内容:

Recognize that chooses the "German" pack, when user make a reinstall of product (with the same installer) to get a new language pack, we want to have something like that:

Component
* International
* French
* German (already installed)

无法选择德语"的地方...

Where "German" can't be selected...

是否可以使用Inno Setup来做到这一点?

Is there a way to do that with Inno Setup?

我发现 InnoSetup:升级时禁用组件页面,但该示例禁用整个组件页面,我想保留它.

I found InnoSetup: Disable components page on upgrade, but the example disables whole component page, and I want to keep it.

编辑:

这适用于2安装.但是,如果我进行第三次安装(用于安装最后一个组件),则安装程序将采用第二次安装的组件,而不是第一次安装.

That's working for 2 install. But if I do a third install (for installing the last component), the setup take the component of second install, but not the first.

我认为这是因为安装程序会覆盖首次安装的注册表项,因此看不到早期安装的组件.

I think this is because the setup overwrites the registry key of the first installation and therefore does not see the components installed in the early ...

这是第一次安装时的日志和注册表项(选择英语):

Here is the log and registry key on first install (english selected) :

2016-03-22 13:57:56.913   New install de Bim

这是第二次安装时的日志和注册表项(英语为灰色,无法选择,选择了法语):

Here is the log and registry key on second install (english is grey and can't be selected, french is selected) :

Created temporary directory: C:\Users\mea\AppData\Local\Temp\is-QV8N6.tmp
2016-03-22 14:00:54.354   Upgrading, previously installed components are [languagepacks,languagepacks\english,canecorevit,canecorevit\2016]
2016-03-22 14:00:54.354   Found installed component [languagepacks]
2016-03-22 14:00:54.354   Disabling installed component [languagepacks] as [Content] at 0
2016-03-22 14:00:54.355   Found installed component [languagepacks\english]
2016-03-22 14:00:54.355   Disabling installed component [languagepacks\english] as [Pack International] at 1
2016-03-22 14:00:54.355   Found installed component [canecorevit]
2016-03-22 14:00:54.356   Found installed component [canecorevit\2016]
2016-03-22 14:00:54.356   Disabling installed component [canecorevit\2016] as [REVIT 2016] at 5
2016-03-22 14:02:48.691   Message box (Yes/No):
                          L'assistant d'installation a détecté que les composants suivants sont déjà installés sur votre système :

                          Pack International

                          Désélectionner ces composants ne les désinstallera pas pour autant.

                          Voulez-vous continuer malgré tout ?
2016-03-22 14:02:49.808   User chose Yes.
2016-03-22 14:02:56.000   Starting the installation process.

这是第三次安装时的日志和注册表项

And here is the log and registry key on third install

Created temporary directory: C:\Users\mea\AppData\Local\Temp\is-J7G5A.tmp
2016-03-22 14:07:41.582   Upgrading, previously installed components are [languagepacks,languagepacks\french,canecorevit,canecorevit\2016]
2016-03-22 14:07:41.583   Found installed component [languagepacks]
2016-03-22 14:07:41.583   Disabling installed component [languagepacks] as [Content] at 0
2016-03-22 14:07:41.583   Found installed component [languagepacks\french]
2016-03-22 14:07:41.584   Disabling installed component [languagepacks\french] as [Pack France] at 2
2016-03-22 14:07:41.584   Found installed component [canecorevit]
2016-03-22 14:07:41.584   Found installed component [canecorevit\2016]
2016-03-22 14:07:41.585   Disabling installed component [canecorevit\2016] as [REVIT 2016] at 5
2016-03-22 14:08:14.122   Message box (Yes/No):
                          L'assistant d'installation a détecté que les composants suivants sont déjà installés sur votre système :

                          Pack France

                          Désélectionner ces composants ne les désinstallera pas pour autant.

                          Voulez-vous continuer malgré tout ?
2016-03-22 14:08:15.132   User chose Yes.

因此,显然,在Software\Microsoft\Windows\CurrentVersion\Uninstall\AppId_is1\Inno Setup: Selected Components上的每次安装后都进行了inno-setup写入,这就是为什么它仅适用于第二次安装,但是不记得在第三次安装中第一次选择组件的原因.

So apparently, inno-setup write after each install on Software\Microsoft\Windows\CurrentVersion\Uninstall\AppId_is1\Inno Setup: Selected Components and that's why it only work for the second install, but doesn't remember that component are choose in first install during a third one.

您可以从注册表值中提取已安装组件的列表

You can extract list of installed components from registry value

Software\Microsoft\Windows\CurrentVersion\Uninstall\AppId_is1\Inno Setup: Selected Components

问题在于列表使用组件名称,并且没有办法将名称映射到复选框,因为仅以编程方式公开了描述(另请参见

The problem is that the list uses component names and there's no way to map the names to the checkboxes as only descriptions are exposed programmatically (see also How to allow to only install specific components in InnoSetup?

幸运的是,先前安装的组件将由Inno Setup本身进行检查.因此,最简单的解决方案是禁用所有最初检查的组件.但是,如果您在默认情况下选中的升级中添加了新组件,则将无法使用.

Luckily the previously installed components will be checked by Inno Setup itself. So the easiest solution is to disable all initially checked components. This will break though if you add a new component in the upgrade that is checked by default.

另一种方法是使用 WizardSelectedComponents支持函数,该函数可以返回所选组件名称和说明的列表.因此,它可用于将描述映射到名称并返回.但仅适用于选定的组件.尽管这对于您的特定目的应该足够了.参见我的代码中的SelectedComponentDescriptionToName.

Another way is to use WizardSelectedComponents support function that can return both list of selected component names and descriptions. So it can be used to map descriptions to names and back. But only for selected components. Though that should be enough for your specific purpose. See SelectedComponentDescriptionToName in my code.

一个限制是描述必须是唯一的,否则映射将失败.因此,例如,您不能具有不同父组件的多个"Deutsch"子组件(这将需要更复杂的代码,并且如果父组件带有checkablealone标志,则根本无法使用)

A limitation is that the description must be unique, otherwise the mapping fails. So you cannot for example have multiple "Deutsch" subcomponents of different parent components (that would require more complicated code and won't work at all if the parent component has checkablealone flag)

#define AppId "myapp"
#define InnoSetupReg \
  "Software\Microsoft\Windows\CurrentVersion\Uninstall\" + AppId + "_is1"
#define InnoSetupSelectedComponentsReg "Inno Setup: Selected Components"

[Setup]
AppId={#AppId}
...

[Code]

function ExtractToken(var S: string): string;
var
  P: Integer;
begin
  P := Pos(',', S);
  if P > 0 then
  begin
    Result := Copy(S, 1, P - 1);
    Delete(S, 1, P);
  end
    else
  begin
    Result := S;
    S := '';
  end;
end;

function SelectedComponentDescriptionToName(Description: string): string;
var
  Descriptions: string;
  Names: string;
begin
  Descriptions := WizardSelectedComponents(True);
  Names := WizardSelectedComponents(False);

  while Descriptions <> '' do
  begin
    Result := ExtractToken(Names);
    if RemoveQuotes(ExtractToken(Descriptions)) = Description then
    begin
      Exit;
    end;
  end;

  Result := '';
end;

procedure InitializeWizard();
var
  Upgrade: Boolean;
  SelectedComponents: string;
  Component: string;
  Name: string;
  I: Integer;
begin
  Upgrade :=
    RegQueryStringValue(HKCU, ExpandConstant('{#InnoSetupReg}'), 
      '{#InnoSetupSelectedComponentsReg}', SelectedComponents) or
    RegQueryStringValue(HKLM, ExpandConstant('{#InnoSetupReg}'),
      '{#InnoSetupSelectedComponentsReg}', SelectedComponents);

  if not Upgrade then
  begin
    Log('New install');
  end
    else
  begin
    Log(Format('Upgrading, previously installed components are [%s]', [
      SelectedComponents]));

    while SelectedComponents <> '' do
    begin
      Component := ExtractToken(SelectedComponents);

      Log(Format('Found installed component [%s]', [Component]));

      for I := 0 to WizardForm.ComponentsList.Items.Count - 1 do
      begin
        if WizardForm.ComponentsList.State[I] = cbChecked then
        begin
          Name :=
            SelectedComponentDescriptionToName(
              WizardForm.ComponentsList.ItemCaption[I]);

          if Name = Component then
          begin
            Log(Format('Disabling installed component [%s] as [%s] at %d', [
              Name, WizardForm.ComponentsList.ItemCaption[I], I]));
            WizardForm.ComponentsList.ItemEnabled[I] := False;
          end;
        end;
      end;
    end;
  end;
end;