While doing day to day activities in office so many times I have been helped by blogs created by developers community. There are lot to learn and lot to serve. I though why not to give something in return to our community.
So one thing which I learned by following blogs is WIX Installer. I tried make it a bit generic so that it could be used as a template to create new "Web Application Installer". I am going to put that in first blog of my life.
Here we go.....
In my case I have created a web application called "DefaultWebApp". If you see my solution explorer you can find the folder structure as under.
2. Build you web application and make sure that there is no compilation error.
3. Now we can create a new WIX installer for web application installation. I am going to add installer project in same solution. Right click on solution and click on Add -> New Project...
4. Select "Windows Installer XML" from Installed Templates in left panel of the dialog box. Choose "Setup Project" from the list of project types in right panel. Provide the appropriate name and the location of the project. Ideally I keep installer project name same as web project and post fix ".Install".
5. Click on Ok button. You will be able to see the new WIX project in your solution explorer as under.
6. You can build the project and can see the msi file generated in bin directory.
The msi file generated previously is of no use till we configure it to create virtual directory, application pool and harvest the file to be installed.
Before doing that let do some configuration settings so that if you want to build it using MSBuild you can provide product version, install location, web site name, port number, application pool name, source directory etc in run time.
7. Right click on the installer project and select option "Unload Project"
8. Once project is unloaded from the solution, again right click on it and choose "Edit<YourProjectName>.wixproj"
Once you click the option mentioned above you will be able to see the project file in edit mode as under. And we are interested in the highlighted part below.
9. Let's add some of the project properties as under which can be used by MSBuild to pass the value as parameter.
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
..... (Existing properties)
<VersionFull Condition=" '$(VersionFull)' == '' ">1.0.0.0</VersionFull>
<InstallLocation Condition=" '$(InstallLocation)' == '' ">WebHome</InstallLocation>
<WebSiteName Condition=" '$(WebSiteName)' == '' ">DefaultWebApp</WebSiteName>
<WebSitePortNumber Condition=" '$(WebSitePortNumber)' == '' ">80</WebSitePortNumber>
<WebSitePoolName Condition=" '$(WebSitePoolName)' == '' ">$(WebSiteName)</WebSitePoolName>
<SourceDir Condition=" '$(SourceDir)' == '' ">C:\Projects\WixDemo\DefaultWebApp\</SourceDir>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath> <DefineConstants>Debug;ProductVersion=$(VersionFull);InstallLocation=$(InstallLocation);WebSiteName=$(WebSiteName);WebSitePortNumber=$(WebSitePortNumber);WebSitePoolName=$(WebSitePoolName);SourceDir=$(SourceDir)</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<OutputPath>bin\$(Configuration)\</OutputPath>
<IntermediateOutputPath>obj\$(Configuration)\</IntermediateOutputPath>
<DefineConstants>ProductVersion=$(VersionFull);InstallLocation=$(InstallLocation);WebSiteName=$(WebSiteName);WebSitePortNumber=$(WebSitePortNumber);WebSitePoolName=$(WebSitePoolName);SourceDir=$(SourceDir)</DefineConstants>
</PropertyGroup>
..... (Existing properties)
</Project>
Above we have set the additional properties which can be passed at run time as parameter. If omitted it will use the default value set above.
10. Save the file and reload the project again.
11. Now let's change the hard coded (Default) value in our installer projects "Product.wxs" file to use the value passed in parameter set earlier in project file. After making changes your Product.wxs file should look like below.
(Make sure you don't change the product Id and UpgradeCode)
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="Your GUID Value"
Name="$(var.WebSiteName) $(var.ProductVersion)"
Language="1033"
Version="$(var.ProductVersion)"
Manufacturer="Demo application Manufaturer"
UpgradeCode="Your GUID Value">
<Package InstallerVersion="200" Compressed="yes" />
<Property Id="FRAMEWORKROOT">
<RegistrySearch Id="FrameworkRootDir"
Root="HKLM"
Key="SOFTWARE\Microsoft\.NETFramework"
Type="directory" Name="InstallRoot" />
</Property>
<Media Id="1" Cabinet="$(var.WebSiteName).cab" EmbedCab="yes" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="INSTALLDIR" Name="$(var.InstallLocation)">
<Directory Id="SiteDirectory" Name="$(var.WebSiteName)"/>
</Directory>
</Directory>
<Feature Id="ProductFeature" Title="Demo Web Application Installer" Level="1">
<ComponentGroupRef Id="Product.Generated" />
</Feature>
</Product>
</Wix>
NOTE: Don't forget to add your GUID value in project Id (Curly bracket not required).
We are now ready to build our installer package using MSBuild and pass command line parameters like product version, install location, web site name, port number, application pool name, source directory etc.
Adding IIS Configuration settings
- For IIS configuration we need to add required references to the installer project as below.
2. Add new item in installer project. From the dialog box select "Wix File" and give it appropriate name. In my case I am giving name as "IISConfiguration".
3. Add code in file as under.
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:iis="http://schemas.microsoft.com/wix/IIsExtension"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<Fragment>
<ComponentGroup Id="IISConfiguration">
<ComponentRef Id="ApplicationPool" />
<ComponentRef Id="VirtualDirectory" />
</ComponentGroup>
<DirectoryRef Id="SiteDirectory">
<!-- Creating Application Pool -->
<Component Id="ApplicationPool" Guid="{Your GUID Value}">
<CreateFolder />
<!--Define application pool-->
<iis:WebAppPool Id="AppPool"
Name="$(var.WebSitePoolName)"
Identity="networkService"
IdleTimeout="20"
QueueLimit="1000"
MaxWorkerProcesses="1">
<iis:RecycleTime Value="02:00"/>
</iis:WebAppPool>
</Component>
<!--Creating Virtual Directory-->
<Component Id="VirtualDirectory" Guid="{Your GUID Value}">
<CreateFolder />
<!--Define Virtual Directory-->
<iis:WebVirtualDir Id="Application_VirtualDir"
Alias="$(var.WebSiteName)"
Directory="SiteDirectory"
WebSite="DefaultWebSite">
<!-- Configuring web application to use application pool -->
<iis:WebApplication Id="$(var.WebSiteName)"
Name="$(var.WebSiteName)"
Isolation="medium"
WebAppPool="AppPool"
AllowSessions ="yes"
SessionTimeout="20"
ParentPaths="no"
Buffer="yes"
ScriptTimeout="90"
ClientDebugging="no"
ServerDebugging="no">
<iis:WebApplicationExtension Extension="asax" CheckPath="yes" Script="yes" Executable="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" Verbs="GET,HEAD,POST" />
<iis:WebApplicationExtension Extension="ascx" CheckPath="yes" Script="yes" Executable="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" Verbs="GET,HEAD,POST" />
<iis:WebApplicationExtension Extension="asp" CheckPath="yes" Script="yes" Executable="$(env.SystemRoot)\System32\inetsrv\asp.dll" Verbs="GET,HEAD,POST" />
<iis:WebApplicationExtension Extension="aspx" CheckPath="no" Script="yes" Executable="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" Verbs="GET,HEAD,POST" />
<iis:WebApplicationExtension Extension="axd" CheckPath="no" Script="yes" Executable="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" Verbs="GET,HEAD,POST" />
<iis:WebApplicationExtension Extension="browser" CheckPath="yes" Script="yes" Executable="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" Verbs="GET,HEAD,POST" />
<iis:WebApplicationExtension Extension="config" CheckPath="yes" Script="yes" Executable="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" Verbs="GET,HEAD,POST" />
<iis:WebApplicationExtension Extension="cs" CheckPath="yes" Script="yes" Executable="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" Verbs="GET,HEAD,POST" />
<iis:WebApplicationExtension Extension="csproj" CheckPath="yes" Script="yes" Executable="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" Verbs="GET,HEAD,POST" />
<iis:WebApplicationExtension Extension="exclude" CheckPath="yes" Script="yes" Executable="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" Verbs="GET,HEAD,POST" />
<iis:WebApplicationExtension Extension="master" CheckPath="yes" Script="yes" Executable="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" Verbs="GET,HEAD,POST" />
<iis:WebApplicationExtension Extension="resources" CheckPath="yes" Script="yes" Executable="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" Verbs="GET,HEAD,POST" />
<iis:WebApplicationExtension Extension="resx" CheckPath="yes" Script="yes" Executable="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" Verbs="GET,HEAD,POST" />
<iis:WebApplicationExtension Extension="skin" CheckPath="yes" Script="yes" Executable="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" Verbs="GET,HEAD,POST" />
<iis:WebApplicationExtension Extension="svc" CheckPath="no" Script="yes" Executable="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" Verbs="GET,HEAD,POST" />
<iis:WebApplicationExtension Extension="vb" CheckPath="yes" Script="yes" Executable="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" Verbs="GET,HEAD,POST" />
<iis:WebApplicationExtension Extension="vbproj" CheckPath="yes" Script="yes" Executable="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" Verbs="GET,HEAD,POST" />
<iis:WebApplicationExtension Extension="vsdisco" CheckPath="no" Script="yes" Executable="[FRAMEWORKROOT]v4.0.30319\aspnet_isapi.dll" Verbs="GET,HEAD,POST" />
</iis:WebApplication>
<!-- Configuring Web application property -->
<iis:WebDirProperties Id="Application_Properties"
AnonymousAccess="yes"
WindowsAuthentication="no"
DefaultDocuments="Default.aspx"
Read="yes"
LogVisits="yes"
Index="yes"
Execute ="yes"
Script="yes" />
</iis:WebVirtualDir>
</Component>
</DirectoryRef>
</Fragment>
</Wix>
NOTE: Don't forget to add your GUID value in above code.
4. Save the file.
5. Open "Product.wxs" file and modify ComponentGroupRef as below in "Feature" element.
<Feature Id="ProductFeature" Title="Demo Web Application Installer" Level="1">
<ComponentGroupRef Id="IISConfiguration" />
</Feature>
6. Build your project. It should be build successfully.
Harvesting web application files
When we install our web application it should not just create virtual directory and configure IIS but also should copy the required files and libraries in appropriate folder. For example in our case apart from creating virtual directory and configuring IIS installer it should also install web application folders (Account, Scripts, Styles etc.) and files (About.aspx, Default.aspx, Global.asax, Site.Master, Web.config etc.) along with the bin folder containing required libraries.
So in that case we have to specify which folder and files to be installed and the hierarchy of the folders. Our application can contain number of folders and files which is not require to be installed by the installer like .pdb, .cs files and folders like "obj", "Debug". So we have to add the require files and at the same time filter out the unwanted files and folders. To achieve this we can transform the source in the required structure and filter out the unwanted files.
1. Create file "TransformDirectoryToWix.xslt" in our installer project.
2. Add below code in "TransformDirectoryToWix.xslt" file.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:wix="http://schemas.microsoft.com/wix/2006/wi"
version="1.0"
exclude-result-prefixes="msxsl wix">
<xsl:output method="xml" indent="yes" encoding="utf-8"/>
<xsl:template match="@* | node()">
<xsl:apply-templates />
</xsl:template>
<xsl:template match ="wix:Fragment[wix:DirectoryRef]">
<Include>
<DirectoryRef Id="INSTALLDIR">
<xsl:apply-templates />
</DirectoryRef>
</Include>
</xsl:template>
<xsl:template match="wix:Directory[wix:Directory and (not(@Name='obj') and not(@Name='logs') and not(@Name='.svn') and not(@Name='Debug') and not(@Name='Release')) or (@Name='bin') or (@Name='css')]">
<Directory Id="{@Id}" Name="{@Name}">
<xsl:apply-templates />
</Directory>
</xsl:template>
<xsl:template match="wix:Component[wix:File[(not(contains(@Source,'.pdb') or contains(@Source,'.cs') or contains(@Source,'.css.svn') or contains(@Source,'.svn') or contains(@Source,'\obj\'))) or (contains(@Source,'.css') and not(contains(@Source,'.svn')))]]">
<Component Id="{@Id}" Guid="{@Guid}" Feature="Complete">
<File Id="{wix:File/@Id}" Source="{wix:File/@Source}" KeyPath="yes" />
</Component>
</xsl:template>
</xsl:stylesheet>
3. Add "Files.wxs" (you can choose file name as per your naming convention) where we will put the transformed output. (Hierarchical representation of components to be installed)
4. Right click on the installer project, choose properties, select Build Events tab as shown in screen below and enter command
"$(WixToolPath)heat.exe" dir "C:\Projects\WixDemo\DefaultWebApp" -var var.SourceDir -gg -sfrag -scom -sreg -suid -t "$(ProjectDir)TransformDirectoryToWix.xslt" -out "$(ProjectDir)Files.wxs in pre-build event text area.
Note: Quotation mark at the end is not required.
5. Open "Product.wxs" file add include tag as under to link "Files.wxs", below "Feature" element.
<?include Files.wxs?>
6. Finally your "Product.wxs" file should look as under.
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="8ee0accb-4a08-4307-801e-7b8c74388097"
Name="$(var.WebSiteName) $(var.ProductVersion)"
Language="1033"
Version="$(var.ProductVersion)"
Manufacturer="Demo application Manufaturer"
UpgradeCode="18c68f5d-ed41-4681-a3c7-1377a8f58c39">
<Package InstallerVersion="200" Compressed="yes" />
<Property Id="FRAMEWORKROOT">
<RegistrySearch Id="FrameworkRootDir"
Root="HKLM"
Key="SOFTWARE\Microsoft\.NETFramework"
Type="directory" Name="InstallRoot" />
</Property>
<Media Id="1" Cabinet="$(var.WebSiteName).cab" EmbedCab="yes" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="INSTALLDIR" Name="$(var.InstallLocation)">
<Directory Id="SiteDirectory" Name="$(var.WebSiteName)"/>
</Directory>
</Directory>
<Feature Id="ProductFeature" Title="Demo Web Application Installer" Level="1">
<ComponentGroupRef Id="IISConfiguration" />
</Feature>
<?include Files.wxs?>
</Product>
</Wix>
7. Build your installer project. It will harvest your source project folder and put the required components in "Files.wxs". We are linking "Files.wxs" in our "Product.wxs", so all the components present in "Files.wxs" will be installed when you will run the installer.
8. Run the installer and check the created virtual directory and the folders and files installed by installer.
Hope this post will give you basic understanding of installing ASP.Net application using WIX.