How to get Inno Setup to unzip a single file? - winapi

Is there a way to unzip just one file from a zip?
I'm using code based on response for How to get Inno Setup to unzip a file it installed (all as part of the one installation process), works perfect for unzip but don't have an idea how can it unzip a single file:
[Code]:
const
NO_PROGRESS_BOX = 4;
RESPOND_YES_TO_ALL = 16;
procedure UnZip(ZipPath, TargetPath: string);
var
Shell: Variant;
ZipFile: Variant;
TargetFolder: Variant;
begin
Shell := CreateOleObject('Shell.Application');
ZipFile := Shell.NameSpace(ZipPath);
if VarIsClear(ZipFile) then
RaiseException(Format('ZIP file "%s" does not exist or cannot be opened', [ZipPath]));
TargetFolder := Shell.NameSpace(TargetPath);
if VarIsClear(TargetFolder) then
RaiseException(Format('Target path "%s" does not exist', [TargetPath]));
TargetFolder.CopyHere(ZipFile.Items, NO_PROGRESS_BOX or RESPOND_YES_TO_ALL);
end;

Use Folder.ParseName to retrieve a reference to a specific file in a ZIP archive "folder". Then pass that reference to Folder.CopyHere to extract it.
const
NO_PROGRESS_BOX = 4;
RESPOND_YES_TO_ALL = 16;
procedure UnZip(ZipPath, FileName, TargetPath: string);
var
Shell: Variant;
ZipFile: Variant;
Item: Variant;
TargetFolder: Variant;
begin
Shell := CreateOleObject('Shell.Application');
ZipFile := Shell.NameSpace(ZipPath);
if VarIsClear(ZipFile) then
RaiseException(Format('Cannot open ZIP file "%s" or does not exist', [ZipPath]));
Item := ZipFile.ParseName(FileName);
if VarIsClear(Item) then
RaiseException(Format('Cannot find "%s" in "%s" ZIP file', [FileName, ZipPath]));
TargetFolder := Shell.NameSpace(TargetPath);
if VarIsClear(TargetFolder) then
RaiseException(Format('Target path "%s" does not exist', [TargetPath]));
TargetFolder.CopyHere(Item, NO_PROGRESS_BOX or RESPOND_YES_TO_ALL);
end;

I found a way that works, not what I was expected but is functional.
UnZip(AppFolder+'\modulos\seimpresoras-2.2.zip', tmpFolder);
FileCopy(tmpFolder+'\seimpresoras\resources\default.properties', AppFolder+'\printers.properties', False);

Related

Install the same file to all subdirectories in Inno Setup

I am newbie with Inno Setup and I have been reading some threads but could not find how to do the following.
I simply would like to search for folders within a directory and in each folder detected install the same file with no selection of wizard page shown to the user. Not recursive, only files inside the detected folders and not subfolders.
I meant to install the same file in all folders detected while giving no option to the user to choose from. However, all other pages in the installer would be displayed as usual.
Thanks in advance
Tag the file with dontcopy flag and then install it programmatically in CurStepChanged(ssInstall) (or ssPostInstall).
Use ExtractTemporaryFile to extract the file to a temporary folder.
Use FindFirst/FindNext functions to find the subfolders.
Use FileCopy to copy the file from a temporary folder to the found subfolder(s).
Log a lot.
This will work well, only if the file is not huge. Otherwise the installer will unpleasantly hang. For a good user experience with huge files, more complex solution is needed.
#define TheFileName "thefile.txt"
[Files]
Source: "{#TheFileName}"; Flags: dontcopy
[Code]
procedure CurStepChanged(CurStep: TSetupStep);
var
RootPath: string;
TempPath: string;
DestPath: string;
FindRec: TFindRec;
Count: Integer;
begin
if CurStep = ssInstall then
begin
Log('Extracting {#TheFileName}...');
ExtractTemporaryFile('{#TheFileName}');
TempPath := ExpandConstant('{tmp}\{#TheFileName}');
RootPath := ExpandConstant('{app}');
Log(Format('Searching in "%s"...', [RootPath]));
Count := 0;
if not FindFirst(RootPath + '\*', FindRec) then
begin
Log(Format('"%s" not found.', [RootPath]));
end
else
begin
try
repeat
if (FindRec.Name <> '.') and (FindRec.Name <> '..') and
(FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY <> 0) then
begin
Log(Format('Found "%s".', [FindRec.Name]));
DestPath := RootPath + '\' + FindRec.Name + '\{#TheFileName}';
if FileCopy(TempPath, DestPath, False) then
begin
Log(Format('The file was installed to "%s".', [DestPath]));
Inc(Count);
end
else
begin
Log(Format('Error installing the file to "%s".', [DestPath]));
end;
end;
until not FindNext(FindRec);
finally
FindClose(FindRec);
end;
if Count = 0 then
begin
Log(Format('No subfolder to install file "%s" to was found in "%s".', [
'{#TheFileName}', RootPath]));
end
else
begin
Log(Format('File "%s" was installed to %d subfolder(s) of "%s".', [
'{#TheFileName}', Count, RootPath]));
end;
end;
end;
end;
Alternatively, if you have a fixed set of folders, you can generate entry for each folder in the [Files] section using preprocessor:
[Files]
#define FolderEntry(Name) \
"Source: ""C:\source\*""; DestDir: ""{app}\" + Name + """; " + \
"Check: CheckDir('" + Name + "')"
#emit FolderEntry('2023')
#emit FolderEntry('2024')
#emit FolderEntry('2025')
[Code]
function CheckDir(DirName: string): Boolean;
begin
Result := DirExists(ExpandConstant('{app}') + '\' + DirName);
end;
If you add SaveToFile to the end of the script:
#expr SaveToFile(AddBackslash(SourcePath) + "Preprocessed.iss")
... then you should see in Preprocessed.iss that the code generates a script like this:
[Files]
Source: "C:\source\*"; DestDir: "{app}\2023"; Check: CheckDir('2023')
Source: "C:\source\*"; DestDir: "{app}\2024"; Check: CheckDir('2024')
Source: "C:\source\*"; DestDir: "{app}\2025"; Check: CheckDir('2025')
Thanks a lot Martin! I did it another way that may be of help to other users.
I set a file for every potential folder I want to detect (it was only for four).
[Files] Source: "C:\Users\XXXXX\dll\*"; DestDir: "{commonappdata}\XXXX\2023"; Check:CheckDir2023;
Then I use the following to check if the folder exists:
function CheckDir2023 : Boolean;
begin
if (DirExists(ExpandConstant('{commonappdata}\xxxxxx\2023\'))) then
begin
Result := True;
end
else
begin
Result := False;
end;
end;

How to extracts files that are in folder within a ZIP file with Inno Setup?

Well, what I have is a Python project, and now am creating an installer.
I added to that the project's file .exe and a .zip file.
The zip file contains .exe modules, data, etc. And its structure is like this:
example.zip:
|---project-folder:
|----here will be the files.
|----here will be the files.
What I want is to extract those files that are inside the project-folder. So the .exe can run.
I have this code to extract a zip file:
[Code]
procedure InitializeWizard;
begin
ForceDirectories(ExpandConstant('{localappdata}\folder-A\app\folder-B'))
end;
const
SHCONTCH_NOPROGRESSBOX = 4;
SHCONTCH_RESPONDYESTOALL = 16;
procedure unzip(ZipFile, TargetFldr: variant);
var
shellobj: variant;
SrcFldr, DestFldr: variant;
shellfldritems: variant;
begin
if FileExists(ZipFile) then begin
if not DirExists(TargetFldr) then
if not ForceDirectories(TargetFldr) then begin
MsgBox('Can not create folder '+TargetFldr+' !!', mbError, MB_OK);
Exit;
end;
shellobj := CreateOleObject('Shell.Application');
SrcFldr := shellobj.NameSpace(ZipFile);
DestFldr := shellobj.NameSpace(TargetFldr);
shellfldritems := SrcFldr.Items;
DestFldr.CopyHere(
shellfldritems, SHCONTCH_NOPROGRESSBOX or SHCONTCH_RESPONDYESTOALL);
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
unzip(ExpandConstant('{app}\example.zip'),ExpandConstant('{app}\'));
end;
end;
The results:
app.exe
unins.bat
unins.exe
example.zip (and I want this zip file to be deleted after extracting)
project-folder (here I want the files inside the folders)
What I want:
app.exe
unins.bat
unins.exe
files... (files that are inside the project-folder)
Refer directly to the subfolder:
SrcFldr := shellobj.NameSpace(ZipFile + '\project-folder');

Convert {constants} returned by CurrentFilename in Inno Setup

What to do to get nice, full filename from the code below:
;bugfixes
Source: "Bugfixes\CombatGameConstants.json"; DestDir: "{code:battletechDataDir}\constants";\
Flags: uninsneveruninstall; Components: DataBugfixes; BeforeInstall: BackupFile()
and the BackupFile() procedure:
procedure BackupFile();
var fileToBackup : String;
begin
{ if backup file already exists skip creation, otherwise rename the file to file.backup }
fileToBackup := CurrentFilename(); { get destination file name }
if not FileExists(fileToBackup + '.backup') then
begin
if not RenameFile(fileToBackup, fileToBackup + '.backup') then
MsgBox('Creation backup file for ' + fileToBackup + ' failed!', mbInformation, MB_OK);
end;
end;
This does not convert {code:battletechDataDir} into full path – CurrentFileName() returns me {code:battletechDataDir}\constants\{code:battletechDataDir}\constants. So either how to convert that {code:battletechDataDir} into directory, or backup given file other way?
You can use ExpandConstant function:
fileToBackup := ExpandConstant(CurrentFilename()); { get destination file name }
Though CurrentFilename is intended for use with wildcard source. With a fixed file name, you might as well refer to the file explicitly:
fileToBackup := battletechDataDir('') + '\constants\CombatGameConstants.json';

How to get versions of files (exe's and dll's) which are included to Inno Setup installer exe file?

As in topic, is it possible? And, I want to display them on one page of installer if parameter (e.g. parameter passed to exe file) is set to true.
I know how to display some page:
if dev then
PageWersjePlikow :=
CreateOutputMsgMemoPage(
1, 'Wersje plików zawarte w instalatorze',
'Lista plików niewidoczna dla klienta',
'Pliki:', 'TU WPISAĆ WERSJE PLIKÓW');
I have some ideas, but every idea is based on .txt file built while compiling exe installer and then read from it...
Use GetVersionNumbers or GetVersionNumbersString support functions.
The GetVersionNumbersString returns version in format Major.Minor.Rev.Build.
If you need a different format, you need to use GetVersionNumbers and format the version components, the way you need (like Major.Minor.Rev):
function MyGetVersionNumbersString(
const Filename: String; var Version: String): Boolean;
var
MS, LS: Cardinal;
Major, Minor, Rev, Build: Cardinal;
begin
Result := GetVersionNumbers(Filename, MS, LS);
if Result then
begin
Major := MS shr 16;
Minor := MS and $FFFF;
Rev := LS shr 16;
Build := LS and $FFFF;
Version := Format('%d.%d.%d', [Major, Minor, Rev]);
end
end;
Thank you! I have found solution for checking cmd parameter:
function GetParam: Boolean;
var
param: string;
i: integer;
begin
Result := False;
for i:= 0 to ParamCount do
begin
param := ParamStr(i);
if (param = '-p') then
begin
Result := True;
break;
end;
end;
end;
With my function I can just run my installer with '-p' parameter and it will show my page containing information which I want :-)

Inno Setup image download

I'm working with Inno Setup Compiler (Pascal Scripting).
My form has an image object (TBitmapImage) and I'd like to supply a dynamic image obtained from the web URL. Is it possible to silently download an image (or other type of file) in Inno Setup scripting?
I would write a small Win32 program that downloads a file from the Internet, such as
program dwnld;
uses
SysUtils, Windows, WinInet;
const
PARAM_USER_AGENT = 1;
PARAM_URL = 2;
PARAM_FILE_NAME = 3;
function DownloadFile(const UserAgent, URL, FileName: string): boolean;
const
BUF_SIZE = 4096;
var
hInet, hURL: HINTERNET;
f: file;
buf: PByte;
amtc: cardinal;
amti: integer;
begin
result := false;
hInet := InternetOpen(PChar(UserAgent), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
try
hURL := InternetOpenUrl(hInet, PChar(URL), nil, 0, 0, 0);
try
GetMem(buf, BUF_SIZE);
try
FileMode := fmOpenWrite;
AssignFile(f, FileName);
try
Rewrite(f, 1);
repeat
InternetReadFile(hURL, buf, BUF_SIZE, amtc);
BlockWrite(f, buf^, amtc, amti);
until amtc = 0;
result := true;
finally
CloseFile(f);
end;
finally
FreeMem(buf);
end;
finally
InternetCloseHandle(hURL);
end;
finally
InternetCloseHandle(hInet);
end;
end;
begin
ExitCode := 0;
if ParamCount < 3 then
begin
MessageBox(0,
PChar(Format('%s: This program requires three command-line arguments.',
[ExtractFileName(ParamStr(0))])),
PChar(ExtractFileName(ParamStr(0))),
MB_ICONERROR);
Exit;
end;
if FileExists(ParamStr(PARAM_FILE_NAME)) then
DeleteFile(PChar(ParamStr(PARAM_FILE_NAME)));
if DownloadFile(ParamStr(PARAM_USER_AGENT), ParamStr(PARAM_URL),
ParamStr(PARAM_FILE_NAME)) then
ExitCode := 1;
end.
This program takes three command-line arguments: the UserAgent to be sent to the web server (can be anything, such as "MyApp Setup Utility"), the URL of the file on the Internet, and the file name of the file that is being created. Don't forget to enclose the arguments inside quotation marks ("). The exit code of the program is 0 if the download failed, and 1 if the download succeeded.
Then, in your Inno Setup script, you can do
[Files]
Source: "dwnld.exe"; DestDir: "{app}"; Flags: dontcopy
[Code]
function InitializeSetup: boolean;
var
ResultCode: integer;
begin
ExtractTemporaryFile('dwnld.exe');
if Exec(ExpandConstant('{tmp}\dwnld.exe'),
ExpandConstant('"{AppName} Setup Utility" "http://privat.rejbrand.se/sample.bmp" "{tmp}\bg.bmp"'),
'', SW_SHOWNORMAL, ewWaitUntilTerminated, ResultCode) then
if ResultCode = 1 then
(* Now you can do something with the file ExpandConstant('{tmp}\bg.bmp') *);
end;
Unfortunately, however, I know of no means by which you can change the WizardImageFile during runtime...
It's actually possible to download almost anything from the web using InnoTools Downloader.
Inno setup doesnt have any inbuilt functions for this, however, you can perform this action using batch files that do the job for you.
1) download a command line URL resource downloader like -
http://www.chami.com/free/url2file_wincon.html
Some tips on how to use it -
http://www.chami.com/tips/windows/062598W.html
2) Package it in your installer
3) create a batch file that calls url2file.exe and fetches your image into the app directory
4) Call this batch file in the initialize setup command of Inno Setup installer script.
5) Use that image wherever you want!
ps - If you are using the image in the setup, check if differed image loading is allowed or not.. i am not sure about that.
Let me know if you have any other questions

Resources