.netwpfwinformswindows-installerinno-setup

How to bundle multiple installers into a single installer?


I have multiple unrelated WPF and Winforms applications that I need to give to users.

Typically, there is one installer per application. However, they want a just a single file they can run to install every app in the appropriate location. So is there anyway to combine multiple installers into a single installer?

I want to be able to specify a root folder for everything to install under, but multiple sub folders will exist to split the apps up into their "type".

Ex: Say the I was deliver three apps: Spotify, VLC Player, and Google Chrome. I need a single installer that installs these into a user specified folder (let's say MainApplications). So when finished it would look like:

What I am currently trying: Going through the typical publish steps in Visual Studio for each project -> putting each installer in the same folder -> executing a file that runs each installer silently.


Solution

  • It is possible and actually we are delivering four our software setups in one installer suite. I am personally using InnoSetup

    To do that, following strategy is used;

    1. Create setup for each Software with unique GUIDs
    2. Collect the all setups files.
    3. Create another installer that manages all setup.exe files for you. You will install them silently as component
    4. Unistall: User can uninstall all sofware via using main setup (for that you need to write a small script to call uninstall commands for each software) or user can uninstall each software individually

    Edit: For point 3;

    you can basically use following code managing your setup.exe files.

    #define ApplicationAInstaller  "ApplicationASetup.exe"
    #define ApplicationA_Component_Description "ComponentName 1.X.X"
    #define ApplicationA_GUID "{6B72B9EC-B06C-4058-C704-7AD0F752C7FC}" ;Comes from for each sub installer
    #define ApplicationA_Min_Version "1.4"
    
    
    [Types]
    Name: "full"; Description: "Full installation"
    Name: "custom"; Description: "Custom installation"; Flags: iscustom
    
    
    [Components]
    Name: "applicationA"; Description: "{#ApplicationA_Component_Description}";  Types: full custom; ExtraDiskSpaceRequired: 215300000 
    Name: "applicationB"; Description: "{#ApplicationB_Component_Description}";  Types: full custom; ExtraDiskSpaceRequired: 238000000 
    
    
    [Files]
    Source: "Components\{#ApplicationAInstaller}"; DestDir: {tmp}; Flags: deleteafterinstall nocompression; Components:applicationA ; AfterInstall: InstallApplicationA; Check: NeedsToInstallApplicationA
    Source: "Components\{#ApplicationBInstaller}"; DestDir: {tmp}; Flags: deleteafterinstall nocompression; Components:applicationB ; AfterInstall: InstallApplicationB; Check: NeedsToInstallApplicationB
    
    
    
    [Code]
    function GetApplicationA_REG_KEY(): String;
    begin
      Result := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\{#ApplicationA_GUID}_is1';
      if not RegKeyExists(HKEY_LOCAL_MACHINE, Result) then
      begin
        Result := 'Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{#ApplicationA_GUID}_is1';
      end;
    end;
    
    
    function NeedsToInstallApplicationA: Boolean;
    var
      regKey           : string; 
      current_version  : string; 
      required_version : string;
    begin
      Result           := True;
      required_version := '{#ApplicationA_Min_Version}';
      regKey           := GetApplicationA_REG_KEY();
      current_version  := GetDisplayVersion(regKey);
    
      Result := required_version <> current_version;
    end;
    
    
    
    procedure SilentInstall(regKey: String; requiredVersion: String; appName: String; appRootDir: String ; appInstaller: string);
    var
      StatusText       : string;
      CurrentFileText  : string;
      ResultCode       : Integer;
      params           : string; 
      dir              : string;
      current_version  : string;
    begin
    
      StatusText                     := WizardForm.StatusLabel.Caption;
      CurrentFileText                := WizardForm.FilenameLabel.Caption
      WizardForm.ProgressGauge.Style := npbstMarquee;
    
      try
        current_version                := GetDisplayVersion(regKey);
        WizardForm.StatusLabel.Caption := 'Installing ' + appName + ' : ' + requiredVersion;
    
      if (current_version <> '-1') and (requiredVersion <> current_version) then
      begin
        WizardForm.StatusLabel.Caption := 'Updating ' + appName + ' to the latest version : ' + requiredVersion;
        UninstallUsingReg(regKey, true);
      end;
      
     { some custom messages for some applications}
      if ExpandConstant('{#ApplicationAName}') = appName then
      begin
        WizardForm.FilenameLabel.Caption := 'This may take a few minutes, please wait ...'
      end;
        
      dir    := '"'+ExpandConstant('{app}') + '\' + appRootDir + '"';
      params := '/norestart /VERYSILENT'+' /DIR=' + dir;
    
      if not Exec(ExpandConstant('{tmp}\' + appInstaller), params, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
      begin
        { you can interact with the user that the installation failed }
        MsgBox(appName+' failed with code: ' + IntToStr(ResultCode) + '.', mbError, MB_OK);
      end;
      finally
        WizardForm.StatusLabel.Caption   := StatusText;
        WizardForm.ProgressGauge.Style   := npbstNormal;
        WizardForm.FilenameLabel.Caption := CurrentFileText;
      end;
    end;
    
    
    procedure InstallApplicationA;
    var
      regKey : string;
    begin
      regKey := GetApplicationA_REG_KEY();
      SilentInstall(regKey, '{#ApplicationA_Min_Version}', '{#ApplicationA_AppName}' , '{#ApplicationA_Install_DIR}', '{#ApplicationAInstaller}'); 
    end;