inno-setuppascalprerequisites

How to handle DotNet prerequisite with Inno Setup Install / DswinDs system?


I have an understanding now of how to use this DswinsHs for downloading a file (as we have it working for my help documentation).

But now I need to migrate some old code that optionally downloaded and installed Dot Net Framework.

Old Code

I have this code (used ISTool DLL):

const
  // Changed to 4.6.2 download link (see: http://msdn.microsoft.com/en-us/library/ee942965%28v=vs.110%29.aspx#redist)
  dotnetRedistURL = 'http://go.microsoft.com/fwlink/?LinkId=780600';

function PrepareToInstall(var NeedsRestart: Boolean): String;
var
 IsInstalled: Cardinal;
begin
  Result := '';
  dotNetNeeded := true;

  // Check for required netfx installation
  // http://msdn.microsoft.com/en-us/library/hh925568%28v=vs.110%29.aspx#net_b
  if(Is64BitInstallMode()) then begin
    if (RegValueExists(HKLM, 'SOFTWARE\Wow6432Node\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release')) then begin
      RegQueryDWordValue(HKLM, 'SOFTWARE\Wow6432Node\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', IsInstalled);
      if(IsInstalled >= 378675) then begin
        dotNetNeeded := false;
        downloadNeeded := false;
      end;
    end;
  end
  else begin
    if (RegValueExists(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release')) then begin
      RegQueryDWordValue(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', IsInstalled);
      if(IsInstalled >= 378675) then begin
        dotNetNeeded := false;
        downloadNeeded := false;
      end;
    end;
  end;

  if(dotNetNeeded) then begin
    if (not IsAdminLoggedOn()) then begin
      Result := ExpandConstant('{cm:DotNet_NeedAdminRights}');
    end
    else begin
      dotnetRedistPath := ExpandConstant('{src}\NDP451-KB2858728-x86-x64-AllOS-ENU.exe');
      if not FileExists(dotnetRedistPath) then begin
        dotnetRedistPath := ExpandConstant('{tmp}\NDP451-KB2858728-x86-x64-AllOS-ENU.exe');
        if not FileExists(dotnetRedistPath) then begin
          isxdl_AddFile(dotnetRedistURL, dotnetRedistPath);
          downloadNeeded := true;
        end;
      end;

      if (downloadNeeded) then begin
        if (MsgBox(ExpandConstant('{cm:DotNet_NeedToDownload}'), mbConfirmation, MB_OKCANCEL) = IDCANCEL) then begin
          Result := ExpandConstant('{cm:DotNet_InstallAborted}');
        end;
      end;
    end;
  end;

  // AJT v19.0.0 We always delete the existing local help file if it exists.
  // The new version will be downloaded on the next wizard form if
  // the user still wants the local help. ("downloadhelp" task selected).
  if (bDownloadHelpDocSetup) then DoDeleteFile(ExpandConstant('{app}\CommunityTalks.chm'));

 end;

Then I have:

procedure CurStepChanged(CurStep: TSetupStep);
var
  hWnd: Integer;
  ResultCode: Integer;
begin
  if (CurStep = ssInstall) then
  begin
    hWnd := StrToInt(ExpandConstant('{wizardhwnd}'));

    // Don't try to init isxdl if it's not needed because it will error on < ie 3
    if (downloadNeeded) then begin
      isxdl_SetOption('label', ExpandConstant('{cm:Downloading}'));
      isxdl_SetOption('description', ExpandConstant('{cm:DownloadingInfo}'));
      if (isxdl_DownloadFiles(hWnd) = 1) then begin
        if (dotNetNeeded = true) then begin
          if Exec(ExpandConstant(dotnetRedistPath), '/quiet', '',
                 SW_SHOW, ewWaitUntilTerminated, ResultCode) then begin
            // handle success if necessary; ResultCode contains the exit code
            if not (ResultCode = 0) then begin
              // Microsoft present an array of options for this. But since
              // The interface was visible I think it is safe to just say
              // that the installation was not completed.
              MsgBox(ExpandConstant('{cm:DotNet_InstallFailed}'), mbInformation, MB_OK);
              Abort();
            end;
          end
          else begin
            // The execution failed for some reason
            MsgBox(SysErrorMessage(ResultCode), mbInformation, MB_OK);
            Abort();
          end;
        end;
      end
      else begin
        // The user most likely cancelled the download of the file
        MsgBox(ExpandConstant('{cm:DotNet_DownloadFailed}'), mbInformation, MB_OK);
        Abort();
      end;
    end;
    end;
end;

That needs changing.


How it is done with DswinsHs

For my download that does work with DwinsHs I basically have two bits as follows:

    ; AJT v19.0.0 Download Help Documentation Setup file.
    ; This is associated with the "downloadhelp" task.
    ; It will be downloaded from the internet and deleted after install.
    Source: "{tmp}\HelpDocSetup.exe"; \
        DestDir: "{app}"; \
        Flags: external deleteafterinstall; \
        Tasks: downloadhelp; \
        Check: DwinsHs_Check( ExpandConstant('{tmp}\HelpDocSetup.exe'), '{#HelpDocSetupURL}', \
                'My_Setup', 'Get', {#HelpDocSetupFileSize}, 0 )
    ; AJT v19.0.0 Installed the downloaded help documentation.
    ; This is only done if the "downloadhelp" task was selected.
    Filename: "{app}\HelpDocSetup.exe"; \
        Parameters: "/SP- /VERYSILENT /InstallPath=""{app}"""; \
        WorkingDir: "{app}"; \
        Flags: waituntilterminated runhidden; \
        Description: "{cm:InstallingHelpDescription}"; \
        StatusMsg: "{cm:InstallingHelpStatusMessage}"; \
        Tasks: downloadhelp

The issue

I need to convert my previous code (DotNet prerequisite) into suitable file / run script lines (except this time I have to pass 0 for the file size as I do not know the size).

In short, my setup requires admin privileges, and technically, we need it to download and install dotnet (if it is not there) before continuing with the setup. The reason being we have these run entries:

Filename: "{dotnet40}\regasm.exe"; \
    Parameters: "PTSTools_x86.dll /codebase"; \
    WorkingDir: "{app}"; \
    Flags: runhidden

Filename: "{dotnet4064}\regasm.exe"; \
    Parameters: "PTSTools_x64.dll /codebase"; \
    WorkingDir: "{app}"; \
    Flags: runhidden; \
    Check: IsWin64

Filename: "{dotnet40}\regasm.exe"; \
    Parameters: "/u PTSTools_x86.dll"; \
    WorkingDir: "{app}"; \
    Flags: runhidden; \
    Check: FileExists(ExpandConstant('{app}\PTSTools.dll')); \
    AfterInstall: DoDeleteFile(ExpandConstant('{app}\PTSTools.dll'))

Filename: "{dotnet4064}\regasm.exe"; \
    Parameters: "/u PTSTools.dll"; \
    WorkingDir: "{app}"; \
    Flags: runhidden; \
    Check: IsWin64 and FileExists(ExpandConstant('{app}\PTSTools.dll')); \
    AfterInstall: DoDeleteFile(ExpandConstant('{app}\PTSTools.dll'))

So having DotNet is a prerequisite for the installer to work. Should I be dealing with this differently?


Am I Doing This Right?

Based on the answer provided and my understanding of the documentation ...

Step 1

In PrepareToInstall we check to see if DotNet is needed and cache the result:

function PrepareToInstall(var NeedsRestart: Boolean): String;
var
 IsInstalled: Cardinal;
begin
  Result := '';
  dotNetNeeded := true;

  // Check for required netfx installation
  // http://msdn.microsoft.com/en-us/library/hh925568%28v=vs.110%29.aspx#net_b
  if(Is64BitInstallMode()) then begin
    if (RegValueExists(HKLM, 'SOFTWARE\Wow6432Node\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release')) then begin
      RegQueryDWordValue(HKLM, 'SOFTWARE\Wow6432Node\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', IsInstalled);
      if(IsInstalled >= 378675) then begin
        dotNetNeeded := false;
      end;
    end;
  end
  else begin
    if (RegValueExists(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release')) then begin
      RegQueryDWordValue(HKLM, 'SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', IsInstalled);
      if(IsInstalled >= 378675) then begin
        dotNetNeeded := false;
      end;
    end;
  end;

  if(dotNetNeeded) then begin
    if (MsgBox(ExpandConstant('{cm:DotNet_NeedToDownload}'), mbConfirmation, MB_OKCANCEL) = IDCANCEL) then begin
      Result := ExpandConstant('{cm:DotNet_InstallAborted}');
    end;
  end;

 end;

Step 2

We add a BeforeDownload handler. This is where we get a chance to add the file we need to download to the list:

function BeforeDownload(): Boolean;
 begin
  if(dotNetNeeded) then
  begin
    dotNetRedistPath := ExpandConstant('{tmp}\NDP451-KB2858728-x86-x64-AllOS-ENU.exe');
    DwinsHs_AppendRemoteFile( dotNetRedistPath, \
                  dotnetRedistURL, 'My_Setup', rmGet, FILESIZE_QUERY_SERVER );
  end;

  Result := True;
end;

Step 3

We add a AfterDownload handler. This is where we perform the install of DotNet.

procedure AfterDownload(State: Integer);
var
  hWnd: Integer;
  ResultCode: Integer;
begin
  if (State = READ_OK) then
  begin
    if(dotNetNeeded) then
    begin
      if Exec(ExpandConstant(dotnetRedistPath), '/quiet', '',
          SW_SHOW, ewWaitUntilTerminated, ResultCode) then begin
        // handle success if necessary; ResultCode contains the exit code
        if not (ResultCode = 0) then begin
          // Microsoft present an array of options for this. But since
          // The interface was visible I think it is safe to just say
          // that the installation was not completed.
          MsgBox(ExpandConstant('{cm:DotNet_InstallFailed}'), mbInformation, MB_OK);
          Abort();
        end;
      end
      else begin
        // The execution failed for some reason
        MsgBox(SysErrorMessage(ResultCode), mbInformation, MB_OK);
        Abort();
      end;
    end;
  end;
end;

I am not sure if "quiet" is the right way to go now ...

Step 4

We adjust the CurPageChanged handler:

procedure CurPageChanged(CurPage: Integer);
begin
  DwinsHs_CurPageChanged(CurPage, @BeforeDownload, @AfterDownload);
end;

Solution

  • Just call DwinsHs_AppendRemoteFile when dotNetNeeded.

    DwinsHs_AppendRemoteFile has basically the same arguments as DwinsHs_Check (DwinsHs_Check is actually only a Check-compatible wrapper around DwinsHs_AppendRemoteFile).

    I believe that's all you need. The [Run] happens only after the download.