Проблема с кэшированием на диске при inno-setup?

Я постараюсь быть максимально кратким, не прикрепляя все связанные исходные файлы. Я выследил проблему настолько, насколько мне позволяют мои знания Паскаля...

Я обнаружил, что проблема с кэшированием диска возникает в моем случае на этапе ssInstall. У меня есть установщик для приложения, которое, если оно обнаружит более старую версию приложения, будет вызывать удаление следующим образом:

procedure CurStepChanged(CurStep: TSetupStep);
var
  uninstallStr: String;
  ResultCode: Integer;
begin
  if (CurStep = ssInstall) and IsUpdatableApplicationInstalled() then
  begin
  uninstallStr := GetUninstallString();
  uninstallStr := RemoveQuotes(uninstallStr);
  Result := Exec(uninstallStr, '/SILENT /NORESTART /SUPPRESSMSGBOXES', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
  if Result and (ResultCode = 0) then
    Log('CurStepChanged = ssInstall; uninstall OK');
  //-------------
  //Sleep(30000);
  //-------------
end;

Также папки / файлы определены так:

[Dirs]
Name: "{app}\db"; Flags: uninsalwaysuninstall

[Files]
Source: "..\bin\*"; DestDir: "{app}\bin"; Flags: ignoreversion createallsubdirs recursesubdirs
Source: "..\java\jre\*"; DestDir: "{app}\jre"; Flags: ignoreversion recursesubdirs createallsubdirs
blah...

Контрольный пример1; Нормальная установка: все идет гладко. Часть файла журнала:

Starting the installation process.
Creating directory: C:\Program Files                               <---
Creating directory: C:\Program Files\MyApp                         <---
Creating directory: C:\Program Files\MyApp\db                      <---
Creating directory: C:\Program Files\MyApp\jre                     <---
Creating directory: C:\Program Files\MyApp\jre\lib
Creating directory: C:\Program Files\MyApp\jre\lib\applet
Directory for uninstall files: C:\Program Files\MyApp
Creating new uninstall log: C:\Program Files\MyApp\unins000.dat    <--- !!!
-- File entry --
Dest filename: C:\Program Files\MyApp\unins000.exe                 <--- !!!
blah...

Тестовый кейс2; Обновите старую версию: при переходе к шагу ssInstall запускается деинсталлятор, он завершается, а затем начинается установка. Часть файла журнала:

CurStepChanged = ssInstall; uninstall OK
Starting the installation process.
Creating directory: C:\Program Files\MyApp\jre\lib
Creating directory: C:\Program Files\MyApp\jre\lib\applet
Directory for uninstall files: C:\Program Files\MyApp
Creating new uninstall log: C:\Program Files\MyApp\unins001.dat    <--- !!!
-- File entry --
Dest filename: C:\Program Files\MyApp\unins001.exe                 <--- !!!
blah...

Как вы можете видеть, некоторые папки не создаются, и мое приложение перестает работать, когда оно пытается записать в папку 'db'.

Если я раскомментирую команду Sleep(), все пройдет гладко, и оба файла журнала будут идентичны.

Кажется, на диске достаточно времени, чтобы сбросить изменения! Каким-то образом в inno-setup должна отсутствовать команда flush().

Может ли кто-нибудь из инно-гуру прокомментировать или как-то помочь? Есть ли flush(), который я мог бы вызвать вместо sleep()? Любая помощь приветствуется. Я просто хочу быть уверен, прежде чем подать запрос об ошибке.

3 ответа

Решение

Вот как я на самом деле работал над этой проблемой. Я публикую свое решение в надежде помочь другим с той же проблемой.

Большое спасибо всем, кто помог, и особенно Миралу за то, что он указал мне правильное направление!

Решение довольно простое; дождитесь, пока деинсталлятор exe будет удален!

const
  DELAY_MILLIS = 250;
  MAX_DELAY_MILLIS = 30000;


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


function ForceUninstallApplication(): Boolean;
var
  ResultCode: Integer;
  uninstallStr: String;
  delayCounter: Integer;
begin
  // 1) Uninstall the application
  Log('forcing uninstall of application);
  uninstallStr := GetUninstallString();
  Result := Exec(uninstallStr, '/SILENT /NORESTART /SUPPRESSMSGBOXES /LOG', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0);
  if not Result then
  begin
    Log('application uninstall failed!');
    Exit
  end;
  Log('application uninstalled!');

  // 2) Be sure to wait a while, until the actual uninstaller is deleted!
  Log('waiting a while until uninstaller changes are flushed in the filesystem...');
  delayCounter := 0;
  repeat
    Sleep(DELAY_MILLIS);
    delayCounter := delayCounter + DELAY_MILLIS;
  until not FileExists(uninstallStr) or (delayCounter >= MAX_DELAY_MILLIS);
  if (delayCounter >= MAX_DELAY_MILLIS) then
    RaiseException('Timeout exceeded trying to delete uninstaller: ' + uninstallStr);
  Log('waited ' + IntToStr(delayCounter) + ' milliseconds');
end;

функция ForceUninstallApplication() может быть успешно вызван либо из PrepareToInstall или же CurStepChanged(ssInstall), В моем случае это занимает около 500 миллисекунд, когда я использую свой жесткий диск SSD и, возможно, пару секунд, когда я использую свой внешний жесткий диск USB.

Просто, чтобы подвести итог комментария по вопросу:

Не удалить

Лучшее решение - вообще не запускать деинсталлятор. Вы можете удалить лишние файлы через [InstallDelete] раздел; например. чтобы полностью удалить подпапку "jre" (которая будет заменена как часть вашей установки), выполните следующие действия:

[InstallDelete]
Type: filesandordirs; Name: "{app}\jre"

(Используйте это только для таких подпапок, как это, и только экономно; вы можете получить неприятности, если удалите слишком много материала.)

Для обычных файлов отдельных приложений, установленных предыдущей версией, которые теперь являются избыточными, вы можете удалить их следующим образом:

[InstallDelete]
Type: files; Name: "{app}\redundant.dll"

(Вам нужно будет сделать что-то более изворотливое, если у них есть "regserver" или "sharedfile" при установке.)

Если вы удалите независимо, подождите дольше

Деинсталлятор не может удалить себя или папку, в которой он находится, пока он еще работает. Хотя Inno позаботится об этом таким образом, чтобы удалить деинсталлятор и папку, это означает, что Exec вызов деинсталлятора вернется до того, как произойдет удаление, и до фактического завершения процесса деинсталляции.

Вам нужно будет ждать дольше после Exec чтобы деинсталляция действительно закончилась, прежде чем вы позволите продолжить установку. Использование Sleep достаточно просто и будет работать в большинстве случаев, но если вы хотите получить лучшие результаты, вам нужно вызвать WinAPI, чтобы проверить список запущенных процессов.

Кроме того, вы должны использовать PrepareToInstall Функция события для выполнения фактического удаления. Это лучше позволит вам обрабатывать такие случаи, как ошибки удаления или когда требуется перезагрузка между удалением и переустановкой. (И потому что он выполняется в "правильное" время в процессе установки.)

Спасибо Fubar за этот ответ! Я только что сделал небольшую модификацию, чтобы избежать той же проблемы с папкой установки, которая удаляется во время процесса удаления. Он может быть удален после вызова ForceUninstallApplication() и избежать создания папки установки.

Чтобы избежать этой проблемы, я просто создаю временный файл в папке установки и удаляю после завершения установки. Когда неизвестный файл присутствует в папке, unins000.exe не удаляет папку.

Вот обновленный код fubar:

const
  DELAY_MILLIS = 250;
  MAX_DELAY_MILLIS = 30000;

var
  tempUninstallFilename: String;


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


function ForceUninstallApplication(): Boolean;
var
  ResultCode: Integer;
  uninstallStr: String;
  delayCounter: Integer;
begin
  // 1) Create a temporary file to avoid destruction of install folder during uninstall.
  uninstallStr := RemoveQuotes(GetUninstallString());
  tempUninstallFilename := ExtractFileDir(uninstallStr) + '\uninstall.log';
  SaveStringToFile(tempUninstallFilename, '...', False);
  Log('Create temp file: ' + tempUninstallFilename);

  // 2) Uninstall the application
  Log('forcing uninstall of application);
  uninstallStr := GetUninstallString();
  Result := Exec(uninstallStr, '/SILENT /NORESTART /SUPPRESSMSGBOXES /LOG', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0);
  if not Result then
  begin
    Log('application uninstall failed!');
    Exit
  end;
  Log('application uninstalled!');

  // 2) Be sure to wait a while, until the actual uninstaller is deleted!
  Log('waiting a while until uninstaller changes are flushed in the filesystem...');
  delayCounter := 0;
  repeat
    Sleep(DELAY_MILLIS);
    delayCounter := delayCounter + DELAY_MILLIS;
  until not FileExists(uninstallStr) or (delayCounter >= MAX_DELAY_MILLIS);
  if (delayCounter >= MAX_DELAY_MILLIS) then
    RaiseException('Timeout exceeded trying to delete uninstaller: ' + uninstallStr);
  Log('waited ' + IntToStr(delayCounter) + ' milliseconds');
end;


//============================================================================================================
// SUMMARY
// You can use this event function to perform your own pre-install and post-install tasks.
procedure CurStepChanged(CurStep: TSetupStep);
begin
  if (CurStep = ssInstall) then
  begin
    ForceUninstallApplication();
  end ;

  if (CurStep = ssPostInstall) then
  begin
    DeleteFile(tempUninstallFilename);
  end;
end;
Другие вопросы по тегам