Packaging a .NET Deployment Project into a Single EXE
So you want to package your MSI and EXE into a single EXE for distribution? There’s no way to do it using .NET, but Inno Setup is an excellent, free utility that will allow us to create a bootstrap utility. You will also need to download the ISTool pack to utilize the isxdl.dll.
This tool will ensure the user has the proper .NET framework installed; if not, it will retrieve it from the Microsoft site and begin installing it for them. It will also retrieve your EXE and MSI files from any web location you specify, allowing them to automatically download and install (or uninstall later) for the user.
Enough rambling, here’s the code to use and enjoy (please note my link to the .NET Framework and registry checking are for 2.0):
[_ISTool]
EnableISX=true
[Setup]
AppName=YourAppName
AppVerName=Your App Version 1.0.0
MinVersion=4.1,4.0
DefaultDirName={pf}\Your Default Directory
DefaultGroupName=Your Default Group Name
Compression=lzma
SolidCompression=true
OutputBaseFilename=Your Output File Name
DisableDirPage=true
DisableProgramGroupPage=true
DisableReadyPage=false
DisableReadyMemo=true
[Files]
Source: C:\Program Files\ISTool\isxdl.dll; Flags: dontcopy
[Messages]
WinVersionTooLowError=YourAppName requires Windows 2000, Windows XP or later.
[Icons]
Name: {group}\Uninstall YourAppName; Filename: {uninstallexe}
[Code]
var
dotnetRedistPath: string;
downloadNeeded: boolean;
dotNetNeeded: boolean;
memoDependenciesNeeded: string;
uninstaller: String;
ErrorCode: Integer;
procedure isxdl_AddFile(URL, Filename: PChar);
external 'isxdl_AddFile@files:isxdl.dll stdcall';
function isxdl_DownloadFiles(hWnd: Integer): Integer;
external 'isxdl_DownloadFiles@files:isxdl.dll stdcall';
function isxdl_SetOption(Option, Value: PChar): Integer;
external 'isxdl_SetOption@files:isxdl.dll stdcall';
const
dotnetRedistURL = 'http://download.microsoft.com/download/5/6/7/567758a3-759e-473e-bf8f-52154438565a/dotnetfx.exe';
const
exeURL = 'http://www.pathtoyoursite.com/setup.exe';
const
msiURL = 'http://www.pathtoyoursite.com/Installation.msi';
function InitializeSetup(): Boolean;
begin
Result := true;
dotNetNeeded := false;
if not FileExists(ExpandConstant('{tmp}\setup.exe')) or not FileExists(ExpandConstant('{tmp}\Installation.msi')) then
begin
isxdl_AddFile(exeURL, ExpandConstant('{tmp}\setup.exe'));
isxdl_AddFile(msiURL, ExpandConstant('{tmp}\Installation.msi'));
downloadNeeded := true;
end
// See if the .NET Framework is already installed
if (not RegKeyExists(HKLM, 'Software\Microsoft\.NETFramework\policy\v2.0')) then begin
dotNetNeeded := true;
if (not IsAdminLoggedOn()) then begin
MsgBox('YourAppName needs the Microsoft .NET Framework to be installed by an Administrator', mbInformation, MB_OK);
Result := false;
end else begin
memoDependenciesNeeded := memoDependenciesNeeded + ' .NET Framework' ;
dotnetRedistPath := ExpandConstant('{src}\dotnetfx.exe');
if not FileExists(dotnetRedistPath) then begin
dotnetRedistPath := ExpandConstant('{tmp}\dotnetfx.exe');
if not FileExists(dotnetRedistPath) then begin
isxdl_AddFile(dotnetRedistURL, dotnetRedistPath);
downloadNeeded := true;
end;
end;
SetIniString('install', 'dotnetRedist', dotnetRedistPath, ExpandConstant('{tmp}\dep.ini'));
end;
end;
end;
function NextButtonClick(CurPage: Integer): Boolean;
var
hWnd: Integer;
ResultCode: Integer;
begin
Result := true;
if CurPage = wpReady 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', 'Downloading YourAppName Components');
isxdl_SetOption('description', 'YourAppName needs to install some components. Please wait while Setup is downloading extra files to your computer.');
if isxdl_DownloadFiles(hWnd) = 0 then Result := false;
end;
if (Result = true) and (dotNetNeeded = true) then begin
if Exec(ExpandConstant(dotnetRedistPath), '', '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then begin
// handle success if necessary; ResultCode contains the exit code
if not (ResultCode = 0) then begin
Result := false;
end;
end else begin
// handle failure if necessary; ResultCode contains the error code
Result := false;
end;
end;
FileCopy(ExpandConstant('{tmp}\Installation.msi'), ExpandConstant('{app}\Installation.msi'), false);
FileCopy(ExpandConstant('{tmp}\setup.exe'), ExpandConstant('{app}\setup.exe'), false);
end;
end;
function UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String;
var
s: string;
begin
if memoDependenciesNeeded <> '' then s := s + 'Dependencies to install:' + NewLine + memoDependenciesNeeded + NewLine;
s := s + MemoDirInfo + NewLine + NewLine;
Result := s
end;
[Run]
Filename: msiexec.exe; Parameters: "/i ""{tmp}\Installation.msi "" /qb"
[UninstallRun]
Filename: msiexec.exe; Parameters: "/x ""{app}\Installation.msi "" /quiet"
Thanks to the various posts on the web that gave me the basic understanding of Inno Setup and allowed me to create this script.
Great script, thanks for posting it. FYI in your [Setup] section you have a series of true and false values. Those should actually be yes or no. For example SolidCompression should either be “yes” or “no” not “true” or “false” according to the docs: http://www.jrsoftware.org/ishelp/topic_setup_solidcompression.htm
Dear Matt:
You saved me. Besides being a priest, for 43 years I have also programmed. But like us all, there is always much to learn so I am like all programmers, a work in progress.
This script saved me. Three days ago, I had not heard of Inno Setup. For 9 months, I have been working on a system, Auto Shutdown Pro II, which I believe everybody needs and hopefully will want.
I was about to go to Beta. I assumed that when I specified in the .Net Setup and Depoloyment the exact directory for .Net Framework 4 as a pre-requisite that Setup .Net produced would be intelligent enough to download and install .Net 4.0 if needed. Man was I wrong. When I had my non-programmer son try and install the program, getting .Net 4.0 properly installed hit like a hammer.
This script you made, with only a couple changes, did everything that was missing. This is great. You saved me and my project. Literally, I know almost nothing about Inno Setup and ISTOOL, but in just believing in what you said would work I got an immediate.
Let me make a few comments for programmers who may see this. First JP suggestion to change all “true” or “false” to “yes” or “no” should NOT BE followed. Change it, and the script no longer works. Secondly, apparently since you wrote this script newer versions of Inno Setup have been released. In lines 36 and 40, you reference a data type as “PChar”. With the newest version of Inno Setup, references to “PChar” is an error and no longer supported. Instead, programmers should change this code from “PChar” to “PAsciChar” and this change will correctly get rid of the compilation errors.
Thank you Matt for this wonderful contribution. I only wish every .Net programmer knew now about Inno Setup, and this script of yours.
Sincerely,
Pastor Burt, Programmer
Gods Church Of Faith
Burt,
Glad this old article is still proving useful.
Happy to help,
Matt