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
Related
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.
I want to copy the created setup file to a network directory. I think the [Run] section will be the best position to do this, but I cannot not found any possibility to call my copy function. Any idea to this issue?
Also, is it possible to use variables declared in the [setup] section for the copy path?
My procedure will be like this (trying to use the variables):
[Code]
procedure CopyFile();
begin
FileCopy('{OutputDir}/{OutputBaseFilename}',
'V:/Service/Software/ASCDeploy/Prod/Asc.Tools.FileCopy/{OutputBaseFilename}', False);
end;
There's no way to run any code after Inno Setup finishes compiling - At least not in the original Inno Setup.
See also Inno Setup - Post Processing.
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.
I've got an installation task using InnoSetup which I'm not quite sure how to do properly.
Situation is as follows:
1 innosetup custom page with some textboxes and checkboxes. Basically containing targeturl, and a windows servicename. Might be more later.
This custom information needs to be passed on to two different app.config files.
Question is now, how to do this?
My first intention was to use the [Run] segment with a bunch of parameters such as:
[Run]
Filename: {app}\MyApp.exe; Parameters: /install; Flags: runminimized
But I don't know how to pass the custom data to the application.
Perhaps one can create some environment variables and pass the data that way?
Perhaps one can create a temporary file with necessary values?
Perhaps one should do this in a loaded dll during the installation and not post install?
Any suggestions would be very much appreciated.
and thankyou. For "#DenverCoder9":
Basically what I ended up doing, which is almost line for line in the bundled examples from Inno-setup. Missed the fact that Inno-setup allows for XML manipuation (via MSXML), which allowed me to to:
include a sample configuration file (app.config.sample).
load the sample configuration file
modify it using the collected data from the custom form.
save it to the correct location.
[Files]
...
Source: ..\UpdateService\UpdateService\Server\bin\Release\UpdateService.exe.config; DestDir: {app}; Permissions: users-modify; Flags: comparetimestamp onlyifdoesntexist; AfterInstall: MyAfterInstall;
[Code]
procedure MyAfterInstall();
var XMLDoc : Variant;
var RootNode : Variant;
begin
// if(FLAG_UPDATE_SERVICE_CONFIG) then begin
XMLDoc := CreateOleObject('MSXML2.DOMDocument');
XMLDoc.async := False;
XMLDoc.resolveExternals := False;
XMLDoc.load(ExpandConstant(CurrentFilename));
RootNode := XMLDoc.documentElement;
...
end;
end;
This has the added benefit that I have a sample file to provide for anyone who needs to modify the application by hand.
Can you not use command-line arguments to pass values entered in your Inno Setup installer to an external application?
The external application (which I assume is .NET) can handle writing the values from the command-line to configuration files (i.e. 'App.config'). The Main(string[] args) method of the .NET application can examine the command-line argument values. Use the [Run] section as you suggested and pass these values as parameters (using Parameters).
If I want to create files in the {commonappdata} folder, I need to add the Permissions:users-modify parameter on a [Files] Source:... line.
This works great ( I finally figured out how to make sure no roaming folder was used when changing one of the installed files from within the installed program ).
However , how can I make INI-entries from the [INI] Filename:... section also writeable by any user on the PC ? There are program values, not user values, after all.
As it is now, the ini-file is written by the Inno-setup installation, but if I later start the installed program and change the ini file grammatically, a roaming version of the ini file is written.
To be complete:
I know about the way to create the ini-file as a template when installing your application, and than , upon first run of the program , copy them over to the the {commonappdata} folder, but I am just wondering whether this can be achieved from within the Inno-Setup script.
I am running the latest version of Inno-Setup 5.4.2 under Windows 7 Prof 64 ( if this should make a difference ).
You'll be blamed for this design. You might have contradicting user settings if you keep them in application data folders. Refer to this question for more blame. ;)
Anyway, only [Files], [Dirs] and [Registry] sections allow 'Permissions' parameter. So it is not possible for an [Ini] section to create an ini file with modified permissions. One way to achieve your task can be to use the 'ini' section to collect necessary information during setup, then as a post-install action, transfer the contents of the ini file to one with modified permissions created through the 'files' section. Something like this:
[Files]
Source: MyProg.ini; DestDir: {commonappdata}\MyCompany; Permissions: users-modify;
;// ini file contents will be transferred to this file
[Ini]
filename: {commonappdata}\MyCompany\MyProg_Temp.ini; section: users; key: username; string: {username}; Flags: UninsDeleteEntry;
;// this is used in the installation, and will be deleted in post-install
...
[Code]
procedure CurStepChanged(CurStep: TSetupStep);
var
s: string;
begin
if CurStep = ssPostInstall then begin
if LoadStringFromFile(ExpandConstant('{commonappdata}\MyCompany\MyProg_Temp.ini'), s) and
SaveStringToFile(ExpandConstant('{commonappdata}\MyCompany\MyProg.ini'), s, False) then
DeleteFile(ExpandConstant('{commonappdata}\MyCompany\MyProg_Temp.ini'));
end;
end;
Inno Setup allows to set permissions only in sections [Files] [Dirs] and [Registry] and you'll have to use those somehow. Here are 2 solution to this problem. Both are good but each with a slight disadvantage.
Solution #1: Set permission to the entire directory
[Dirs]
Name: {commonappdata}\MyCompany; Permissions:everyone-modify
[INI]
Filename: {commonappdata}\MyCompany\MyProg.ini; Section: "SomeSection"; Key: "SomeKey"; String: "SomeValue"
This is a great solution if you don't mind to have the entire directory's permissions modified. I did mind and came up with a second solution.
Solution #2: Create your .ini file in {tmp} and copy it in the [Files] section:
#define TargetIniDir "{commonappdata}\MyCompany"
#define TargetIniName "MyProg.ini"
....
[Files]
Source: {tmp}\{#TargetIniName}; DestDir: {#TargetIniDir}; Flags:external; Permissions: users-modify;
....
[Code]
procedure PrepareIniFileForCopy(section, key, value, iniFileTemp, iniFileTarget:String);
begin
if FileExists(iniFileTarget) then
FileCopy(iniFileTarget, iniFileTemp, False);
SetIniString(section, key, value, iniFileTemp);
end;
procedure CurStepChanged(CurStep: TSetupStep);
var
iniFile, iniFileTemp:String;
begin
if CurStep=ssInstall then begin
PrepareIniFileForCopy('SomeSection', 'SomeKey', 'SomeValue', ExpandConstant('{tmp}\{#TargetIniName}'), ExpandConstant('{#TargetIniDir}\{#TargetIniName}'));
end;
end;
This will create your .ini file in the {tmp} directory (which will be deleted after install finishes), then copied in the [Files] section to the desired commondata directory with desired permissions.
Note the external flag in the [Files] section which means the file isn't packed to the setup at compile time, rather taken dynamically at installation time. Also note that the creation of the temporary file must be done before the install (CurStep=ssInstall means right before installation).
I think this solution is good, but not very pretty. You split your operation to two different places, that rely one one being done before the other.
Both solution can add values to existing .ini files, not just create new ones.