Create a hardlink with Inno Setup - installation

I have thousand of own installers that requires a critical dll file for uninstallation step, this dll file sizes about 2 mb then to avoid unnecessary disk space (2mb*100 installers) I would like to store the file once in {cf} then make a hardlink for the next installers that requires that file.
I could create a hardlink in Inno Setup without the need of external apps such as mklink.exe usage?
This is a brief example of what I have, all my installers follow the same "structure":
[Files]
; VCL Styles
Source: {tmp}\uninstall.vsf; DestDir: {app}; \
Flags: ignoreversion
Source: {tmp}\uninstall.dll; DestDir: {app}; \
Flags: ignoreversion uninsneveruninstall
; Temp files
Source: {tmp}\*; DestDir: {tmp}; Excludes: uninstall.dll, uninstall.vsf; \
Flags: recursesubdirs createallsubdirs ignoreversion
; Program
Source: {app}\*; DestDir: {app}; \
Flags: recursesubdirs createallsubdirs ignoreversion
As you could see, I'm moving the uninstall.dll to {app}, but what I would like to do is: If doesn't exists, copy the uninstall.dll file to {cf}\InnoSetup\uninstall.dll filepath and make a hardlink to {app}\uninstall.dll, if already exists the file then just make the hardlink, nothing more, I won't still store the uninstall.dll file in {app}\uninstall.dll, just I want a symbolic reference because the uninstall.dll file should never be uninstalled.
How I could do it?

Inno Setup does not support creating hardlinks natively.
I wouldn't consider the mklink an external application. It's a built-in Windows tool. So if you do not need to support Windows XP, you can safely rely on it. Or you can fallback to installing the DLL regularly, if the mklink is not available.
Or use the CreateHardLink function from the Code section.
#define MyApp "MyApp"
#define UninstallDll "uninstall.dll"
[Files]
Source: "{#UninstallDll}"; DestDir: "{cf}\{#MyApp}"; \
Flags: ignoreversion uninsneveruninstall
[Code]
function CreateHardLink(lpFileName, lpExistingFileName: string;
lpSecurityAttributes: Integer): Boolean;
external 'CreateHardLinkW#kernel32.dll stdcall';
procedure CurStepChanged(CurStep: TSetupStep);
var
ExistingFile, NewFile: string;
begin
if CurStep = ssPostInstall then
begin
ExistingFile := ExpandConstant('{cf}\{#MyApp}\{#UninstallDll}');
NewFile := ExpandConstant('{app}\{#UninstallDll}');
if CreateHardLink(NewFile, ExistingFile, 0) then
begin
Log('Hardlink created');
end
else
if FileCopy(ExistingFile, NewFile, False) then
begin
// FAT file system?
Log('Hardlink could not be created, file copied instead');
end
else
begin
MsgBox('Cannot install {#UninstallDll}', mbError, MB_OK);
end;
end;
end;
(Tested on Unicode version of Inno Setup – The only version as of Inno Setup 6)
And do not forget to delete the file when uninstalling:
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
if CurUninstallStep = usUninstall then
begin
if DeleteFile(ExpandConstant('{app}\{#UninstallDll}')) then
begin
Log('File deleted');
end
else
begin
Log('Cannot delete file');
end;
end;
end;
You can of course use also the [UninstallDelete] entry. I just like to uninstall the file using the same technology used to install it.
Your question title is "Create a hardlink with Inno Setup".
The CreateHardLink creates a hardlink. A hardlink is another reference to the same contents. Basically the hardlink is indistinguishable from the original file (even the original file is a hardlink actually). Both original file and the hardlink are just references to the same contents. If you delete the original file (or the new hardlink), you actually remove just one reference to the contents. The contents is still preserved. The contents is removed with the last reference only. The hardlink does not occupy an additional space on the disk (the contents is stored only once).
For details see Hard link article on Wikipedia.
While the mklink creates a symlink (aka symbolic link) by default. A symlink is like a shortcut, it's a reference to the original file (not contents). It's a file on its own, that contains a path to the target file. The symlink has a size of its own (occupied by the reference to the target file). If you remove the original file, the symlink still exists (because there's no reference to the symlink from the original file), but becomes invalid (the contents is gone). Again, it's similar to a shortcut.
For details see Symbolic link article on Wikipedia.
You can create a hardlink with the mklink, if you add the /H switch:
/H Creates a hard link instead of a symbolic link.
If you want to create the symlink instead of the hardlink, it's a different question (though the answer is simple, use the CreateSymbolicLink function). Though again, note that the hardlink does not occupy additional space on the disk, what seems to be your concern. So I believe you should keep using the CreateHardLink function.

Related

Inno Setup - Create folder in Public Users Documents in Windows 10

I am trying to add a folder to an installation which will eventually hold user output data. I can't put a folder into Program Files because users will not have the required permissions to write to it.
If it is not being installed to Program Files, then the data folder can be created inside the application folder (this is working fine).
I have a little piece of code to detect whether the installation was made to Program Files and, if so, I wanted to use CreateDir() to make a data folder in C:\Users\Public\Documents\{'MyAppName}\DB This seems to fail, in [Code] even though the standard Inno Setup script works:
[Dirs]
Name: "{commondocs}\{#MyAppName}\DB"
I am using the DeinitialiseSetup() procedure to make this happen at the end of installation, once the path is definite.
This is my code:
[Code]
procedure DeinitializeSetup();
begin
{ If it has been installed in the Program Files Folder put DB in Public Documents }
if Pos(ExpandConstant('{pf}'),ExpandConstant('{app}')) > 0 then
begin
if not CreateDir (ExpandConstant('{commondocs}\{#MyAppName}\DB')) then
MsgBox('Error: Data folder could not be created.', mbInformation, MB_OK);
end
else
begin
if not CreateDir (ExpandConstant('{app}\DB')) then
MsgBox('Error: Data folder could not be created.', mbCriticalError, MB_OK);
end;
end;
Following another SO suggestion I used:
PrivilegesRequired=lowest
in the script but it did not work with or without this. I am beginning to think this may be a permissions issue but am not sure why, as the installer standard [Dirs] script works fine.
This is not the same as the other questions regarding identifying the path - I have got all the paths I want, only: [Code] CreateDir() does not seem able to create a folder in {commondocs}.
Many thanks for any suggestions.
My guess is that the {commondocs}\{#MyAppName} does not exist. CreateDir function can create a single directory only. It won't create parent folders for you, if they do not exist (contrary to [Dirs] section entry).
You can use ForceDirectories function instead:
Creates all the directories along the specified directory path all at once.
Side note: Do not use DeinitializeSetup to create the directories – Is is triggered even if the installation fails, or even if the user cancels the installation.
Use CurStepChanged(ssPostInstall):
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
{ Your code }
end;
end;

How to handle path with spaces in Inno Setup?

I want to allow paths with spaces (for example program files) when installing my program with Inno Setup. However paths with spaces let my installed service crash.
The Inno Setup file looks like this:
[Setup]
AppName=Demo
DefaultDirName={pf}\demo
[Files]
Source: "bin\nssm.exe"; DestDir: "{app}"
Source: "bin\jdk1.8.0_152\jre\*"; DestDir: "{app}\jre"; Flags: recursesubdirs
Source: "build\libs\demo.jar"; DestDir: "{app}"
[Run]
Filename: "{app}\nssm.exe"; \
Parameters: "install demo ""{app}\jre\bin\java.exe"" -jar ""{app}\demo.jar"""
Filename: "{app}\nssm.exe"; Parameters: "start demo"
"nssm.exe" is a service wrapper to execute a java application as a windows service.
The critical part is this line:
Filename: "{app}\nssm.exe"; \
Parameters: "install demo ""{app}\jre\bin\java.exe"" -jar ""{app}\demo.jar"""
As suggested in this question/answer, I tried to use double double quotes, but this doesn't help, the service is still crashing. If I change DefaultDirName to a path without spaces everything works as expected.
DefaultDirName=c:\demo
How do I have to handle paths with spaces?
The problem was the combination of Inno Setup and nssm, which both are escaping double quotes with double quotes. That makes multiple double quotes necessary.
Solution:
Filename: "{app}\nssm.exe"; Parameters: "install demo ""{app}\jre\bin\java.exe"" -jar """"""{app}\demo.jar"""""""
See nssm documentation section "Quoting issues".

Error with the shortcut of vb script created by inno setup

EDIT: I have made little edit to question, describing cause of problem at last.
I have build a setup using Inno. The main file, from where execution starts, is a vbs file. I have set Inno to make shortcut in desktop with a custom icon. But after installation the shortcut gives vbs error of file missing. If i go to main vbs file and run directly or create another shortcut of that vbs file manually in desktop, I can run that shortcut any number of times. So where is the problem. Is it Inno's problem or some scripting problem.
Here's the vbs script (its aim is to start a batch file but dont show cmd window while opening the batch command)
Set WshShell = CreateObject("WScript.Shell")
WshShell.Run chr(34) & "run.bat" & Chr(34), 0
Set WshShell = Nothing
I don't know vbs and this script was lying in the Internet. So if there's some obvious problem with the script, please help me correct it.
The exact error I get is:
Script: C:\Admin\start.vbs
Line: 2
Char: 1
Error: The system cannot find the file specified.
Code: 80070002
Source: (null)
Here's the script I used in Inno
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppName=Test
AppVersion=1.0
AppVerName=Test 1.0
AppPublisher=USV
DefaultDirName=C:\Test
DisableDirPage=yes
DefaultGroupName=Test
DisableProgramGroupPage=yes
OutputDir=C:\Users\Ashu\Desktop
OutputBaseFilename=Test
SetupIconFile=C:\Test\logo2.ico
Compression=lzma
SolidCompression=yes
; "ArchitecturesAllowed=x64" specifies that Setup cannot run on
; anything but x64.
ArchitecturesAllowed=x64
; "ArchitecturesInstallIn64BitMode=x64" requests that the install be
; done in "64-bit mode" on x64, meaning it should use the native
; 64-bit Program Files directory and the 64-bit view of the registry.
ArchitecturesInstallIn64BitMode=x64
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
Source: "C:\Test\start.vbs"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\Test\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{group}\Test"; Filename: "{app}\start.vbs"; IconFilename: {app}\logo2.ico;
Name: "{group}\{cm:UninstallProgram,Test}"; Filename: "{uninstallexe}"; IconFilename: {app}\logo2.ico;
Name: "{commondesktop}\Test"; Filename: "{app}\start.vbs"; Tasks: desktopicon; IconFilename: {app}\logo2.ico;
[Run]
Filename: "{app}\importstarter.bat";
Filename: "{app}\start.vbs"; Description: "{cm:LaunchProgram,Test}"; Flags: shellexec postinstall skipifsilent
EDIT:
I have updated script that closely resembles the problem. Cause for the problem is in shortcut property, target property is set but start in property is set blank. Correcting it to desired directory solves the problem. Program runs in first attempt because setup directly runs from the main file instead of the shortcut. So this must be a problem of Inno Script.
Your problem is caused by a lack of specific working/current paths and the use of relative paths.
When the vbs file is run, the working/current directory would be that of the installer, c:\Windows\, j:\MyData\, etc. When it tries to run run.bat, it can't find it in the current directory, resulting in the "The system cannot find the file specified." error from the script engine.
The best way to fix this is to force the current directory of the running script to the folder containing it.
(I don't know how to do this off hand)
Alternatively, you can set the current directory on the shortcuts and the [Run] entry:
[Icons]
Name: "{group}\Test"; Filename: "{app}\start.vbs"; IconFilename: {app}\logo2.ico; WorkingDir: "{app}";
Name: "{group}\{cm:UninstallProgram,Test}"; Filename: "{uninstallexe}"; IconFilename: {app}\logo2.ico;
Name: "{commondesktop}\Test"; Filename: "{app}\start.vbs"; Tasks: desktopicon; IconFilename: {app}\logo2.ico; WorkingDir: "{app}";
[Run]
Filename: "{app}\importstarter.bat"; WorkingDir: "{app}";
Filename: "{app}\start.vbs"; WorkingDir: "{app}"; Description: "{cm:LaunchProgram,Test}"; Flags: shellexec postinstall skipifsilent
You can also put this at the top of the batch files so they don't need a specific current directory:
cd /d %~dp0
Note that recent (since 2010) versions of Inno Setup force a working directory to be set to protect against this error.
You need to specify full path and lose the last pointless line.
Set WshShell = CreateObject("WScript.Shell")
WshShell.Run chr(34) & "c:\somefolder\run.bat" & Chr(34), 0
Thanks everyone for your concern. The issue was with Inno Setup. I updated it to newer version and the problem was gone without making any changes to the script.
The problem was caused since the shortcut does not contain any starting point folder (thanks #Noodles). So it was unable to find file in the default directory of cmd.

running InstallUtil {app}/file.exe in inno setup

I want to copy service files to {app} directory and then use this as a parameter in InstallUtil.exe.
Here's some part of my code :
[Files]
Source: WCFService.exe; DestDir: {app}
Source: WCFService.exe.config; DestDir: {app}
[Run]
Filename: {dotnet40}\InstallUtil.exe; Parameters: {app}\WCFService.exe
This code doesn't work (but the files are copied into {app} directory correctly). However, if I use something like this :
[Files]
Source: WCFService.exe; DestDir: {src}
Source: WCFService.exe.config; DestDir: {src}
[Run]
Filename: {dotnet40}\InstallUtil.exe; Parameters: WCFService.exe
it works correctly. Does anyone know what's going on?
I have to use inno setup.
In this case you could try to set WorkingDir parameter to {app} in the [Run] section.
Like this:
[Run]
Filename: "{dotnet40}\InstallUtil.exe"; WorkingDir: "{app}"; Parameters: "WCFService.exe"
{app} may contain spaces, and so must be properly quoted when using it on command lines:
[Run]
Filename: {dotnet40}\InstallUtil.exe; Parameters: """{app}\WCFService.exe"""
The outermost set of quotes is for Inno itself; each pair of doubled quotes within that will end up putting a single quote on the command line.

Permanent customized folder icons with InnoSetup in any computer

I have modified some folder icons and I am including these folders in my InnoSetup installation. The problem is that once my program is installed, my customized folder icons are gone and what I see is just the oldfashioned "yellow" Windows folder icons.
EDIT
The answer was provided by the user TLama. It worked in my computer at first. I had some problems with different Windows versions at different computers. I will write now my working code after having tried sucessfully in several computer systems.
Icons used:
Ico1.ico
Ico2.ico
Ico3.ico
Modified folder icons:
c:\FDR1
c:\FDR2\FDR3
Step 1:
I have used the software "Folder icon changer" to have my icon in place for the three folders I wanted changed. You may use any other free software too. After execution, a desktop.ini appeared in each of the newly changed icon folders. For instance, the FDR1 has the content:
[.Shellclassinfo]
Iconfile=F:\Resource\Icons\Ico1.ico
Iconindex= 0
Step 2:
I have then erased the path above and saved "Ico1.ico" into the directory "c:\FDR1" I had just modified :
[.Shellclassinfo]
Iconfile=Ico1.ico
Iconindex= 0
I did the same for the Ico2.ico (inside the FDR2) and the Ico3.ico (inside the FDR3). The "Icon1, 2 and 3" and "desktop.ini" file attributes were all set to hidden. But, it is important NOT to set the icon properties to "read only".
Step 3:
Inside Inno repeat TLama's suggestion.
#define OutputDirectory_1 "c:\FDR1"
#define OutputDirectory_2 "c:\FDR2"
#define OutputDirectory_3 "c:\FDR2\FDR3"
[Dirs]
Name: {#OutputDirectory_1}; Attribs: system
Name: {#OutputDirectory_2}; Attribs: system
Name: {#OutputDirectory_3}; Attribs: system
[Files]
Source: "c:\FDR1\Ico1.ico"; DestDir: {#OutputDirectory_1}; Attribs: hidden system
Source: "c:\FDR2\Ico2.ico"; DestDir: {#OutputDirectory_2}; Attribs: hidden system
Source: "c:\FDR2\FDR3\Ico3.ico"; DestDir: {#OutputDirectory_3}; Attribs: hidden system
Step 4:
Compile !
Now, your folder icons will permanently work in any computer and system !!
Your target folder should have either read only or system attribute configured. To create such folder you can use, like Miral mentioned, [Dirs] section and its attributes. This will have an advantage, that after you run the installation process, InnoSetup automatically notifies Shell about changes, so the folder icon will be changed without an extra notification function call.
; this is a defined preprocessor variable used to simplify the script
; management; this variable contains the path, where the icon will be
; applied (it's used twice in a script, so it's easier to manage that
; from one place)
#define OutputDirectory "d:\TargetDirectory"
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
OutputDir=userdocs:Inno Setup Examples Output
[Files]
; here you need to use "hidden" and "system" values in Attribs parameter
; to include into the Desktop.ini file hidden and system file attributes
Source: "Desktop.ini"; DestDir: {#OutputDirectory}; Attribs: hidden system
[Dirs]
; here you need to use either "readonly" or "system" value in Attribs parameter
; to setup to the output directory read only or system file directory attribute
Name: {#OutputDirectory}; Attribs: readonly
Important:
Don't forget that you have to compile the script using CTRL + F9 before running, whenever you change the content of your input Desktop.ini file as well as when you change the value of the preprocessor path variable (I've been missing this few times and then wondering about the setup package content).
In order to activate custom folder icons you have to programmatically set the "read-only" attribute of the folder containing the desktop.ini file. (You can't do this from Explorer, but you can via the command line and from Inno.)
[Dirs]
Name: {app}; Attribs: readonly
Note that the path inside the desktop.ini file must be valid on the user's filesystem; you may want to use an [Ini] entry to create or modify this file to suit the installation path.
(This doesn't actually make the folder read-only -- this attribute is treated differently on folders by Windows because only files can meaningfully be read-only.)

Resources