Inno Setup: ¿Cómo desinstalar automáticamente la versión anterior instalada?

88

Estoy usando Inno Setup para crear un instalador.

Quiero que el instalador desinstale automáticamente la versión instalada anterior, en lugar de sobrescribirla. ¿Cómo puedo hacer eso?

Quan Mai
fuente
2
Tenga en cuenta que, como dijo mlaan , normalmente no es necesario hacer esto con una configuración basada en Inno, a menos que esté actualizando desde una versión que no sea Inno.
Deanna
7
Deanna: depende del caso. Para algunos programas con sistemas de complementos automáticos, que leen cualquier cosa en una carpeta, la eliminación de archivos antiguos es una necesidad absoluta al instalar una nueva versión, y simplemente ejecutar la desinstalación suele ser la forma más limpia de hacerlo.
Nyerguds
1
@Nyerguds Pero InnoSetup se encarga de eso al tener una opción para eliminar ciertos archivos / carpetas antes de que comience la instalación (marca "InstallDelete"), por lo que aún no necesitaría desinstalar la versión anterior primero.
NickG
3
@NickG: De nuevo, depende del caso. Esa sería la situación ideal, sí, y con mucho la preferida, pero en realidad hay bastantes situaciones no ideales. Un ejemplo son los archivos dll registrados, en muchas posibles versiones de destino.
Nyerguds

Respuestas:

27

Debería poder leer la cadena de desinstalación del registro, dado el AppId (es decir, el valor que utilizó AppIDen la [Setup]sección-). Se puede encontrar en Software\Microsoft\Windows\CurrentVersion\Uninstall\{AppId}\(podría ser HKLMo HKCU, por lo que es mejor marcar ambos) donde {AppId}debe sustituirse por el valor real que utilizó. Busque los valores UninstallStringo QuietUninstallStringy use la Execfunción para ejecutarlo desde su InitializeSetup()función de evento.

Actualización: se eliminó la solución alternativa que no funciona usando una [Run]entrada de sección con {uninstallexe}- ¡gracias a todos los comentaristas que señalaron esto!

Oliver Giesen
fuente
+1, pero definitivamente use secuencias de comandos para leer el nombre del desinstalador anterior, porque de lo contrario no funcionará si el usuario ha ingresado una ruta diferente.
mghie
3
No creo que la [Run]solución de la sección funcione, porque se ejecuta demasiado tarde en el proceso de instalación. Del manual Inno Setup: La sección [Ejecutar] es opcional y especifica cualquier número de programas para ejecutar después de que el programa se haya instalado correctamente, pero antes de que el programa de instalación muestre el cuadro de diálogo final.
Craig McQueen
Edite esta publicación y elimine la parte [Ejecutar], no funciona. Sin embargo, la segunda parte funciona. Gracias :-)
Saulius Žemaitaitis
11
Una advertencia: para una aplicación de 32 bits en Windows de 64 bits, la ruta puede ser `Software \ Wow6432Node \ Microsoft \ Windows \ CurrentVersion \ Uninstall \ {AppId}`
Adrian Cox
4
@Adrian: Si bien eso puede ser cierto en la capa física, creo que las llamadas WinAPI utilizadas por Inno ya se encargarán de eso, al menos si el setup.exe en sí es un proceso de 32 bits.
Oliver Giesen
112

He usado lo siguiente. No estoy seguro de que sea la forma más sencilla de hacerlo, pero funciona.

Este utiliza {#emit SetupSetting("AppId")}que se basa en el preprocesador de configuración Inno. Si no lo usa, corte y pegue su ID de aplicación directamente.

[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

Consulte también esta publicación de blog "Ejemplo de secuencia de comandos de configuración de Inno para la comparación de versiones", que va un paso más allá, lee el número de versión de cualquier versión instalada previamente y compara ese número de versión con el del paquete de instalación actual.

Craig McQueen
fuente
3
gracias por consultar la publicación de mi blog. La muestra completa de esa publicación está disponible aquí, code.google.com/p/lextudio/source/browse/trunk/trunk/setup/…
Lex Li
Lo único que falta aquí es la compatibilidad con una entrada de desinstalación en HKCU en lugar de HKLM.
Oliver Giesen
1
¿Puedo sugerir agregar la capacidad de desinstalar si algún usuario instaló la aplicación, particularmente si el usuario actual es un 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
Gran solución, funciona bien. Sin embargo, abre una ventana durante la instalación que muestra "Desinstalando [nombre del software]". ¿Es posible evitar que se abra esta ventana? Porque la instalación de mi software es tan rápida que la ventana de instalación se cierra antes que la ventana de desinstalación y se ve raro ...
André Santaló
4
@ AndréSantaló Use / VERYSILENT en lugar de / SILENT
Gautam Jain
7

Si "solo desea eliminar los íconos antiguos" (porque los suyos han cambiado / actualizado), puede usar esto:

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

Esto se ejecuta "al comienzo de la instalación", por lo que básicamente elimina los iconos antiguos, y los nuevos seguirán instalados allí después de que esto esté completamente hecho.

Solo hago esto con cada instalación "en caso de que algo haya cambiado" en cuanto a iconos (todo se reinstala de todos modos).

Rogerdpack
fuente
Si tiene una actualización para sus íconos, simplemente déjelos sobrescribir. No es necesario eliminarlos. Bueno, si desea eliminarlos, puede usar esta opción. Esa es la forma correcta. De todos modos, el tipo con el que estabas hablando (mlaan, Martijn Laan) es el autor de Inno Setup y creo que sabe de lo que está hablando :-)
TLama
1
Sí, es cuando desea cambiar el nombre o mover un ícono cuando lo necesita. Por ejemplo, si v5 tiene uno llamado "ejecutar" y v6 lo ha cambiado a "ejecutar básico" si un usuario instala v5 y luego v6, terminará con 2 íconos, cuando en realidad querías 1 ("ejecutar básico"). Entonces, este truco termina siendo necesario (@mlaan +1 para cambiar el comportamiento predeterminado de innosetup para eliminar los íconos antiguos y no necesitar esto ...)
rogerdpack
6

Al usar Inno Setup, no hay razón para desinstalar una versión anterior a menos que esa versión haya sido instalada por un programa de instalación diferente. De lo contrario, las actualizaciones se gestionan automáticamente.

mlaan
fuente
6
Nuestro programa ha cambiado de estructura, por lo que es necesario desinstalar la versión anterior.
Quan Mai
11
No, puede agregar entradas a su script para manejar el cambio de estructura durante una actualización.
mlaan
@mlaan ¿Y qué entradas serían esas?
mitofechelon
1
Simplemente puede usar una [InstallDelete]sección para eliminar archivos / directorios antiguos. Los nuevos archivos se colocarán en las ubicaciones correctas durante la instalación.
daiscog
2
Si actualiza una biblioteca de terceros como DevExpress, que tiene números de versión en los nombres de DLL (como 15.1 instalada antes y 15.2 ahora), entonces desea eliminar la versión anterior. En mi humilde opinión, esa es una buena razón.
Thomas Weller
2

La respuesta proporcionada por Craig McQueen es totalmente viable. Aunque, agregaría esos comentarios:

  • El {#emit SetupSetting("AppId")}código no me funciona, así que solo agrego mi ID de aplicación.
  • No quería ejecutar mi programa de desinstalación porque tengo un archivo de configuración INI almacenado en la carpeta AppData / que es eliminado por el desinstalador, y no quiero que se borre al instalar una nueva versión. Entonces, modifiqué un poco el código provisto por Craig McQueen para quitar el directorio donde está instalado el programa, luego de recuperar su ruta.

Entonces, con respecto al código de Craig McQueen, los cambios son:

  • Recupera la InstallLocationllave en lugar de la UninstallStringllave.
  • Utilice la DelTreefunción en lugar de laExec(sUnInstallString, ...)

fuente
1

Para cualquier persona que use lo GetUninstallString()sugerido anteriormente para forzar una desinstalación interna CurStepChanged()y tenga problemas de almacenamiento en caché de disco, consulte a continuación una solución relacionada que en realidad espera un tiempo después de la desinstalación para que se elimine el desinstalador exe.

¿Problema de almacenamiento en caché de disco con inno-setup?

fubar
fuente
0

Puede ejecutar un desinstalador en la sección [código]. Tienes que averiguar cómo obtener la ruta al desinstalador existente. Para simplificar, cuando instalo mis aplicaciones, agrego un valor de cadena de registro que apunta a la carpeta que contiene el desinstalador, y simplemente ejecuto el desinstalador en la devolución de llamada de InitializeWizard.

Tenga en cuenta que los nombres del desinstalador de instalación de Inno tienen el formato uninsnnn.exe, debe tenerlo en cuenta en su código.

Jim en Texas
fuente
0

Me editaron el código de @Crain Mc-Queen, creo que este código es mejor porque no es necesario modificarlo en un proyecto diferente:

[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
fuente
-1

Debo estar perdiendo algo. Los nuevos archivos se copian en el directorio de destino antes de que se elimine la instalación anterior. Luego viene el desinstalador los borra y elimina el directorio.

Shaul
fuente
2
No estoy seguro de lo que intentas decir, pero ten en cuenta que no siempre se trata solo de copiar archivos. Imagine que habría instalado su producto, que con la próxima versión viene con una estructura de archivos totalmente modificada, donde muchos de los archivos originales se eliminaron y los archivos nuevos tienen diferentes nombres y se almacenan en diferentes directorios. ¿Cuál sería la forma más sencilla de actualizar? ¿No sería desinstalar la versión anterior?
TLama
Utilizo INNO para instalar un controlador y las aplicaciones que lo acompañan. Naturalmente, INNO no realiza directamente la instalación / desinstalación del controlador. Más bien, INNO copia una aplicación de instalación / eliminación de controladores y luego la ejecuta. Desinstalación realizada de manera similar: INNO ejecuta el eliminador de controladores y luego elimina los archivos.
Shaul
-8

No utilice la sección [Ejecutar], sino [UninstallRun]. De hecho, el programa en [Ejecutar] se ejecuta después de la instalación, lo que provoca la desinstalación del programa inmediatamente después de la instalación: - | En su lugar, la sección [UninstallRun] se evalúa antes de la instalación.

Andrea Ferroni alias bubbakk
fuente
3
[UninstallRun]no es una solución para la pregunta.
Craig McQueen
-8

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

En la función InitializeSetup (), puede llamar a "MSIEXEC / x {su ID de programa}" después de que el usuario le pida que desinstale la versión anterior.

Tonny Nguyen
fuente
5
MSIEXEC solo funciona con el paquete MSI. Eso no se aplica a Inno Setup.
Lex Li