Introduction
Software deployment is one of the complex task in the software development world and nowadays we need to create separate installers for different CPU Architectures like 32 bit, 64 bit and Itanium, it adds more complexity to software deployment. End users often confused to choose the right one when we give different installers for the same product, so in this article we are going to see how to create single installer package for multiple CPU architecture and how to trigger the right installer based on user machine configuration.
Background
Recently, I worked in a product which needs to deploy with different CPU architecture. End user wanted a single installer for different host applications and operating systems and also we need to perform some custom operations before start installing the MSI installer. For that we found a solution with SFX and HTA, we are going see it in details with rest of this article so the reader of this article should have knowledge on how to create an archive file, HTML/HTML Applications (HTA), Java Script and VBScript. Please read the following links when you don’t have any prior knowledge on above topics.
SFX
SFX stands for Self Extractor. It is a compressed and archived executable file which decompresses and extracts the content without any archival programs.
Most of the archival programs support to create SFX files and some programs convert the archive files to SFX files.
Refer this link for more information.
HTA
HTA stands for HTML Application. It is a HTML based windows program and it supports Java Script and VB Script.
HTA structure exactly same like HTML page, additionally it will include the hta host tag which look as below,
<HTA:APPLICATION ID="oHTA"
APPLICATIONNAME="myApp"
BORDER="thin"
BORDERSTYLE="normal"
CAPTION="yes"
ICON=""
MAXIMIZEBUTTON="yes"
MINIMIZEBUTTON="yes"
SHOWINTASKBAR="no"
SINGLEINSTANCE="no"
SYSMENU="yes"
VERSION="1.0"
WINDOWSTATE="maximize"/>
This tag should place under header tag of html and rest of things are similar to HTML. We can use any one script from Jscript or VBScript or both. Here the sample hta file with script,
<html>
<head>
<title>My First HTA Splash Screen</title>
<style type="text/css">
html, body
{
margin: 0;
padding: 0;
height: 100%;
}
body
{
background-color: #FFF;
border: 0px;
margin: 0px;
font-family: @Calibri;
background-color: LightGray;
}
#container
{
min-height: 100%;
position: relative;
}
#header
{
background-color: Lime;
}
#body
{
background-color: LightGray;
padding: 5px;
position: relative;
}
#footer
{
background-color: Gray;
position: absolute;
bottom: 0;
width: 100%;
min-height: 60px;
}
</style>
</head>
<script language="javascript" type="text/javascript">
function CloseMe() {
window.close();
}
function ShowBrowserVersion() {
var browser = GetBrowserInfo();
alert(browser.name + " - " + browser.version);
}
function GetBrowserInfo() {
var ua = navigator.userAgent, tem, M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
if (/trident/i.test(M[1])) {
tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
return { name: 'IE', version: (tem[1] || '') };
}
if (M[1] === 'Chrome') {
tem = ua.match(/\bOPR\/(\d+)/)
if (tem != null) { return { name: 'Opera', version: tem[1] }; }
}
M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?'];
if ((tem = ua.match(/version\/(\d+)/i)) != null) { M.splice(1, 1, tem[1]); }
return {
name: M[0],
version: M[1]
};
}
</script>
<hta:application id="htaSplash" applicationname="Splash Screen" scroll="No" scrollflat="no"
caption="No" singleinstance="Yes" borderstyle="none" minimizebutton="No" maximizebutton="No"
showintaskbar="no" innerborder="no" border="none" />
</head>
<body>
<div id="container">
<div id="header">
<table width="100%">
<tr>
<td align="center">
Header
</td>
</tr>
</table>
</div>
<div id="body">
<table width="100%">
<tr>
<td align="center">
Body
</td>
</tr>
<tr>
<td>
<a href="#" onclick="javascript:ShowBrowserVersion();">Browser Version - Javascript</a>
</td>
</tr>
<tr>
<td>
<a href="#" onclick="vbs:SayHello">Say Hello - VBscript</a>
<br />
</td>
</tr>
<tr>
<td>
<a href="#" onclick="javascript:CloseMe();">Close</a>
<br />
</td>
</tr>
<tr>
<td>
<br />
<br />
<br />
<br />
<br />
</td>
</tr>
</table>
</div>
<div id="footer">
<table width="100%">
<tr>
<td align="center">
Footer
</td>
</tr>
</table>
</div>
</div>
<script language="VBScript" type="text/vbscript">
intHeight = 250
intWidth = 500
Me.ResizeTo intWidth, intHeight
Me.MoveTo (screen.width / 2) - (intWidth / 2), (screen.height / 2) - (intHeight / 2)
Sub SayHello
MsgBox "Hello World!"
End Sub
Sub CloseWindow
window.Close
End Sub
</script>
Snapshot of above sample HTA markup:
For more information on HTA refer the following links,
Solution
After some research we found a possible solution and it is as below,
- Archive the all installer and other required resources as a Self-extractor.
- Create a utility to detect the installed host application versions and Operation System bit size and run this utility after self-extraction completed.
- Utility will run the appropriate the installer based on information which gathered from the client machine.
Since HTA supports for VB Script we can achieve almost same thing what we can do with it. It also provides the user interface to show our brand, product information and install guide, etc.
Alternatively, we can use SFX and VB Script to achieve the above solution when we don’t want to show the UI after extraction.
This is not only solution we got to solve this issue, you may find some other solutions (with third party installer tools) better than this. Here, I just want to share what I have used in a product with the combination of SFX and HTA.
Now, it is the time to put all our knowledge into action.
Implementation
In order to implement the above solution we need to do the following three steps,
- Create MSI Installer for different CPU Architecture.
- Create a Splash Window.
- Create SFX.
Create MSI Installer
Create your product installer for different CPU Architecture. I used Visual Studio 2010 for this article sample, you may use other MSI creator programs. Here I am not going to explain how to create a MSI installer and you can refer this article when you need a reference.
Create Splash Window
Create a splash window with HTA. Here, you can design for your won taste and requirements but I designed the following splash window to demonstrate.. It includes the following functionalities,
- Read Operating System Details.
- Read CPU Details.
- Read Physical Memory Details.
- Scan for prerequisites (not implemented in this sample but you can add your own).
- Display the above information.
- Choose the appropriate installer based on CPU Architecture.
- Show installer guide.
Splash Window Source:
<html>
<head>
<title>Installer Splash</title>
<style type="text/css">
html, body
{
margin: 0;
padding: 0;
height: 100%;
}
body
{
background-color: #FFF;
border: 0px;
margin: 0px;
font-family: @Calibri;
background-color:LightGray;
}
#container
{
min-height: 100%;
position: relative;
}
#header
{
background-color:Orange;
}
#body
{
background-color:LightGray;
padding: 5px;
position: relative;
}
#background
{
background: url(images/product.png) no-repeat center center;
filter: alpha(opacity=20);
z-index:-1;
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
width: 100%;
height: 100%;
}
#footer
{
background-color:Gray;
position: absolute;
bottom: 0;
width: 100%;
min-height: 60px;
}
.pdtitle
{
padding-top: 20px;
padding-left: 10px;
color: #fff;
vertical-align: middle;
}
.copyright
{
color: #fff;
font-size: x-small;
}
.sysinfotable
{
border: 1px solid #B5B5B5;
font-size: smaller;
}
.inforow
{
height: 30px;
}
.bodyheader
{
font-size: large;
font-weight: bold;
}
.infocaption
{
font-weight: bold;
background-color: White;
color: Orange;
}
.infovalue
{
padding-left: 2px;
}
.bottomborder
{
border-bottom: 1px #B5B5B5 solid;
}
.leftborder
{
border-left: 1px #B5B5B5 solid;
}
#installmenu a
{
color: #0066FF;
font-weight: bolder;
}
.pageVisible
{
display:block;
}
.pageInVisible
{
display:none;
}
.loading
{
padding-top:100px;
padding-left:10px;
font-size:30;
color:#7C6D5A;
font-weight:bold;
}
#pageLoading
{
width:100%;
}
</style>
</head>
<script language="javascript" type="text/javascript">
function CloseMe() {
window.close();
}
function InstallAddin() {
Install();
}
function ShowBrowserVersion() {
var browser = GetBrowserInfo();
alert(browser.name + " - " + browser.version );
}
function GetBrowserInfo() {
var ua = navigator.userAgent, tem, M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
if (/trident/i.test(M[1])) {
tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
return { name: 'IE', version: (tem[1] || '') };
}
if (M[1] === 'Chrome') {
tem = ua.match(/\bOPR\/(\d+)/)
if (tem != null) { return { name: 'Opera', version: tem[1] }; }
}
M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?'];
if ((tem = ua.match(/version\/(\d+)/i)) != null) { M.splice(1, 1, tem[1]); }
return {
name: M[0],
version: M[1]
};
}
</script>
<hta:application id="htaSplash" applicationname="Installer Splash" scroll="No" scrollflat="no" caption="No"
singleinstance="Yes" borderstyle="none" minimizebutton="No" maximizebutton="No"
showintaskbar="no" innerborder="no" border="none"/>
</head>
<body>
<div id="container">
<div id="header">
<table width="100%">
<tr>
<td width="75%" valign="middle">
<h2 class="pdtitle">Product Name V1.0</h2>
</td>
<td width="25%" align="center">
<a href="http://www.yourproductsite.com"><img src="images/pdlogo.png" height="64px" border="0" alt="Product Logo" /></a>
</td>
</tr>
</table>
</div>
<div id="body">
<div id="pageLoading">
<table cellpadding="0" cellspacing="0" width="100%" height="100%" border="0">
<tr>
<td class="loading" align="center">
Loading...
</td>
</tr>
</table>
</div>
<div id="pageInfo" class="pageInVisible">
<table width="100%" border="0">
<tr>
<td class="bodyheader">
Welcome to Product
</td>
</tr>
<tr>
<td style="font-size:smaller; padding-right:10px">Product introduction here
</td>
</tr>
<tr>
<td style="font-size:small;font-weight:bold;">
System/Pre-requisites Information :
</td>
</tr>
<tr>
<td>
<table width="100%" border="0">
<tr>
<td width="70%">
<table width="100%" class="sysinfotable" border="0" cellpadding="0" cellspacing="0">
<tr>
<td class="inforow infocaption bottomborder" align="right">
Operating System :
</td>
<td class="inforow bottomborder infovalue leftborder" id="lblOS">
</td>
</tr>
<tr>
<td class="inforow infocaption bottomborder " align="right">
Processor :
</td>
<td class="inforow bottomborder infovalue leftborder altbackground" id="lblProcessor">
</td>
</tr>
<tr>
<td class="inforow infocaption bottomborder" align="right">
Phycial Memory :
</td>
<td class="inforow bottomborder infovalue leftborder" id="lblRAM">
</td>
</tr>
<tr>
<td class="inforow infocaption bottomborder" align="right">
Pre-requisites 01 :
</td>
<td class="inforow bottomborder infovalue leftborder altbackground" id="lblReq01">
</td>
</tr>
<tr>
<td class="inforow infocaption " align="right">
Pre-requisites 02 :
</td>
<td class="inforow infovalue leftborder" id="lblReq02">
</td>
</tr>
</table>
</td>
<td width="1%">
</td>
<td width="29%">
<table width="100%" cellpadding="1" cellspacing="0" id="installmenu">
<tr>
<td align="right">
<a href="#" onclick="vbs:Install" title="Click here to install the product">Install Product</a>
</td>
<td>
<a href="#" onclick="vbs:Install"><img src="images/setup.png" style="border:0px" width="32" height="32" alt="Click here to install the product"/></a>
</td>
</tr>
<tr>
<td align="right">
<a href="#" onclick="vbs:Help" title="Click here to read the installation guide">Installer Guide</a>
</td>
<td>
<a href="#" onclick="vbs:Help"><img src="images/Help.png" style="border:0px" width="32" height="32" alt="Click here to read the installation guide"/></a>
</td>
</tr>
<tr>
<td align="right">
<a href="#" onclick="javascript:CloseMe();" title="Click here to close">Exit</a>
</td>
<td>
<a href="#" onclick="javascript:CloseMe();" style="border:0px"><img src="images/close.png" style="border:0px" width="32" height="32" alt="Click here to close" /></a>
</td>
</tr>
</table>
</td>
<td width="1%">
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
<!--
<div id="background"> <br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /></div>
</div>
<div id="footer">
<table width="100%">
<tr>
<td align="right">
<div class="copyright" id="copyright"></div>
<a class="copyright" href="http://www.yourwebsite.com">www.yourcompanywebsite.com</a>
</td>
</tr>
</table>
</div>
</div>
<SCRIPT Language="VBScript" type="text/vbscript">
Dim osBit
Dim osName,ramSize,cpuDetails,preReq01,preReq02
intHeight = 380
intWidth = 600
Me.ResizeTo intWidth, intHeight
Me.MoveTo (screen.width / 2) - (intWidth / 2), (screen.height / 2) - (intHeight / 2)
Sub ReadSystemInfo
On Error Resume Next
strComputer = "."
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
strOS=""
Set colOperatingSystems = objWMIService.ExecQuery("Select * from Win32_OperatingSystem")
For Each objOperatingSystem in colOperatingSystems
strOS = objOperatingSystem.Caption
Next
osBit = 32
If Trim(strOS) <> "" Then
Set WshShell = CreateObject("WScript.Shell")
OsType = WshShell.RegRead("HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\PROCESSOR_ARCHITECTURE")
If OsType = "AMD64" then
osBit = 64
End If
End If
osName = strOS & "," & osBit & "bit"
Set colItems = objWMIService.ExecQuery("Select * from Win32_PhysicalMemory")
For Each objItem in colItems
TotalRam = TotalRam + objItem.Capacity / 1024 / 1024 / 1024
Next
ramSize = TotalRam & " GB"
Set colItems = objWMIService.ExecQuery("Select * from Win32_Processor",,48)
For Each objItem in colItems
cpuDetails = objItem.Name
Next
preReq01 = "Installed / Not Installed"
preReq02 = "Installed / Not Installed"
Set objWMIService = Nothing
Set WshShell = Nothing
End Sub
Sub ShowSystemInfo
On Error Resume Next
lblOS.InnerHTML = osName
lblProcessor.InnerHTML = cpuDetails
lblRAM.InnerHTML = ramSize
lblReq01.InnerHTML = preReq01
lblReq02.InnerHTML = preReq02
pageInfo.ClassName = "pageVisible"
pageLoading.ClassName = "pageInVisible"
End Sub
Sub Install
Set objFSO = CreateObject("Scripting.FileSystemObject")
strFilePath = objFSO.GetAbsolutePathName(".")
Set objShell = CreateObject("WScript.Shell")
If osBit = 64 Then
strFilePath = strFilePath & "\Install\ProductSetup_x64.msi"
Else
strFilePath = strFilePath & "\Install\ProductSetup_x86.msi"
End if
objShell.Run strFilePath, 1, False
Set objFSO = Nothing
Set objShell = Nothing
End Sub
Sub Help
Set objFSO = CreateObject("Scripting.FileSystemObject")
strFilePath = objFSO.GetAbsolutePathName(".")
Set objShell = CreateObject("WScript.Shell")
strFilePath = strFilePath & "\InstallerGuide.pdf"
objShell.Run strFilePath, 1, False
Set objFSO = Nothing
Set objShell = Nothing
End Sub
Sub ShowCopyRight
copyRight.InnerHTML = " © Your Company " & Year(Date)
End Sub
Sub CloseWindow
window.Close
End Sub
ReadSystemInfo
ShowSystemInfo
ShowCopyRight
</Script>
Splash Window Snapshot:
The following code is responsible for to trigger the right installer based on user machine CPU architecture.
osBit = 32
Set WshShell = CreateObject("WScript.Shell")
OsType = WshShell.RegRead("HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\PROCESSOR_ARCHITECTURE")
If OsType = "AMD64" then
osBit = 64
End If
Sub Install
Set objFSO = CreateObject("Scripting.FileSystemObject")
strFilePath = objFSO.GetAbsolutePathName(".")
Set objShell = CreateObject("WScript.Shell")
If osBit = 64 Then
strFilePath = strFilePath & "\Install\ProductSetup_x64.msi"
Else
strFilePath = strFilePath & "\Install\ProductSetup_x86.msi"
End if
objShell.Run strFilePath, 1, False
Set objFSO = Nothing
Set objShell = Nothing
End Sub
Create the HTA project folder with following structure then copy MSI installers and other resources to it.
Project Folder:
- Product Setup
- Splash.hta
- InstallerGuide.pdf
- Images
- Close.png
- Help.png
- Pdlogo.png
- Product.png
- Setup.png
- Install
- ProductSetup_x86.msi
- ProductSetup_x64.msi
Make sure that everything is ready before going to next step.
Create SFX
Now we are ready to create the self-extractor package with all deployment files including installer splash file. To do that we need some special programs, we have lots of SFX creator in the market but we are going to use two open source applications to accomplish the task and those are,
- 7 Zip File manager – which creates the archive file in .7z format.
- 7 Zip SFX Maker – which creates the self-extractor for 7 Zip archive.
First we need to create archive file with 7 Zip file manager then we need to create self-extractor with 7 Zip SFX Maker. It required following steps to get the final output.
- Install the 7 Zip File Manager from here. Skip this step if you already installed it on your machine.
- Create 7 Zip archive file with following items,
- Splash.hta
- Images folder
- Install folder
- InstallerGuide.pdf
- Name the archive file whatever you want but here we going to give it as Productsetup.7z
- Install the 7 Zip SFX Maker from here. Skip this step if you have installed it already on your machine.
- Open 7 Zip SFX Maker.
- 7 Zip SFX Maker contains five important tabs and those are,
- File tab – where we need add our 7 zip archive files.
- Dialog tab – it contains the configuration for UI for various state of extractor.
- Icon – which contains the list icons for extractor, here we can choose our icon for self-extractor exe.
- Tasks- where we can specifics the actions which need to perform after extraction completed and it allows the following action types,
- Create Shortcut
- Run Program
- Delete File or Folder
- Set Environment Variable
- Metadata – which contains the standard file attributes and here we can change their values.
- Go to Files tab, click on plus button to add 7 zip archive files, it will display the file open dialog.
- Choose the productsetup.7z from the hard disk then click on “Open” button.
- Now we can see the selected file added in the file list box. You can repeat the step 7 to 8 to add more files.
- Go to Dialogs tab and do the following things,
- Change the default title to your custom title and here we going to give as “Product Setup”.
- Choose the Overwrite mode as what you want. By default it set to “Overwrite all files”, for our example leave the default one.
- We can set the predefined folder or temporary folder to extract the archived files and select the extract to temporary folder option for our example.
- Select Use XP style when you want windows XP based UI theme and select this option for our example.
- Select Compress SFX stub with UPX when you want to reduce the file size, UPX provides excellent compress ratio. Select this option for our example.
- Select Delete SFX file after extraction when you want to remove the extractor exe and don’t select this option for our example.
- Select Hide Icon in Title bar of all windows when you want to hide the icon from the title bar and select this option for our example.
- Select Show SFX icon in Begin, Finish and Cancel Prompt when you want to show the prompt for each state of extraction and don’t select this option for our example.
- Go to all sub tabs and do the appropriate settings.
- In our example, we are interested to trigger the appropriate MSI installer based on current machine CPU architecture and operating system, so we should hide the all self extractor UIs except our HTA splash window. Go through all dialog tabs and unselect the checkbox which given in the all sub tabs (as shown in the snapshot).
- Go to Icon tab and choose your icon from the list. When you are not happy with existing list then do the following steps to add custom icon to your self-extractor.
- Click on File -> Save Settings menu or “Save Settings…” button at bottom of the screen.
- It will display the Save File Dialog, give your name here and give it as Product.xml for our example.
- Go to Prodcut.xml folder and open it any xml/text editor.
- Go to Settings -> General -> Icon tag, here you can set the path for your custom icon file (path of .ico file).
- Save the settings file and open the updated file through 7 Zip SFX Maker. Make sure that “Allow user to change path” option is unselected under Dialogs -> Extract Path tab. There is bug in this application whenever we open any saved settings file the specified option set to default value.
- Go to Tasks tab and click on plus button to add a task to execute after the extraction complete. Please do the following things to show our HTA splash window after the extraction,
- Click on “Add” button.
- It will display the new task option window, here you select the “Run Program” option.
- It will display the Run Program option window and enter the following details.
- Enter Splash.hta at end of the Program textbox.
- Select the Do not wait for program to finish option.
- Select the Hide Console windows option.
- Finally click on “OK” button.
- Now you can see an entry in the task list box.
- Go to Metadata tab, select each attributes and change the value according to your need.
- Save the settings one more time.
- Click on “Make SFX”.
- Finished message will be displayed on the status bar once it build successfully.
- We almost done with our example and now we are going to do the test the output.
- Go to Product Setup folder, here you can find a self-extractor exe file with name of ProductSetup.sfx.exe.
- Double click on ProductSetup.sfx.exe, it will extract the ProductSetup.7z to temporary folder and start the Splash.hta after the extraction.
- While loading splash.hta scan for system information like OS, CPU and physical memory and custom pre-requisites information and display it on the screen.
- Click on “Install Product”, the splash window run the appropriate MSI installer based on OS bit size.
- Click on "Installer Guide" to see the installer guide so user can go through it before starting the installation.
As we expected, we got a single installer package and it helps user to run the right installer based on his/her machine configuration and also it facilitates us to show our brand and product information to user during every installation.
Conclusion
In this article we have seen how to create single package for multiple CPU architecture MSI installers and I hope it has delivered some useful information to you. Please provide your valuable feedback under comment section.