Friday, 8 July 2011

Working with MSBuild - <ItemGroup>

While working on MSBuild and doing some RND, I found that if we utilize <ItemGroup> properly we can achieve lot's things very easily and keep the build file very clean. I am not going in details but still trying to highlight how we can use it in various scenarios. You can utilize it then according to your requirement.


I assume you have created build file. If not then create build.xml (you can use naming convention as per your requirement). In my case I have created it "build.xml" at "C:\Projects\MSBuildDemo\", which contains some basic code as under. 


<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 
       <ItemGroup>
              <Department Include="Admin">
                     <Id>0001</Id>
                     <Name>Paul</Name>
       <Package>£45000</Package>
              </Department>
              <Department Include="HR">
                     <Id>0002</Id>
                     <Name>Ewen</Name>
      <Package>£51000</Package>
              </Department>
              <Department Include="Finance">
                     <Id>0003</Id>
                     <Name>Jacob</Name>
      <Package>£48000</Package>
              </Department>
              <Department Include="HR">
                     <Id>0004</Id>
                     <Name>Jacob</Name>
      <Package>£41000</Package>
              </Department>
       </ItemGroup>
 
</Project>
   

Note: Our example will be based on the </ItemGroup> which is added in it. 

Scenario 1: I want list of all departments in ItemGroup.


Add target as under in build file which will display list of all departments.


<Target Name="DepartmentsInItemGroup">
    <Message Text="***** Display department in ItemGroup  *****"/>

    <Message Text="@(Department)" />
</Target>

If you will open Visual Studio Command Prompt and execute the target "DepartmentsInItemGroup", you will see the result as under.


Notice here you will get "HR" department twice.

Scenario 2: I want list of unique departments in ItemGroup.

Same as above create target as below and execute it.




<Target Name="DepartmentList">
    <Message Text="***** List of Departments *****"/>

    <Message Text="%(Department.Identity)" />
</Target>


You will get the result as under having unique department.


Scenario 3: If you will see the <ItemGroup> above there are two person with the same name "Jacob". One is in Finance and other is in HR department. I want unique names from the <ItemGroup>.

Same as above create target as below and execute it.


<Target Name="UniqueEmployeeName">
    <Message Text="***** List of Unqiue Employee Names *****"/>

    <Message Text="%(Department.Name)"/>
</Target>

You will see the result as under.



If you will notice you will see names "Paul", "Ewen", "Jacob". There are two Jacob's but only one is displayed.


Scenario 4: I want Department based employee names list from the <ItemGroup>.

Create target as below and execute it.

<Target Name="DepartmentBasedEmployeeName">
    <Message Text="***** List of Department Based Employee Name *****"/>

    <Message Text="%(Department.Identity): @(Department->'%(Name)')" />
</Target>


You will see the result as under.


Scenario 5: I want to display same departments list for multiple authorized branches (Cross Join ItemGroups).


Create a property containing Authorized branches as under.



<PropertyGroup>
       <AuthorizedBranches>Branch1;Branch2;Branch3</AuthorizedBranches>
</PropertyGroup>

Create Target as under

<Target Name="DepartmentsInItemGroup">
    <Message Text="***** Display department in ItemGroup  *****"/>

       <ItemGroup>
              <AuthorizedBranch Include="$(AuthorizedBranches)"/>
       </ItemGroup>
      
    <Message Text="%(AuthorizedBranch.Identity)" />
       <Message Text=" "/>
      
       <CreateItem Include="@(Department)" AdditionalMetadata="BranchName=%(AuthorizedBranch.Identity)">
              <Output TaskParameter="Include" ItemName="BranchDetails"/>
       </CreateItem>

       <Message Text="Department : %(BranchDetails.Identity); AuthorsizedBranchName : %(BranchDetails.BranchName)" />
      
       <Message Text=" "/>
       <Message Text="Department : %(BranchDetails.Identity)"/>
      
  </Target>

You will see the Output as under.





Hope this will help to get started with <ItemGroup>. If you notice we have used different @, % with department itemgorup. So what that stands for?

@ is for an item in collection, which is a group of files with attached metadata under a name.

% denote an access to a metadata of an item.
There are wellknown metadatas (like RecursiveDir, see the definition in msdn) automatically attached to an item, or you can attach your own metadata when you define your items.
$ denotes access to a property.

Wednesday, 15 June 2011

Installing ASP.Net web application using WIX (Windows Installer XML)

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.....

Topics covered
  1. Introduction
  2. Creating new WIX Setup project.
  3. Adding IIS Configuration settings (Virtual directory, setting application properties, Application pool)
  4. Harvesting web application files.
  5. Transforming file source according to required folder structure.
Introduction
The Windows Installer XML (WiX) is a toolset that builds Windows installation packages from XML source code. The toolset supports a command line environment that developers may integrate into their build processes to build MSI and MSM setup packages. (I am not a regular blogger but I may cover integrating with MSBuild and uninstallation and installation or web application created by WIX.)


To gain more knowledge you can google or buy 























Creating new WIX Setup project
I am assuming that you already have WiX v3.5 installed on your machine. If not you can find it at http://wix.codeplex.com/releases/view/60102

Before creating Wix installer let's create a simple web application which we want to install using  WIX.

  1. Open visual studio  and create new web application. (Assuming you know how to create new web application.)






















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



  1. 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.