Read contents of a file from within Inno Setup installer - inno-setup

I am using Inno Setup. I read from file with
LoadStringsFromFile(My_file, Lines)
but the file is outside installation file defined with
OutputBaseFilename={#MySetupExeName}
Can I open a file inside iss code, and this file to be in installation file (OutputBaseFilename)?

Use ExtractTemporaryFile:
[Files]
Source: myfile.txt; Flags: dontcopy
[Code]
...
ExtractTemporaryFile('myfile.txt');
LoadStringsFromFile(ExpandConstant('{tmp}\myfile.txt'), Lines);
...
Though as the contents is fixed, you can as well hard-code it.
Or read it from the file on compile-time, instead of having to extract the file on install-time. You can use preprocessor FileRead function for that. Though it's more complicated than the straightforward code above. We would have to know more about that you need the contents for and how it looks like to offer an efficient solution.

Related

Read text file line by line in Inno Setup

I'm trying to read a text file line by line using Inno Setup.
I have tried this one that mentioned here: https://jrsoftware.org/ispphelp/index.php?topic=fileread
function ShowLines(): Boolean;
var
list: Integer;
begin
list := FileOpen(ExpandConstant('{tmp}\file.txt'));
if !FileEof(list) then begin
try
repeat
MsgBox(FileRead(list), mbInformation, MB_OK);
until !FileEof(list);
finally
FileClose(list);
end;
end;
Result := True;
end;
But it will give error, on FileOpen (and maybe on other File functions) that it is an unknown identifier. Where is the problem?
The file is less than 50kb.
All the functions you are trying to call from Pascal Script are actually preprocessor functions. The Pascal Script does not have built-in function that can read file by line (or any kind of chunk).
You can implement that using WinAPI file functions, like CreateFile and ReadFile.
But if the file is not too big, you can simply use built-in function LoadStringsFromFile. For an example, see Read strings from file and give option to choose installation.
Similar question: "Unknown Identifier 'FileOpen'" when trying to detect locked file in Inno Setup code.
Seeing that you are reading the file from {tmp}, chances are that you are actually reading a temporary file extracted from the installer itself. If that's the case, it means that you have the file available on compile time already. In such case, you can indeed use the preprocessor function to read the file on compile-time.
But that needs code in completely different language/syntax. Some examples:
How to use wildcards in [CustomMessages] section of Inno Setup?
Can I use .isl files for the messages with preprocessor directives in Inno Setup?

Inno Setup Call AfterInstall for each external File

I am really struggling with this one. I have an entry in the files section of an Inno Setup (5.5.9) configuration file, that is something like the following:
[Code]
procedure showMsgBoxOfFile;
begin
MsgBox(ExpandConstant(CurrentFilename), mbInformation, MB_OK);
end;
[Files]
Source: {src}\Some\Path\myFile*Pattern.ext; DestDir: {app}; Flags: external; \
AfterInstall: showMsgBoxOfFile;
When I run the installer generated by running the above script, I get a single message box with the {app} directory, even though four files are copied. This seems to be in direct contradiction of the Inno Setup documentation on BeforeInstall/AfterInstall, which states:
A BeforeInstall or AfterInstall function for a [Files] section entry using a wildcard is called once per file matching the wildcard. Use CurrentFileName to check for which file the function is called.
Further, another question on Stack Overflow is from a user who wanted to do the exact opposite (get only one notification when multiple files were changed). The accepted answer to that question indicates that:
there is no way to call [AfterInstall] once after all the files are installed
I noticed that if I remove the "external" flag from my file copy, I DO get one message box per file. Is there a way to get a one notification per copied file when the "external" flag is specified? Does anyone know if there is a plan to document this difference in the Inno Setup help files?
Indeed, for entries with the external flag, the BeforeInstall and AnswerInstall functions are called only once.
What you can do, is to copy the files programmatically. It would give you a full control over, what you do for each file.
See Inno Setup: copy folder, subfolders and files recursively in Code section

How to get the file name of the current Inno Setup script file?

I want to obtain the name of the current Inno Setup script file in order to name the generated setup package after it. For example if the current Inno Setup script file is "MyAppSetup.iss", I want the generated setup file to be "MyAppSetup.exe".
The name of the generated setup file is controlled by the OutputBaseFilename declaration in the [Setup] section of Inno Setup, so if there's an Inno Setup preprocessor variable that returns the name of the current script file that would be great. Something like:
OutputBaseFilename={#SourceFileName}.exe
Unfortunately, there isn't an Inno Setup preprocessor variable {#SourceFileName}. I know about the {#SourcePath} variable, but it returns the directory path of the script file without it's file name. Here's a list with some predefined Inno Setup preprocessor variables, but none of them seems to return the name of the current script file. I hoped the __FILE__ variable would work after reading the descriptions of the variables in the list, but it returns an empty string.
It's not possible. There's the __FILE__, but it has a value for #included files only.
If you never have more than one .iss file in a directory, you can use the FindFirst to find its name.
#define ScriptFindHandle = FindFirst(SourcePath + "\*.iss", 0)
#if !ScriptFindHandle
#error "No script found"
#endif
#define SourceFileName = FindGetFileName(ScriptFindHandle)
#if FindNext(ScriptFindHandle)
#error "More than one script found"
#endif
#expr FindClose(ScriptFindHandle)
#define SourceBaseName = RemoveFileExt(SourceFileName)
Then to name the setup file after the current script file in the [Setup] section you should use:
[Setup]
OutputBaseFilename={#SourceBaseName}
But if you are automating compilation of a large number of installers, I assume you use a command-line compilation. So then you can simply pass a script name in a parent batch file (or a similar script):
set SCRIPT=MyAppSetup
"C:\Program Files (x86)\Inno Setup 5\ISCC.exe" %SCRIPT%.iss /DBaseName=%SCRIPT%
Use the BaseName like:
[Setup]
OutputBaseFilename={#BaseName}
I know this is not exactly what you want, but why can't you do this:
[ISPP]
#define ScriptFileName "MyAppSetup"
[Setup]
AppPublisher={#AppPublisher}
OutputBaseFilename={#ScriptFileName}Setup
Then all you need to do it edit the one reference at the top of your file.
Update
I came across this link where it states:
You can use:
#expr SetSetupSetting("OutputBaseFilename", sFileName)
However, the difficult part is automatically finding the file name.
You could use the following ISPP functions to do additional
compile-time tasks that can't be done by ISPP built-in functions:
Exec function: Executes an external program to do the additional
functionality, and writes the result to an INI file. By default Exec
waits for the process to finish.
ReadIni function: Reads the result
from the INI file, and incorporates it into the script.
How do you determine the file name is up to you. Perhaps you could
enumerate the windows and extract the file name from Inno Setup
window title, but because you may be having multiple Inno Setup
copies open, you must find a reliable way to do it. Inno adds
[Compiling] to the title during compilation which makes it easier to
find which copy is being used, but there could be multiple copies in
compiling state. You can be absolutely sure about which copy is
running your program by checking the process ID of the parent
process, you can get that by using Process32First/Process32Next and
checking the 32ParentProcessID for your process. Too much work, I
know..
But there was another comment:
(If you're doing an automated build, though, you can set the output
filename from the command line -- that might be sufficient for what
you actually want.)
So have you considered using a batch file and the command line? Then you can use the benefits of batch lines with your compiling. Information is provided here about using the current file name in batch files.
I personally think that the batch file compilation is the way to go.

inno-setup update file in all subdirectories where it exists

I'm trying to create an installer that will update a single csv file, that exists in various locations depending on which of our applications a user has installed.
Our applications install inside a folder with our Company Name in Program Files.
So, Company > App1, Company > App2, Company > App3, etc. When the csv file exists, it is directly inside the App folder.
I've tried:
[Files]
Source: "file.csv"; DestDir: "{pf}\Company\*";
With various flags and functions to no avail
flag: onlyifdestfileexists
Skips everything because it doesn't exist
Check: FileExists('file.csv')
Does nothing at all
Check: FileExistsWildcard('file.csv')
Which calls a function using FindFirst() tries to create a new directory called * to install the file in, rather than overwriting the file in the directory where it was found, as does
[Files]
Source: "file.csv"; DestDir: "{pf}\Company\"; Check: FileExistsWildcard('*\file.csv')
The problem seems to have to do with the wildcard * not doing anything when used with DestDir. Instead of searching through all subfolders, it just looks for a subfolder named "*". The recursesubdirs flag of course works for iterating over subdirectories for Source, but there is no equivalent for DestDir.
Wildcards like that are not supported in DestDir.
If you know the names of all possible subdirectories up front (which presumably is the case as these are your own applications) then the simplest way to do this is to include multiple [Files] entries, one for each possible application. You can then use the onlyifdestfileexists flag. Note that when using the same Source path on multiple entries, only one copy of the file will be stored inside the installer, so it won't bloat the size.
If your applications support being installed into different locations (as most do) then you'll need to modify this slightly; instead of hard-coding the DestDir you'll need to have each entry look something like this:
Source: file.csv; DestDir: {code:FindAppPath|App1}; Flags: onlyifdestfileexists
with corresponding [Code] function:
function FindAppPath(AppName: String): String;
In this function, use RegQueryStringValue or other methods to look up the currently installed location of the application being searched for (via AppName, which can either be an arbitrary string of your choosing or could be part of the registry path being looked up; alternately you could write separate functions for each application if that's easier). If a given application is not installed, then return '', which will then skip installing the file due to the onlyifdestfileexists flag. Note that you will still need one entry per application, so you will need to know in advance the maximum number of possibly installed applications.
(An alternate means of skipping installation if you didn't wish to use this flag is to define a Check function eg. Check: IsAppInstalled('App1') or Check: IsApp1Installed.)
If you don't know (or don't want to rely on knowing) the maximum possible number of installed applications up front, then the only solution is to fall back to pure [Code]. You will need to have a single [Files] entry like so:
Source: file.csv; DestDir: {tmp}; AfterInstall: CopyToApplications
And then implement the CopyToApplications procedure to locate the paths to all the possible installations (again, probably via RegQueryStringValue) and then for each one:
FileCopy(ExpandConstant('{tmp}\file.csv'), AddBackslash(PathToApp) + 'file.csv');
You may also want to look at the CodeDlg example script and use CreateOutputProgressPage to provide feedback to the user while this process is going on, although it may not be necessarily if the file size is small enough.
Note that in all cases you probably want to disable uninstallation support (since each app's own individual uninstaller will take care of it); make sure you have Uninstallable=no in the [Setup] section.

Get the path specified in the source of file section

Is there any way by which we can get the path of the source file in [file] section be made available in [code] section. I need to have the full path as been given in the source. I need to check it with the content of a file and if the path is present in the file, then only i need to copy that particular file. I am using Check: in the file section and need to have the whole path of file made available in code section for comparison.
To get the chosen install folder from pascal script, you can use either ExpandConstant('{app}') or WizardDirValue(). Note that I don't think the returned path contains a trailing backslash.
This would simply check a file existence:
function IsMyFilePresent: Boolean;
begin
Result:=FileExists(ExpandConstant('{app}\filename.ext'));
end;
If it's an ini file, you can use this code to retrieve the data of certain keys inside it:
(example using WizardDirValue())
inifile:=WizardDirValue()+'\filename.ext';
MyString:=GetIniString('SectionName', 'KeyName', 'DefaultValue', inifile);
Probably the CurrentFileName() function that:
Returns the destination name of the [Files] entry that is currently being processed.
You can probably work out the source from this. I'm not sure how it handles wildcards though (but I suspect it just returns "blah/*"

Resources