Inno Setup: Jak automatycznie odinstalować poprzednią zainstalowaną wersję?

88

Używam Inno Setup do tworzenia instalatora.

Chcę, aby instalator automatycznie odinstalował poprzednio zainstalowaną wersję, zamiast ją nadpisywać. Jak mogę to zrobić?

Quan Mai
źródło
2
Zauważ, że jak powiedział mlaan , normalnie nie ma potrzeby robienia tego z konfiguracją opartą na Inno, chyba że aktualizujesz z wersji innej niż Inno.
Deanna
7
Deanna: to zależy od przypadku. W przypadku niektórych programów z automatycznymi systemami wtyczek, które odczytują wszystko w folderze, usunięcie starych plików jest absolutną koniecznością podczas instalowania nowej wersji, a zwykłe odinstalowanie jest zwykle najczystszym sposobem na zrobienie tego.
Nyerguds
1
@Nyerguds Ale InnoSetup radzi sobie z tym, mając opcję usunięcia niektórych plików / folderów przed rozpoczęciem instalacji (flaga „InstallDelete”), więc nadal nie musisz najpierw odinstalowywać starej wersji.
NickG,
3
@NickG: Znowu, zależy od przypadku. To byłaby sytuacja idealna, tak i zdecydowanie preferowana, ale w rzeczywistości jest całkiem sporo sytuacji nieidealnych. Jednym z takich przykładów są zarejestrowane pliki dll w wielu możliwych wersjach docelowych.
Nyerguds

Odpowiedzi:

27

Powinno być możliwe odczytanie ciągu dezinstalacji z rejestru, biorąc pod uwagę identyfikator AppId (tj. Wartość użytą AppIDw sekcji [Setup]). Można go znaleźć pod Software\Microsoft\Windows\CurrentVersion\Uninstall\{AppId}\(może być albo HKLMalbo HKCU, więc najlepiej sprawdzić oba), gdzie {AppId}należy go zastąpić rzeczywistą użytą wartością. Poszukaj wartości UninstallStringlub QuietUninstallStringi użyj Execfunkcji, aby uruchomić ją z InitializeSetup()funkcji zdarzenia.

Aktualizacja: usunięto niedziałające alternatywne rozwiązanie z użyciem [Run]wpisu sekcji z {uninstallexe}- podziękowania dla wszystkich komentujących, którzy zwrócili na to uwagę!

Oliver Giesen
źródło
+1, ale zdecydowanie użyj skryptów do odczytania starej nazwy deinstalatora, ponieważ w przeciwnym razie nie zadziała, jeśli użytkownik wprowadził inną ścieżkę.
mghie
3
Nie sądzę, aby [Run]rozwiązanie sekcji działało, ponieważ działa zbyt późno w procesie instalacji. Z podręcznika Inno Setup: Sekcja [Run] jest opcjonalna i określa dowolną liczbę programów do wykonania po pomyślnym zainstalowaniu programu, ale zanim program instalacyjny wyświetli ostatnie okno dialogowe.
Craig McQueen
Edytuj ten post i usuń część [Uruchom], to nie działa. Jednak druga część działa. Dziękuję :-)
Saulius Žemaitaitis
11
Jedno ostrzeżenie: w przypadku aplikacji 32-bitowej w 64-bitowym systemie Windows ścieżka może wyglądać następująco: `Software \ Wow6432Node \ Microsoft \ Windows \ CurrentVersion \ Uninstall \ {AppId}`
Adrian Cox,
4
@Adrian: Chociaż może to być prawdą w warstwie fizycznej, myślę, że wywołania WinAPI używane przez Inno już się tym zajmą - przynajmniej jeśli sam plik setup.exe jest procesem 32-bitowym.
Oliver Giesen
112

Użyłem następujących. Nie jestem pewien, czy to najprostszy sposób, ale działa.

To używa, {#emit SetupSetting("AppId")}który opiera się na Preprocesorze Inno Setup. Jeśli tego nie używasz, wytnij i wklej swój identyfikator aplikacji bezpośrednio.

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

Alternatywy

Zobacz także ten wpis na blogu „Inno Setup Script Sample for Version Porównanie”, który idzie o krok dalej i czyta numer każdej poprzednio zainstalowanej wersji i porównuje ten numer z numerem bieżącego pakietu instalacyjnego.

Craig McQueen
źródło
3
dziękuję za odniesienie się do mojego wpisu na blogu. Pełna próbka tego posta jest dostępna tutaj, code.google.com/p/lextudio/source/browse/trunk/trunk/setup/ ...
Lex Li
Jedyne, czego tutaj brakuje, to obsługa wpisu Uninstall w HKCU zamiast HKLM.
Oliver Giesen
1
Czy mogę zasugerować dodanie możliwości odinstalowania, jeśli którykolwiek użytkownik zainstalował aplikację, szczególnie jeśli bieżący użytkownik jest administratorem? ... 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
Świetne rozwiązanie, działa dobrze. Jednak podczas instalacji otwiera się okno z informacją „Odinstalowywanie [nazwa oprogramowania]”. Czy można zapobiec wyskakiwaniu tego okna? Ponieważ instalacja mojego oprogramowania jest tak szybka, że ​​okno instalacji zamyka się przed oknem dezinstalacji i wygląda dziwnie ...
André Santaló,
4
@ AndréSantaló Use / VERYSILENT zamiast / SILENT
Gautam Jain
7

Jeśli chcesz „tylko usunąć stare ikony” (ponieważ Twoje uległy zmianie / aktualizacji), możesz użyć tego:

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

Jest to uruchamiane „na początku instalacji”, więc zasadniczo usuwa stare ikony, a nowe będą nadal tam instalowane po zakończeniu instalacji.

Po prostu robię to przy każdej instalacji "na wypadek, gdyby coś się zmieniło" pod względem ikon (wszystko i tak zostanie ponownie zainstalowane).

rogerdpack
źródło
Jeśli masz aktualizację ikon, po prostu pozwól im nadpisać. Nie ma potrzeby ich usuwania. Cóż, jeśli chcesz je usunąć, możesz skorzystać z tej opcji. To jest właściwy sposób. W każdym razie facet, z którym rozmawiałeś (mlaan, Martijn Laan) jest autorem Inno Setup i myślę, że wie, o czym mówi :-)
TLama
1
Tak, potrzebujesz tego, gdy chcesz zmienić nazwę lub przenieść ikonę. Na przykład, jeśli wersja 5 ma jedną o nazwie „uruchom”, a wersja 6 zmieniła jej nazwę na „uruchom podstawową”, jeśli użytkownik zainstaluje wersję 5, a następnie wersję 6, otrzymają 2 ikony, kiedy naprawdę chcesz 1 („uruchom podstawową”). Więc ta sztuczka okazuje się konieczna (@mlaan +1 do zmiany domyślnego zachowania innosetup na usuwanie starych ikon i nie potrzeba tego ...)
rogerdpack
6

Podczas korzystania z Inno Setup nie ma powodu, aby odinstalować poprzednią wersję, chyba że ta wersja została zainstalowana przez inny program instalacyjny. W przeciwnym razie aktualizacje są obsługiwane automatycznie.

mlaan
źródło
6
Nasz program zmienił strukturę, więc starą wersję należy odinstalować.
Quan Mai
11
Nie, nie, możesz dodać wpisy do skryptu, aby obsłużyć zmianę struktury podczas aktualizacji.
mlaan,
@mlaan A jakie to byłyby wpisy?
mythofechelon
1
Możesz po prostu użyć [InstallDelete]sekcji, aby usunąć stare pliki / katalogi. Nowe pliki zostaną następnie umieszczone we właściwych lokalizacjach podczas instalacji.
daiscog
2
Jeśli zaktualizujesz bibliotekę innej firmy, taką jak DevExpress, która ma numery wersji w nazwach DLL (np. 15.1 zainstalowana wcześniej i 15.2 teraz), chcesz usunąć starą wersję. IMHO to dobry powód.
Thomas Weller,
2

Odpowiedź udzielona przez Craiga McQueena jest całkowicie realna. Chociaż dodałbym te uwagi:

  • {#emit SetupSetting("AppId")}Kod nie działa dla mnie, więc po prostu dodać swój identyfikator aplikacji.
  • Nie chciałem uruchamiać programu deinstalacyjnego, ponieważ mam plik konfiguracyjny INI przechowywany w folderze AppData /, który jest usuwany przez deinstalator i nie chcę, aby był on usuwany podczas instalowania nowej wersji. Więc zmodyfikowałem nieco kod dostarczony przez Craiga McQueena, aby usunąć katalog, w którym jest zainstalowany program, po pobraniu jego ścieżki.

Tak więc, jeśli chodzi o kod Craiga McQueena, zmiany są następujące:

  • Pobierz InstallLocationklucz zamiast UninstallStringklucza.
  • Użyj DelTreefunkcji zamiastExec(sUnInstallString, ...)

źródło
1

Dla każdego, kto używa GetUninstallString()sugerowanego powyżej, aby wymusić odinstalowanie wewnątrz CurStepChanged()i ma problemy z buforowaniem dysku, zobacz poniżej powiązane rozwiązanie, które faktycznie czeka chwilę po odinstalowaniu na usunięcie deinstalatora exe!

Problem z pamięcią podręczną dysku w przypadku inno-setup?

fubar
źródło
0

Możesz uruchomić deinstalator w sekcji [kod]. Musisz dowiedzieć się, jak uzyskać ścieżkę do istniejącego deinstalatora. Dla uproszczenia podczas instalowania aplikacji dodaję wartość ciągu rejestru wskazującą na folder zawierający dezinstalator i po prostu wykonuję deinstalator w wywołaniu zwrotnym InitializeWizard.

Należy pamiętać, że wszystkie nazwy deinstalatorów instalacji Inno mają postać uninsnnn.exe, należy to uwzględnić w kodzie.

Jim w Teksasie
źródło
0

Mam edytowany kod @Crain Mc-Queen, myślę, że ten kod jest lepszy, ponieważ nie trzeba go modyfikować w innym projekcie:

[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
źródło
-1

Muszę czegoś przegapić. Te nowe pliki zostaną skopiowane do katalogu docelowego, zanim nastąpi usunięcie starej instalacji. Następnie deinstalator usuwa je i usuwa katalog.

Shaul
źródło
2
Nie jestem pewien, co próbujesz powiedzieć, ale pamiętaj, że nie zawsze chodzi tylko o kopiowanie plików. Wyobraź sobie, że masz zainstalowany produkt, który w następnej wersji ma całkowicie zmienioną strukturę plików, w którym wiele oryginalnych plików zostało usuniętych, a nowe pliki mają różne nazwy i są przechowywane w różnych katalogach. Jaki byłby najłatwiejszy sposób na uaktualnienie? Czy nie oznaczałoby to odinstalowania poprzedniej wersji?
TLama
Używam INNO do instalacji sterownika i towarzyszących mu aplikacji. Oczywiście faktyczna instalacja / usuwanie sterownika nie jest wykonywana bezpośrednio przez INNO. Zamiast tego INNO kopiuje aplikację do instalowania / usuwania sterowników, a następnie ją uruchamia. Odinstalowanie wykonano podobnie: INNO uruchamia narzędzie do usuwania sterowników, a następnie usuwa pliki.
Shaul
-8

Nie używaj sekcji [Run], ale [UninstallRun]. W rzeczywistości programy w [Run] są uruchamiane po instalacji, powodując odinstalowanie programu natychmiast po instalacji: - | Zamiast tego sekcja [UninstallRun] jest sprawdzana przed instalacją.

Andrea Ferroni alias bubbakk
źródło
3
[UninstallRun]nie jest rozwiązaniem na to pytanie.
Craig McQueen
-8

Skorzystaj z tego łącza: http://news.jrsoftware.org/news/innosetup/msg55323.html

W funkcji InitializeSetup () możesz wywołać „MSIEXEC / x {identyfikator Twojego programu}” po wyświetleniu monitu użytkownika o odinstalowanie starej wersji

Tonny Nguyen
źródło
5
MSIEXEC działa tylko dla pakietu MSI. Nie dotyczy to Inno Setup.
Lex Li