Inno Setup: Como desinstalar automaticamente a versão anterior instalada?

90

Estou usando o Inno Setup para criar um instalador.

Quero que o instalador desinstale automaticamente a versão anterior instalada, em vez de substituí-la. Como eu posso fazer isso?

Quan Mai
fonte
2
Observe que, como disse mlaan , normalmente não há necessidade de fazer isso com uma configuração baseada no Inno, a menos que você esteja atualizando de uma versão não Inno.
Deanna
7
Deanna: depende do caso. Para alguns programas com sistemas de plugins automáticos, que lêem qualquer coisa em uma pasta, a remoção de arquivos antigos é uma necessidade absoluta ao instalar uma nova versão, e simplesmente executar a desinstalação é geralmente a maneira mais limpa de fazer isso.
Nyerguds,
1
@Nyerguds Mas o InnoSetup atende a isso tendo a opção de deletar certos arquivos / pastas antes do início da instalação (sinalizador "InstallDelete") para que você ainda não precise desinstalar a versão antiga primeiro.
NickG
3
@NickG: Mais uma vez, depende do caso. Essa seria a situação ideal, sim, e de longe a preferida, mas na realidade, existem muitas situações não ideais. Um exemplo são os arquivos dll registrados, em muitas versões de destino possíveis.
Nyerguds

Respostas:

28

Você deve ser capaz de ler a string de desinstalação do registro, dado o AppId (ou seja, o valor usado AppIDna [Setup]seção-). Ele pode ser encontrado em Software\Microsoft\Windows\CurrentVersion\Uninstall\{AppId}\(pode ser HKLMou HKCU, então é melhor verificar ambos), onde {AppId}deve ser substituído pelo valor real que você usou. Procure os valores UninstallStringou QuietUninstallStringe use a Execfunção para executá-los a partir de sua InitializeSetup()função de evento.

Atualização: solução alternativa não funcional removida usando uma [Run]entrada -section com {uninstallexe}- graças a todos os comentadores que apontaram isso!

Oliver Giesen
fonte
+1, mas definitivamente use o script para ler o nome do desinstalador antigo, caso contrário, ele não funcionará se um caminho diferente for inserido pelo usuário.
mghie
3
Não acho que a [Run]solução da seção funcione, porque é executado muito tarde no processo de instalação. Do manual do Inno Setup: A seção [Run] é opcional e especifica qualquer número de programas a serem executados após o programa ter sido instalado com sucesso, mas antes que o programa de Instalação exiba a caixa de diálogo final.
Craig McQueen de
Por favor, edite esta postagem e remova a parte [Executar], ela não funciona. A segunda parte funciona. Obrigado :-)
Saulius Žemaitaitis
11
Um aviso: para um aplicativo de 32 bits no Windows de 64 bits, o caminho pode ser `Software \ Wow6432Node \ Microsoft \ Windows \ CurrentVersion \ Uninstall \ {AppId}`
Adrian Cox
4
@Adrian: Embora isso possa ser verdade na camada física, acho que as chamadas WinAPI usadas pelo Inno já cuidarão disso - pelo menos se o próprio setup.exe for um processo de 32 bits.
Oliver Giesen
114

Eu usei o seguinte. Não tenho certeza se é a maneira mais simples de fazer isso, mas funciona.

Este usa o {#emit SetupSetting("AppId")}que depende do pré-processador Inno Setup. Se você não usar isso, recorte e cole o ID do aplicativo diretamente.

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

Alternativas

Veja também esta postagem do blog "Amostra de script do Inno Setup para comparação de versões" que vai um passo adiante e lê o número da versão de qualquer versão instalada anteriormente e compara esse número de versão com o do pacote de instalação atual.

Craig McQueen
fonte
3
obrigado por se referir ao meu post no blog. O exemplo completo dessa postagem está disponível aqui, code.google.com/p/lextudio/source/browse/trunk/trunk/setup/…
Lex Li
A única coisa que falta aqui é o suporte para uma entrada de desinstalação em HKCU em vez de HKLM.
Oliver Giesen
1
Posso sugerir adicionar a capacidade de desinstalar se algum usuário instalou o aplicativo, especialmente se o usuário atual for um Administrador? ... UserSIDs: TArrayOfString; I: Integer; ... if not RegQueryStringValue(HKCU, sUnInstPath, 'UninstallString', sUnInstallString) then if isAdminLoggedOn() and RegGetSubkeyNames( HKEY_USERS, '', UserSIDs ) then for I := 0 to GetArrayLength( UserSIDs ) - 1 do begin if RegQueryStringValue( HKEY_USERS, UserSIDs[I] + '\' + sUnInstPath, 'UninstallString', sUnInstallString ) then break; end;
Terrance
2
Ótima solução, funciona bem. No entanto, ele abre uma janela durante a instalação mostrando "Desinstalando [nome do software]". É possível evitar que esta janela seja exibida? Porque a instalação do meu software é tão rápida que a janela de instalação fecha antes da janela de desinstalação e parece estranho ...
André Santaló
4
@ AndréSantaló Use / VERYSILENT em vez de / SILENT
Gautam Jain
7

Se você "deseja apenas remover os ícones antigos" (porque os seus foram alterados / atualizados), você pode usar isto:

; attempt to remove previous versions' icons
[InstallDelete]
Type: filesandordirs; Name: {group}\*;

Isso é executado "no início da instalação", então basicamente remove os ícones antigos, e os novos ainda serão instalados lá depois que isso for concluído.

Eu apenas faço isso com cada instalação "caso algo tenha mudado" em relação ao ícone (tudo é reinstalado de qualquer maneira).

rogerdpack
fonte
Se você tiver atualizações para seus ícones, deixe-os sobrescrever. Não há necessidade de removê-los. Bem, se você deseja removê-los, você pode usar esta opção. Essa é a maneira correta. Enfim, o cara com quem você estava falando (mlaan, Martijn Laan) é o autor do Inno Setup e acho que ele sabe do que está falando :-)
TLama
1
Sim, é quando você deseja renomear ou mover um ícone que você precisa disso. Por exemplo, se a v5 tem um chamado "executar" e a v6 o renomeou para "executar básico", se um usuário instalar a v5 e depois a v6, eles acabarão com 2 ícones, quando na verdade você queria 1 ("executar básico"). Portanto, este truque acaba sendo necessário (@mlaan +1 para alterar o comportamento padrão do innosetup para remover ícones antigos e não precisar disso ...)
rogerdpack 01 de
6

Ao usar o Inno Setup, não há razão para desinstalar uma versão anterior, a menos que essa versão tenha sido instalada por um programa de instalação diferente. Caso contrário, as atualizações são tratadas automaticamente.

Mlaan
fonte
7
Nosso programa sofreu uma mudança na estrutura, portanto, a versão antiga precisa ser desinstalada.
Quan Mai
11
Não, não precisa, você pode adicionar entradas ao seu script para lidar com a alteração da estrutura durante uma atualização.
mlaan de
@mlaan E quais entradas seriam essas?
mythofechelon
1
Você pode simplesmente usar uma [InstallDelete]seção para remover arquivos / diretórios antigos. Os novos arquivos serão colocados nos locais corretos durante a instalação.
daiscog
2
Se você atualizar uma biblioteca de terceiros como DevExpress, que tem números de versão em nomes de DLL (como 15.1 instalado antes e 15.2 agora), então você deseja remover a versão antiga. IMHO esse é um bom motivo.
Thomas Weller
2

A resposta fornecida por Craig McQueen é totalmente viável. Embora, eu adicionaria esses comentários:

  • O {#emit SetupSetting("AppId")}código não funciona para mim, então apenas adiciono meu ID do aplicativo.
  • Eu não queria executar meu programa de desinstalação porque tenho um arquivo de configuração INI armazenado na pasta AppData / que é removido pelo desinstalador e não quero que seja apagado ao instalar uma nova versão. Então, modifiquei um pouco o código fornecido por Craig McQueen para remover o diretório onde está instalado o programa, após recuperar seu caminho.

Portanto, em relação ao código de Craig McQueen, as mudanças são:

  • Recupere a InstallLocationchave em vez da UninstallStringchave.
  • Use a DelTreefunção em vez doExec(sUnInstallString, ...)

fonte
1

Para qualquer pessoa que use o GetUninstallString()sugerido acima para forçar uma desinstalação interna CurStepChanged()e tenha problemas de cache de disco, veja abaixo uma solução relacionada que realmente espera um pouco após a desinstalação para que o exe desinstalador seja excluído!

Problema de cache de disco com configuração inno?

danoso
fonte
0

Você pode executar um desinstalador na seção [código]. Você tem que descobrir como obter o caminho para o desinstalador existente. Para simplificar, quando instalo meus aplicativos, adiciono um valor de string de registro que aponta para a pasta que contém o desinstalador e apenas executo o desinstalador no retorno de chamada InitializeWizard.

Lembre-se de que os nomes dos desinstaladores de instalação do Inno estão todos no formato uninsnnn.exe, você precisa levar isso em consideração no seu código.

Jim In Texas
fonte
0

Eu editei o código do @Crain Mc-Queen, acho que este código é melhor porque não precisa ser modificado em outro projeto:

[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;
MohsenB
fonte
-1

Eu devo estar esquecendo alguma coisa. Os novos arquivos são copiados para o diretório de destino antes de ocorrer a remoção da instalação antiga. Em seguida, vem o desinstalador os exclui e remove o diretório.

Shaul
fonte
2
Não tenho certeza do que você está tentando dizer, mas observe que nem sempre se trata apenas de copiar arquivos. Imagine que você instalou seu produto, que com a próxima versão vem com estrutura de arquivos totalmente alterada, onde muitos dos arquivos originais foram removidos e novos arquivos têm nomes diferentes e são armazenados em diretórios diferentes. Qual seria a maneira mais fácil de atualizar? Isso não seria desinstalar a versão anterior?
TLama,
Eu uso INNO para instalar um driver e seus aplicativos que o acompanham. Naturalmente, a instalação / remoção real do driver não é feita diretamente pelo INNO. Em vez disso, o INNO copia um aplicativo instalador / removedor de driver e o executa. Desinstalação feita de forma semelhante: INNO executa o removedor de driver e exclui os arquivos.
Shaul
-8

Não use a seção [Run], mas o [UninstallRun]. De fato, os programas em [Executar] são executados após a instalação, causando a desinstalação do programa imediatamente após a instalação: - | Em vez disso, a seção [UninstallRun] é avaliada antes da instalação.

Andrea Ferroni alias bubbakk
fonte
3
[UninstallRun]não é uma solução para a questão.
Craig McQueen
-8

Siga este link: http://news.jrsoftware.org/news/innosetup/msg55323.html

Na função InitializeSetup (), você pode chamar "MSIEXEC / x {seu ID do programa}" após o prompt do usuário para desinstalar a versão antiga

Tonny Nguyen
fonte
5
MSIEXEC só funciona para pacotes MSI. Isso não se aplica ao Inno Setup.
Lex Li