inno-setup

Do not include duplicate files multiple times in the Inno Setup installer


Can the size of my installer be reduced?

At the moment I have this code for my GoogleAuthAndSync support utility:

; Google Calendar Interface v3
Source: "Google.Apis.Auth.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "Google.Apis.Auth.PlatformServices.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "Google.Apis.Calendar.v3.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "Google.Apis.Core.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "Google.Apis.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "Google.Apis.PlatformServices.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "Google.GData.Client.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "Google.GData.Contacts.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "Google.GData.Extensions.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "GoogleAuthandSync.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "GoogleAuthandSync.exe.config"; DestDir: "{app}"; Flags: ignoreversion
Source: "Microsoft.Threading.Tasks.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "Microsoft.Threading.Tasks.Extensions.Desktop.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "Microsoft.Threading.Tasks.Extensions.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "Newtonsoft.Json.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "System.Net.Http.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "System.Net.Http.Extensions.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "System.Net.Http.Primitives.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "Zlib.Portable.dll"; DestDir: "{app}"; Flags: ignoreversion

As you can see, those files get installed into the {app} folder. The reason I did not use a wild card is because I also have language resource dll files and would not want to include them.

Then, I have my OutlookCalIFConsole utility that gets installed into a subfolder:

; Outlook Calendar Interface
Source: "OutlookCalIFConsole\*.dll"; DestDir: "{app}\OutlookCalIFConsole"; Flags: ignoreversion
Source: "OutlookCalIFConsole\OutlookCalIFConsole.exe"; DestDir: "{app}\OutlookCalIFConsole"; Flags: ignoreversion sign
Source: "OutlookCalIFConsole\OutlookCalIFConsole.exe.config"; DestDir: "{app}\OutlookCalIFConsole"; Flags: ignoreversion

I have just updated all of my Nuget Packages on both projects and this is the results for the DLL files:

D:\My Programs\2017\GoogleAuthandSync\GoogleAuthandSync\bin\Release>dir *.dll

26/04/2016  12:16         2,236,416 BouncyCastle.Crypto.dll
08/02/2018  11:32           105,472 Google.Apis.Auth.dll
08/02/2018  11:32             5,120 Google.Apis.Auth.PlatformServices.dll
26/03/2018  08:38            89,600 Google.Apis.Calendar.v3.dll
08/02/2018  11:32            67,072 Google.Apis.Core.dll
08/02/2018  11:32            71,680 Google.Apis.dll
08/02/2018  11:32             4,096 Google.Apis.PlatformServices.dll
25/06/2013  11:24           212,992 Google.GData.Client.dll
25/06/2013  11:33            29,184 Google.GData.Contacts.dll
25/06/2013  11:27            90,112 Google.GData.Extensions.dll
08/03/2017  20:26           276,480 log4net.dll
24/03/2018  18:44           662,528 Newtonsoft.Json.dll
13/08/2013  01:19            27,296 System.Data.DataSetExtensions.dll
13/08/2013  01:19         1,172,568 System.Data.dll
13/08/2013  01:19            63,064 System.Net.dll
05/09/2017  14:54           197,984 System.Net.Http.dll
19/02/2015  21:10            22,232 System.Net.Http.Extensions.dll
19/02/2015  21:10            21,720 System.Net.Http.Primitives.dll
14/10/2017  18:34            39,848 System.Security.Cryptography.Algorithms.dll
05/11/2016  05:57            23,480 System.Security.Cryptography.Encoding.dll
05/11/2016  05:57            22,816 System.Security.Cryptography.Primitives.dll
14/10/2017  18:34            38,848 System.Security.Cryptography.X509Certificates.dll
13/08/2013  01:19           921,688 System.XML.dll
13/08/2013  01:19            43,112 System.Xml.Linq.dll
22/06/2015  09:00            81,920 Zlib.Portable.dll

D:\My Programs\2017\OutlookCalIFConsole\OutlookCalIFConsole\bin\Release>dir *.dll

11/01/2018  01:05           185,856 CommandLine.dll
23/03/2018  15:10            63,488 Microsoft.Graph.Core.dll
26/03/2018  11:13         4,714,520 Microsoft.Graph.dll
08/05/2017  17:49           226,528 Microsoft.Identity.Client.dll
24/03/2018  18:44           662,528 Newtonsoft.Json.dll
05/11/2016  05:55            34,496 System.Console.dll
05/11/2016  05:56            22,184 System.IO.dll
05/09/2017  14:54           197,984 System.Net.Http.dll
05/11/2016  05:56            22,728 System.Reflection.dll
19/07/2017  10:01            29,440 System.Reflection.TypeExtensions.dll
05/11/2016  05:57            29,880 System.Runtime.dll
05/11/2016  05:56            33,000 System.Runtime.Extensions.dll
14/10/2017  18:34            39,848 System.Security.Cryptography.Algorithms.dll
05/11/2016  05:57            23,480 System.Security.Cryptography.Encoding.dll
05/11/2016  05:57            22,816 System.Security.Cryptography.Primitives.dll
14/10/2017  18:34            38,848 System.Security.Cryptography.X509Certificates.dll

Many of the DLL files are now the same date etc. So, is there a way of adding the affected files only once into the installer and deploying out into both locations?

I don't want to overcomplicate things or cause myself a maintenance issue.


Note that I also set SourceDir directive:

[ISPP]
#define SourceDir "..\Meeting Schedule Assistant\Release"
[Setup]
SourceDir={#SourceDir}

Solution

  • You can use preprocessor to iterate the DLL files in the subfolder and compare them against files in the base folder. If they match, use the file in the base folder as a source. Inno Setup will compile a source file only once to the installer, when used multiple times.

    The code is somewhat complicated by your use of SourceDir directive (of which the preprocessor is obviously not aware). Without the directive, the code would be simpler.

    [Files]
    #pragma parseroption -p-
    
    #define FileEntry(Source, DestPath) \
        "Source: " + Source + "; DestDir: " + DestPath + "\n"
    
    #define GetFileTimestamp(Path) GetFileDateTimeString(Path, 'dd/mm/yyyy hh:nn:ss', '-', ':')
    
    #define ProcessFile(RootPath, Path, AlternativePath, DestPath, FindResult, FindHandle) \
        FindResult ? \
            Local[0] = FindGetFileName(FindHandle), \
            Local[1] = AddBackslash(Path) + Local[0], \
            Local[2] = AddBackslash(AlternativePath) + Local[0], \
            Local[3] = AddBackslash(RootPath) + Local[1], \
            Local[4] = AddBackslash(RootPath) + Local[2], \
            Local[5] = \
               FileExists(Local[4]) && \
               (GetFileTimestamp(Local[3]) == GetFileTimestamp(Local[4])), \
            FileEntry((Local[5] ? Local[2] : Local[1]), DestPath) + \
            ProcessFile(RootPath, Path, AlternativePath, DestPath, \
                FindNext(FindHandle), FindHandle) \
        : ""
    
    #define ProcessFolderWithAlternativeSource(RootPath, Path, AlternativePath, DestPath) \
        Local[0] = FindFirst(AddBackslash(AddBackslash(RootPath) + Path) + "*.dll", 0), \
        ProcessFile(RootPath, Path, AlternativePath, DestPath, Local[0], Local[0])
    
    #pragma parseroption -p+
    
    #emit ProcessFolderWithAlternativeSource( \
        "..\Meeting Schedule Assistant\Release", "OutlookCalIFConsole", ".", \
        "{app}\OutlookCalIFConsole")
    

    Or you can use SetupSetting to read the SourceDir directive:

    #emit ProcessFolderWithAlternativeSource( \
        SetupSetting("SourceDir"), "OutlookCalIFConsole", ".", "{app}\OutlookCalIFConsole")
    

    If you add SaveToFile to the end of the script:

    #expr SaveToFile(AddBackslash(SourcePath) + "Preprocessed.iss")
    

    ... then you should see something like this, in the Preprocessed.iss:

    [Files]
    Source: OutlookCalIFConsole\CommandLine.dll; Dest: {app}\OutlookCalIFConsole
    Source: OutlookCalIFConsole\Microsoft.Graph.Core.dll; Dest: {app}\OutlookCalIFConsole
    Source: OutlookCalIFConsole\Microsoft.Graph.dll; Dest: {app}\OutlookCalIFConsole
    Source: OutlookCalIFConsole\Microsoft.Identity.Client.dll; Dest: {app}\OutlookCalIFConsole
    Source: .\Newtonsoft.Json.dll; Dest: {app}\OutlookCalIFConsole
    Source: OutlookCalIFConsole\System.Console.dll; Dest: {app}\OutlookCalIFConsole
    Source: OutlookCalIFConsole\System.IO.dll; Dest: {app}\OutlookCalIFConsole
    Source: .\System.Net.Http.dll; Dest: {app}\OutlookCalIFConsole
    Source: OutlookCalIFConsole\System.Reflection.dll; Dest: {app}\OutlookCalIFConsole
    Source: OutlookCalIFConsole\System.Reflection.TypeExtensions.dll; Dest: {app}\OutlookCalIFConsole
    Source: OutlookCalIFConsole\System.Runtime.dll; Dest: {app}\OutlookCalIFConsole
    Source: OutlookCalIFConsole\System.Runtime.Extensions.dll; Dest: {app}\OutlookCalIFConsole
    Source: .\System.Security.Cryptography.Algorithms.dll; Dest: {app}\OutlookCalIFConsole
    Source: .\System.Security.Cryptography.Encoding.dll; Dest: {app}\OutlookCalIFConsole
    Source: .\System.Security.Cryptography.Primitives.dll; Dest: {app}\OutlookCalIFConsole
    Source: .\System.Security.Cryptography.X509Certificates.dll; Dest: {app}\OutlookCalIFConsole
    

    Note the entries for Newtonsoft.Json.dll, System.Net.Http.dll and System.Security.Cryptography.*.


    Instead of comparing the files by GetFileDateTimeString (indirectly via GetFileDateTimeString), you can also use GetVersionNumbersString (as the files are DLLs).

    If you need additional parameters in the [Files] section entries, modify FileEntry macro accordingly.