I’m using Inno Setup to package a Java application for Windows; the application tree is like this:
| MyApp.jar ---lib | dependency-A-1.2.3.jar | dependency-B-2.3.4.jar | dependency-Z-x.y.z.jar
I use Ant to prepare the whole tree (all the files and folders) beforehand, including the lib
directory (using *.jar
wildcard to copy the dependencies), then I simply call ISCC
with:
[Files] Source: "PreparedFolder*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs
Now, I need to cleanup the lib
directory everytime the user upgrades the application because I want to remove any obsolete dependencies. I could add the following section to my .iss
file:
[InstallDelete] {app}lib*.jar
but I’m not feeling safe because if a user decides to install the application in an existing folder that contains a not-empty lib
subfolder (rare but not impossible), there is a chance that some user files are deleted on upgrade.
Is there any best practice to avoid this kind of troubles? Do other installers take care of these headaches? Thanks.
Advertisement
Answer
You can uninstall the previous version before the installation:
- Inno Setup: How to automatically uninstall previous installed version?
- How to detect old installation and offer removal?
If you cannot do a complete uninstallation, you would have to implement a partial uninstallation.
Ideal would be to reverse-engineer the uninstaller log (unins000.dat
), extract only installations to the lib
subfolder and process (undo) them. But as that is an undocumented binary file, it can be difficult to do.
If you maintain an explicit list of files to be installed in the [Files]
section, like
[Files] Source: "libdependency-A-1.2.3.jar"; Dest: "{app}lib" Source: "libdependency-B-2.3.4.jar"; Dest: "{app}lib"
then whenever a dependency changes, move the previous version to the [InstallDelete]
section:
[Files] Source: "libdependency-A-1.3.0.jar"; Dest: "{app}" Source: "libdependency-B-2.3.4.jar"; Dest: "{app}" [InstallDelete] {app}libdependency-A-1.2.3.jar
If you install the dependencies using a wildcard,
[Files] Source: "lib*.jar"; Dest: "{app}lib"
and you cannot reverse-engineer the uninstaller log, you would have to replicate its functionality by your own means.
You can use a preprocessor to generate a file with installed dependencies. Install that file to the {app}
folder and process the file before installation.
[Files] Source: "MyApp.jar"; DestDir: "{app}" Source: "lib*.jar"; DestDir: "{app}lib" #define ProcessFile(Source, FindResult, FindHandle) Local[0] = FindGetFileName(FindHandle), Local[1] = Source + "\" + Local[0], Local[2] = FindNext(FindHandle), "'" + Local[0] + "'#13#10" + (Local[2] ? ProcessFile(Source, Local[2], FindHandle) : "") #define ProcessFolder(Source) Local[0] = FindFirst(Source + "\*.jar", faAnyFile), ProcessFile(Source, Local[0], Local[0]) #define DepedenciesToInstall ProcessFolder("lib") #define DependenciesLog "{app}dependencies.log" [UninstallDelete] Type: files; Name: "{#DependenciesLog}"
[Code] procedure CurStepChanged(CurStep: TSetupStep); var AppPath, DependenciesLogPath: string; Dependencies: TArrayOfString; Count, I: Integer; begin DependenciesLogPath := ExpandConstant('{#DependenciesLog}'); if CurStep = ssInstall then begin // If dependencies log already exists, // remove the previously installed dependencies if LoadStringsFromFile(DependenciesLogPath, Dependencies) then begin Count := GetArrayLength(Dependencies); Log(Format('Loaded %d dependencies, deleting...', [Count])); for I := 0 to Count - 1 do DeleteFile(ExpandConstant('{app}lib' + Dependencies[I])); end; end else if CurStep = ssPostInstall then begin // Now that the app folder already exists, // save dependencies log (to be processed by future upgrade) if SaveStringToFile(DependenciesLogPath, {#DepedenciesToInstall}, False) then begin Log('Created dependencies log'); end else begin Log('Failed to create dependencies log'); end; end; end;
Another approach is to delete all files in the installation folder that is not installed by the latest installer.
The easiest solution is to delete all files in the installation folder before the installation.
You can use [InstallDelete]
section for that. But if you have some folder/files with configuration in the installation folder, it won’t allow you to exclude them.
You can code that Pascal Scripting instead. See Inno Setup – Delete whole application folder except for data subdirectory. You can call the DelTreeExceptSavesDir
function from my answer to the that question from CurStepChanged(ssInstall)
event function:
procedure CurStepChanged(CurStep: TSetupStep); begin if CurStep = ssInstall then begin DelTreeExceptSavesDir(WizardDirValue); end; end;
If you really want to delete only obsolete files, to avoid deleting and re-creating existing files, you can use preprocessor to generate a list of files to be installed for the Pascal Scripting and use that to delete only really obsolete files.
#pragma parseroption -p- #define FileEntry(DestDir) " FilesNotToBeDeleted.Add('" + LowerCase(DestDir) + "');n" #define ProcessFile(Source, Dest, FindResult, FindHandle) FindResult ? Local[0] = FindGetFileName(FindHandle), Local[1] = Source + "\" + Local[0], Local[2] = Dest + "\" + Local[0], (Local[0] != "." && Local[0] != ".." ? FileEntry(Local[2]) + (DirExists(Local[1]) ? ProcessFolder(Local[1], Local[2]) : "") : "") + ProcessFile(Source, Dest, FindNext(FindHandle), FindHandle) : "" #define ProcessFolder(Source, Dest) Local[0] = FindFirst(Source + "\*", faAnyFile), ProcessFile(Source, Dest, Local[0], Local[0]) #pragma parseroption -p+
[Code] var FilesNotToBeDeleted: TStringList; function InitializeSetup(): Boolean; begin FilesNotToBeDeleted := TStringList.Create; FilesNotToBeDeleted.Add('data'); {#Trim(ProcessFolder('buildexe.win-amd64-3.6', ''))} FilesNotToBeDeleted.Sorted := True; Result := True; end; procedure DeleteObsoleteFiles(Path: string; RelativePath: string); var FindRec: TFindRec; FilePath: string; FileRelativePath: string; begin if FindFirst(Path + '*', FindRec) then begin try repeat if (FindRec.Name <> '.') and (FindRec.Name <> '..') then begin FilePath := Path + '' + FindRec.Name; FileRelativePath := RelativePath + '' + FindRec.Name; if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY <> 0 then begin DeleteObsoleteFiles(FilePath, FileRelativePath); end; if FilesNotToBeDeleted.IndexOf(Lowercase(FileRelativePath)) < 0 then begin if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY <> 0 then begin if RemoveDir(FilePath) then begin Log(Format('Deleted obsolete directory %s', [FilePath])); end else begin Log(Format('Failed to delete obsolete directory %s', [FilePath])); end; end else begin if DeleteFile(FilePath) then begin Log(Format('Deleted obsolete file %s', [FilePath])); end else begin Log(Format('Failed to delete obsolete file %s', [FilePath])); end; end; end; end; until not FindNext(FindRec); finally FindClose(FindRec); end; end else begin Log(Format('Failed to list %s', [Path])); end; end; procedure CurStepChanged(CurStep: TSetupStep); begin if CurStep = ssInstall then begin Log('Looking for obsolete files...'); DeleteObsoleteFiles(WizardDirValue, ''); end; end;