Tuesday, August 28, 2012

WIX Developer Guide by PACKT

I stumbled upon this really good source of WiX with details and examples which are easy to understand and very comprehensive. Following is the sample chapter which they have offered for free sharing and for a beginner this is still a good thing to start with.

PACKT - WiX: A Developer's Guide to Windows Installer XML is good and I recommend you to buy it at least eBook if you are really interested else don't waste your money.

Saturday, August 11, 2012

Serialize Class Object using Triple DES algorithm

DES encryption is not safe and although the code is a little old check out the vector and key in the code below, Triple DES is much safe. The crux of encryption is predictiblity and creating vector and key randon will make things more unpredictable and in this case user 192 bits for it.

Referred following
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;

Initialization
TripleDESCryptoServiceProvider cryptic = new TripleDESCryptoServiceProvider();
IFormatter formatter = new BinaryFormatter();
Stream stream = null;

Serialize
stream = new FileStream("MyConfig.txt", FileMode.Create, FileAccess.Write, FileShare.None);
using (CryptoStream cs = new CryptoStream(stream, new TripleDESCryptoServiceProvider().CreateEncryptor(cryptic.Key, cryptic.IV), CryptoStreamMode.Write))
{
    formatter.Serialize(cs, testClassObject);
    cs.FlushFinalBlock();
}
stream.Close();

Deserialize
stream = new FileStream("MyConfig.txt", FileMode.Open, FileAccess.Read, FileShare.None);
using (CryptoStream cs = new CryptoStream(stream, new TripleDESCryptoServiceProvider().CreateEncryptor(cryptic.Key, cryptic.IV), CryptoStreamMode.Read))
{
    testClassObject = (TestClass)formatter.Deserialize(cs);
}
stream.Close();

Reference:
http://msdn.microsoft.com/en-us/library/system.security.cryptography.tripledescryptoserviceprovider.aspx

Monday, July 23, 2012

WiX Custom Action - Copy MSI after Install

In WiX there is a functionality to execute application after successfully closing the setup in async mode.
Add following in the .wxs file and execute candle.exe with usual commands.

<Property Id="WixShellExecTarget" Value="[CommonAppDataFolder]Product_Data\test.exe" />
<CustomAction Id="LaunchApplication" BinaryKey="WixCA" DllEntry="WixShellExec" Impersonate="yes" />
<UI>
   <Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchApplication">NOT Installed</Publish>
</UI>

In command line compilation for Light.exe following "-ext" needed to add.
These extensions will allow use of WixUIExtension.dll and WixUtilExtension.dll.

light.exe d:\abc\test.wixobj -ext WixUIExtension -ext WixUtilExtension -out d:\abc\test.msi

This is the basic logic behind executing any exe after completing setup. This application executes in async mode and in my case I have used it to copy the present MSI from its present location to program data for safekeeping. This copy MSI is a simple implementation of copying file from one location to another hence not including that code here.

Reference
http://windows-installer-xml-wix-toolset.687559.n2.nabble.com/Why-doesn-t-this-work-Opening-PDF-at-Finish-button-click-td3087051.html

Sunday, July 15, 2012

Animate Form - Slowly Show and Close

This is something makes a simple Form more aesthetically rich. Form cosmetic aspects plays major part but launch of a form also plays a major aspects how user perceive it. I was looking ways to do that, one way is to use timer to change the opacity of form during show and hide. Other way and better way to animate it. User need to use user32.dll for it.

Implementation:

Add following reference
using System.Runtime.InteropServices;

Initialization - add following in FormClass

namespace TestNamespace
{
    public partial class TestForm : Form
    {
        [DllImport("user32.dll")]
        static extern bool AnimateWindow(IntPtr hWnd, int time, AnimateWindowFlags flags);
        [Flags]
        enum AnimateWindowFlags
        {
            AW_HOR_POSITIVE = 0x00000001,
            AW_HOR_NEGATIVE = 0x00000002,
            AW_VER_POSITIVE = 0x00000004,
            AW_VER_NEGATIVE = 0x00000008,
            AW_CENTER = 0x00000010,
            AW_HIDE = 0x00010000,
            AW_ACTIVATE = 0x00020000,
            AW_SLIDE = 0x00040000,
            AW_BLEND = 0x00080000
        }
    }
}

Need to handle two form events "XXX_Load" and "XXX_FormClosing" and add following code in that

private void TestForm_Load(object sender, EventArgs e)
{
   AnimateWindow(this.Handle, 1000, AnimateWindowFlags.AW_BLEND | AnimateWindowFlags.AW_ACTIVATE);
}

private void TestForm_FormClosing(object sender, FormClosingEventArgs e)
{
   AnimateWindow(this.Handle, 1000, AnimateWindowFlags.AW_BLEND | AnimateWindowFlags.AW_HIDE);
}

Reference :- http://subversion.assembla.com/svn/nex-framework/trunk/.NEX%20framework/WindowsAPI/user32.cs

Saturday, July 7, 2012

Simple Singleton Object

This is a simple Singleton class created which I have serialized using separate serialized class and saved in a text file in encrypted form. The main reason of having separate Singleton Class object and Serialized Class object is to maintain its basic integrity and keep maintainable easy.

Reference - http://msdn.microsoft.com/en-us/library/ff650316.aspx

To See how to serialize in encrypted form please refer to my previous implementation (link below).
Serialize singleton object to file - encrypted

Implementation:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;

namespace TestNamespace
{
    public class SingletonClass
    {
        string abc;
        
        // This is the crux of singleton implementation
        private static SingletonClass instance;
        private SingletonClass(){}
        public static SingletonClass Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new SingletonClass();
                }
                return instance;
            }
        }

        // standard get and set methods
        public Version ABC
        {
            get { return abc; }
            set { abc = value; }
        }
    }
}

Thursday, July 5, 2012

Serialize Class Object using DES algorithm

There is a specific requirement I have in my present implementation. Need to maintain a data store in the application in which some of the data needed to be stored and fetched whenever application launched again. To achieve created class in Singleton and made it serialized. Some of the objects in the class are not serialized.
I will be posting the implementation of Singleton class object later. Following is the implementation where class object has been serialized and de-serialized. I also have a requirement to encrypt the data.

To achieve this used DESCryptoServiceProvider and CryptoStream.
"TestStoreObj" is the class object which is in Singleton and I am serializing it here.

Note :- You can use Triple DES for it but it will impact performance due to multiple iteration of encryption.

Implementation:

Referred following
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;

Initialization
DESCryptoServiceProvider cryptic = new DESCryptoServiceProvider();
IFormatter formatter = new BinaryFormatter();
Stream stream = null;

Serialize
stream = new FileStream("MyConfig.txt", FileMode.Create, FileAccess.Write, FileShare.None);
using (CryptoStream cs = new CryptoStream(stream, new DESCryptoServiceProvider().CreateEncryptor(cryptic.Key, cryptic.IV), CryptoStreamMode.Write))
{
    formatter.Serialize(cs, TestStoreObj);
    cs.FlushFinalBlock();
}
stream.Close();

Deserialize
stream = new FileStream("MyConfig.txt", FileMode.Open, FileAccess.Read, FileShare.None);
using (CryptoStream cs = new CryptoStream(stream, new DESCryptoServiceProvider().CreateEncryptor(cryptic.Key, cryptic.IV), CryptoStreamMode.Read))
{
    TestStoreObj = (TestSingletonClass)formatter.Deserialize(cs);
}
stream.Close();

Reference:
http://msdn.microsoft.com/en-us/library/system.security.cryptography.des.aspx

Saturday, June 30, 2012

Generating sequential GUID

It is all over the place and there are many ways to do it. This small post is for my quick reference.

rpcrt4.dll 
is the Remote Procedure Call (RPC) API, used by Windows applications for network and Internet communication. This is an essential system process and should not be deleted.

UuidCreateSequential function
For security reasons, UuidCreate was modified so that it no longer uses a machine's MAC address to generate UUIDs. UuidCreateSequentialwas introduced to allow creation of UUIDs using the MAC address of a machine's Ethernet card.

The UuidCreateSequential function returns RPC_S_UUID_LOCAL_ONLY when the originating computer does not have an ethernet/token ring (IEEE 802.x) address. In this case, the generated UUID is a valid identifier, and is guaranteed to be unique among all UUIDs generated on the computer. However, the possibility exists that another computer without an ethernet/token ring address generated the identical UUID. Therefore you should never use this UUID to identify an object that is not strictly local to your computer. Computers with ethernet/token ring addresses generate UUIDs that are guaranteed to be globally unique.

Note:  The UuidCreateSequential function tends to be slightly faster than the UuidCreate function. When the performance of the generation of a UUID is a significant consideration, the UuidCreateSequential function may be used.

Implementation:

Declaration
using System.Runtime.InteropServices;

Initializing objects at class level
[DllImport("rpcrt4.dll", SetLastError = true)]
private static extern int UuidCreateSequential(out Guid value);

[Flags]
private enum RetUuidCodes : int
{
    RPC_S_OK = 0, //The call succeeded.
    RPC_S_UUID_LOCAL_ONLY = 1824, //The UUID is guaranteed to be unique to this computer only.
    RPC_S_UUID_NO_ADDRESS = 1739 //Cannot get Ethernet or token-ring hardware address for this computer.
}

Function to create sequencial Guid
public Guid CreateGuid()
{
   Guid guid;
   int result = UuidCreateSequential(out guid);
   if (result == (int)RetUuidCodes.RPC_S_OK)
      return guid;
   else
      return Guid.NewGuid();
}

In functions generate sequencial Guid using following
Guid uid = CreateGuid();
Reference
http://stackoverflow.com/questions/170346/what-are-the-performance-improvement-of-sequential-guid-over-standard-guid

Saturday, June 23, 2012

Creating Wizard using C# Form (Tab Control)

In one of the previous posting there is a Wizard implementation which used console application and multiple forms. But sometimes requirements are so that implementation needed to some sort of Wizard type but not with all pomp and show, even time constraint makes it hard to achieve an evolving application.

Creating Wizard using tab control is the other to do that, it is simple way to do that. It is a single form implementation with Wizard flavor. This implementation has following basic tweaks to achieve, rest of the implementations and functionality are user specific:

  1. Inherit TabControl and hide tabs by supressing TCM_ADJUSTRECT
  2. Then replace the instance of TabControl Class object initialisation with your class (in my case I names WizardTabControl). Do this replacement once you have completed the screen design and feature is complete.
  3. Set the dock property to Fill for TabPage

Implementation:
Following changes in Form.Designer.cs is the only thing needed. Add Following code at the end to inherit the TabControl class.
namespace System.Windows.Forms
{
    public class WizardTabControl : TabControl
    {
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == 0x1328 && !DesignMode) // Hide tabs by trapping the TCM_ADJUSTRECT message
                m.Result = IntPtr.Zero;
            else
                base.WndProc(ref m);
        }
    }
}

Changes in Form class, replace the TabControl reference with WizardTabControl
private System.Windows.Forms.WizardTabControl tabControl1;
private void InitializeComponent()
{
    this.tabControl1 = new System.Windows.Forms.WizardTabControl();
}

Saturday, June 16, 2012

WiX Custom Action – delete files before installation

In this implementation before installing MSI need to achieve following task:

  1. Delete the files from specific folders like .log, .ini, .dll etc. but configurable. 
  2. List of files to delete need to be configurable so that in future if new files are identified then make the change in configuration file and it will work. This list is something that needs to be provided with installer but at the same time will be only delivering MSI and not config file. 
Steps in Implementation:

  1. Create simple XML file (let say ConfigXML.xml) for maintaining list of files to delete. 
  2. Add ConfigXML.xml in MSI as an external binary file which is not part of basic installer package. 
  3. In MSI installation process add custom action before installation to retrieve file and delete those files. To implement Custom Action used C# DLL with WiX 3.5.

In the example below reading files from XML and deleting it is not included that will be taken separately. File deletion implementation left to user to implement.

XML file (ConfigXML.xml)
<?xml version="1.0" ?>
<root>
  <Node Path="c:\abc">a1.log</Node>
  <Node Path="d:\abc">a2.ini</Node>
  <Node Path="f:\abc">a3.dll</Node>
</root>

Changes in the .wxs file
<Binary Id="TestxxxXML.xml" SourceFile="d:\TestCfgXML.xml"/>
<Binary Id="myActDLL" SourceFile="d:\MyAction.CA.dll"/>

<CustomAction Id="deleteAction" BinaryKey="myActDLL" DllEntry="DeleteFiles" Impersonate="no" Execute="deferred" Return="check" />

<InstallUISequence>
  <Custom Action="deleteAction" After="ValidateProductID" >NOT Installed</Custom>
</InstallUISequence>
<InstallExecuteSequence>
  <Custom Action="deleteAction" After="ValidateProductID" >NOT Installed</Custom>
</InstallExecuteSequence>

Custom Action DLL implementation
namespace MyAction
{
    public class CustomActionClass
    {
        [CustomAction]
        public static ActionResult DeleteFiles(Session session)
        {
            try
            {
                string sPath = Path.Combine(Path.GetTempPath(), "TestXML.xml");
                var f = File.Create(sPath);
                
                using (var view = session.Database.OpenView("SELECT `Data` FROM `Binary` WHERE `Name` = `TestxxxXML.xml`", "testbinary"))
                {
                    view.Execute();
                    var rec = view.Fetch();
                    var Strm = rec.GetStream("Data");
                    if (Strm != null)
                    {
                        using (f)
                        {
                            byte[] buf = new byte[Int32.MaxValue];
                            int len;
                            while ((len = Strm.Read(buf, 0, buf.Length)) > 0)
                            {
                                file.Write(buf, 0, len);
                            }
                        }
                    }
                }
                f.Close();
                // From here code the operation to read from XML and delete those files; I would suggest you to use PLINQ for it.
            }
            catch (Exception e)
            {
                return ActionResult.Failure;
            }
            return ActionResult.Success;
        }
    }
}

Saturday, June 9, 2012

Creating Wizard using C# Form and Console (multiple Forms)

This is a simple implementation of Wizard using multiple forms in C#. We can use Wizard templates available but sometimes to have flexibility and control over the operations we can use console application and build workflow to display forms.

For Wizard in order to maintain all the data you can have a separate data layer which if possibly build with Singleton design pattern. Following is the basic example of how to implement workflow of Forms in a console application. It is advisable to refer other examples on net which can be more efficient.

I will appreciate if you give suggestions for more improvement. Use comment section for it.

Implementation:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace SampleExe
{
    class Program
    {
        [DllImport("user32.dll")]
        public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        [DllImport("user32.dll")]
        static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        [STAThread()]
        static void Main()
        {
            int iDlg = 0;
            // Hide console window
            IntPtr hWnd = FindWindow(null, Console.Title);
            if (hWnd != IntPtr.Zero)
                ShowWindow(hWnd, 0);

            // Initialize all the forms
            // For all the forms as required you need to have Next, Previous and Cancel button
            // Set Dialog Result property
            // Next button -> Yes
            // Previous button -> No
            // Cancel button -> Cancel 
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            Form1 X1 = new Form1();
            Form2 X2 = new Form2();
            Form3 X3 = new Form3();
            Form4 X4 = new Form4();
            Form5 X5 = new Form5();
            DialogResult dlForm = DialogResult.Yes;
            while (dlForm != DialogResult.Cancel)
            {
                if (dlForm == DialogResult.Yes)
                {
                    iDlg++;
                    switch (iDlg)
                    {
                        case 1:
                            dlForm = X1.ShowDialog();
                            break;
                        case 2:
                            dlForm = X2.ShowDialog();
                            break;
                        case 3:
                            dlForm = X3.ShowDialog();
                            break;
                        case 4:
                            dlForm = X4.ShowDialog();
                            break;
                        case 5:
                            dlForm = X5.ShowDialog();
                            break;
                        default:
                            dlForm = DialogResult.Cancel;
                            break;
                    }
                }
                else if (dlForm == DialogResult.No)
                {
                    iDlg--;
                    switch (iDlg)
                    {
                        case 1:
                            dlForm = X1.ShowDialog();
                            break;
                        case 2:
                            dlForm = X2.ShowDialog();
                            break;
                        case 3:
                            dlForm = X3.ShowDialog();
                            break;
                        case 4:
                            dlForm = X4.ShowDialog();
                            break;
                        case 5:
                            dlForm = X5.ShowDialog();
                            break;
                        default:
                            dlForm = DialogResult.Cancel;
                            break;
                    }
                }
            }
        }
    }
}
reference:
http://stackoverflow.com/questions/2211514/suppress-command-window-from-started-command-line-application-with-net?rq=1

Saturday, June 2, 2012

XML page using XSLT - one table page with different flavours

For some time now I am working on building a generic XSL which can be used to format different XML as per the defined standard. In this posting will be showing you the basic logic of building the XML and XSL. This has been created manually; will be working further to automate the XML generation as well as XSL generation.

Following screenshots of the webpage that has been generated and following objectives has been achieved in implementation.
  1. One standard XSL for generating formatting multiple XML. 
  2. Display all data in a single page in different ways. 
  3. XML should contain instructions of how data should get displayed and not XSL. 
Page when loaded:












Page when expanded:

Implementation:
I am not describing how it has been implemented but use both the XML and XSL to generate your page. Get your hands dirty and understand it and try to implement it the way you want. Basic things in implementation one Tag bifurcated by Type; how to display data depends upon Type (List/ Record/ Field)

Sample XML
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="TEST_XSL.xsl"?>
<Node Desc="XXX Screen" Type="ReplyScreen">
  <Node Type="List" Desc="General Header" DisplayINList="NO">
    <Node Type="Record" Desc="" BOLD="YES">
      <Node Type="Field">
        <Label1>Name</Label1>
        <Data1>Test Name</Data1>
        <Label2>Status</Label2>
        <Data2>Test Status</Data2>
      </Node>
      <Node Type="Field">
        <Label1>Code</Label1>
        <Data1>XYZ</Data1>
        <Label2>Number</Label2>
        <Data2>*********901</Data2>
      </Node>
    </Node>
  </Node>
  <Node Type="List" Desc="" DisplayINList="YES">
    <Node Type="Record" Desc="Other Details" BOLD="YES">
      <Node Type="Field">
        <Label1>Address Line 1</Label1>
        <Data1>1234 XXXX</Data1>
        <Label2>Address Line 2</Label2>
        <Data2>7890 YYYY</Data2>
      </Node>
      <Node Type="Field">
        <Label1>Name 1</Label1>
        <Data1>TEST NAME 1</Data1>
        <Label2>Name 2</Label2>
        <Data2>TEST NAME 2</Data2>
      </Node>
    </Node>
  </Node>
  <Node Type="List" Desc="XXX Details" DisplayINList="YES">
    <Node Type="Record" Desc="55 - 000003002009 Amount 10.00" BOLD="YES">
      <Node Type="Field">
        <Label1>Code</Label1>
        <Data1>55</Data1>
        <Label2>Number</Label2>
        <Data2>*********009</Data2>
      </Node>
      <Node Type="Field">
        <Label1>Date</Label1>
        <Data1>07/14/03</Data1>
        <Label2>Expire Date</Label2>
        <Data2>99/99/99</Data2>
      </Node>
    </Node>
    <Node Type="Record" Desc="00 - 000000000001 Amount 100.00" BOLD="YES">
      <Node Type="Field">
        <Label1>B1</Label1>
        <Data1>XXX 1</Data1>
        <Label2>B2</Label2>
        <Data2>XXX 2</Data2>
      </Node>
      <Node Type="Field">
        <Label1>B</Label1>
        <Data1>100207</Data1>
        <Label2>Date</Label2>
        <Data2>99/99/99</Data2>
      </Node>
    </Node>
  </Node>
  <Node Type="List" Desc="Name Details" DisplayINList="NO">
    <Node Type="Record" Desc="" BOLD="YES">
      <Node Type="Field">
        <Label1>A1</Label1>
        <Data1>Description 1</Data1>
        <Label2>@#$%^</Label2>
        <Data2>@#$%^</Data2>
      </Node>
      <Node Type="Field">
        <Label1>A2</Label1>
        <Data1>Description 2</Data1>
        <Label2>@#$%^</Label2>
        <Data2>@#$%^</Data2>
      </Node>
    </Node>
  </Node>
  <Node Type="List" Desc="Other Messages" DisplayINList="NO">
    <Node Type="Record" Desc="Message 1" BOLD="NO" />
    <Node Type="Record" Desc="Message 2" BOLD="NO" />
  </Node>
</Node>

Sample XSL
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
  <html>
    <head>
      <script language="JavaScript">
        <xsl:comment>
          <![CDATA[
 
         function GetElemParent(elem)
         {
          var ret = null;
          if(elem.level == 1)
           return ret;
          var prevSibling = elem.previousSibling;
          while(prevSibling)
          {
           if(parseInt(prevSibling.level) == (parseInt(elem.level) - 1))
           {
            ret = prevSibling;
            break;
           }
           prevSibling = prevSibling.previousSibling;
          }
          return ret;
         }
         
         function GetElemChildren(elem)
         {
          var ret = new Array();
          
          var index = 0;
          var nextSibling = elem.nextSibling;
          while(nextSibling)
          {
           if(parseInt(nextSibling.level) == (parseInt(elem.level) + 1))
           {
            ret[index] = nextSibling;
            index++;
           }
           else if(parseInt(nextSibling.level) == parseInt(elem.level))
            break;
           nextSibling = nextSibling.nextSibling;
          }
          return ret;
         }
         
         function ExpandElement(elem)
         {
          var children = GetElemChildren(elem);
          for(var i = 0; i < children.length; i++)
          {
           var child = children[i];
           ShowElement(child);
          }
         }
         
         function CollapseElement(elem)
         {
          var children = GetElemChildren(elem);
          for(var i = 0; i < children.length; i++)
          {
           var child = children[i];
           HideElement(child);
          }
         }
         
         function ShowElement(elem)
         {
          elem.style.display = "";
          if(IsElementExpanded(elem))
          {
           var children = GetElemChildren(elem);
           for(var i = 0; i < children.length; i++)
           {
            var child = children[i];
            ShowElement(child);
           }
          }
         }
         
         function IsElementExpanded(elem)
         {
          var expanded = elem.children(0).expanded;
          if(expanded == undefined)
           return true;
          return expanded;
         }
         
         function HideElement(elem)
         {
          elem.style.display = "none";
          var children = GetElemChildren(elem);
          for(var i = 0; i < children.length; i++)
          {
           var child = children[i];
           HideElement(child);
          }
         }
         
         function DoExpandClick(elem)
         {
          if(window.event.srcElement.tagName == "IMG")
          {
           if(elem.expanded)
           {
            var parent = elem.parentElement;
            CollapseElement(parent);
            
            elem.expanded = false;
            elem.rowSpan = "1";
            elem.getElementsByTagName("IMG")(0).src = "expand.gif";
           }
           else
           {
            var parent = elem.parentElement;
            ExpandElement(parent);
            
            elem.expanded = true;
            elem.rowSpan = elem.rowspancfg;
            elem.getElementsByTagName("IMG")(0).src = "collapse.gif";
           }
          }
         }
         function DoCollapseLoad(elem)
         {
          var children = elem.all;
          for(var i = 0; i < children.length; i++)
          {
           var child = children[i];
           if (child.nodeName == "IMG")
           {
            child.click();
           }
          }
         }
        ]]>//
        </xsl:comment>
      </script>
     <h2 align="center">
       <xsl:apply-templates select="//Node" mode="head"/>
     </h2>
    </head>
    <body style="background-color:#d0e4fe; font-family:'Arial';" onload="DoCollapseLoad(document.firstChild)">
      <table border="0" cellpadding="1" style="border: 1px solid green; border-collapse:collapse; border-separation=0px" cellspacing="0">
        <xsl:apply-templates select="//Node" mode="row"/>
      </table>
    </body>
  </html>
</xsl:template>

<xsl:template match="Node" mode="head">
  <xsl:if test ="@Type='ReplyScreen'">
    <xsl:value-of select="@Desc"/>
  </xsl:if>
</xsl:template>

<xsl:template match="Node" mode="row">
  <xsl:if test ="@Type='Record' and @Desc!=''">
    <tr level="1" style="border:1px solid green;">
      <xsl:apply-templates select="self::Node" mode="popcell"/>
    </tr>
  </xsl:if>
  <xsl:if test ="@Type='Field'">
    <tr level="2">
      <xsl:apply-templates select="self::Node" mode="popcell"/>
    </tr>
  </xsl:if>
  <xsl:if test ="@Type='List' and @Desc!=''">
    <tr level="1">
      <xsl:apply-templates select="self::Node" mode="popcell"/>
    </tr>
  </xsl:if>
</xsl:template>

<xsl:template match="Node" mode="popcell">
  <xsl:if test ="@Type='Record' and ../@DisplayINList='YES'">
    <td valign="top"  expanded="true" onclick="DoExpandClick(this)">
      <xsl:if test="count(@Desc)>0">
        <xsl:variable name="rowspancount" select="count(child::Node) + 1"/>
        <xsl:attribute name ="rowspan">
          <xsl:value-of select="$rowspancount"/>
        </xsl:attribute>
        <xsl:attribute name="rowspancfg">
          <xsl:value-of select="$rowspancount"/>
        </xsl:attribute>
      </xsl:if>
      <xsl:if test="count(@Desc)=0">
        <xsl:variable name="rowspancount" select="count(child::Node)"/>
        <xsl:attribute name ="rowspan">
          <xsl:value-of select="$rowspancount"/>
        </xsl:attribute>
        <xsl:attribute name="rowspancfg">
          <xsl:value-of select="$rowspancount"/>
        </xsl:attribute>
      </xsl:if>
      <img src="./collapse.gif"/>
    </td>
    <xsl:if test="count(@Desc)>0">
      <xsl:if test="@BOLD='YES'">
        <td width="100%" valign="top" style="font-weight: bold; font-size:11pt; border:1px solid green;" colspan="4" bgcolor="#BDBDBD">
          <xsl:value-of select="@Desc"/>
        </td>
      </xsl:if>
      <xsl:if test="@BOLD='NO'">
        <td width="100%" valign="top" colspan="4" bgcolor="#F2F2F2" style="font-size:11pt; border:1px dotted green;"><!--assuming that data will display--> 
          <xsl:value-of select="@Desc"/>
        </td>
      </xsl:if>
    </xsl:if>
  </xsl:if>
  <xsl:if test ="@Type='Record' and ../@DisplayINList='NO'">
    <td valign="top"  expanded="true">
      <xsl:if test="@Desc!=''">
        <xsl:variable name="rowspancount" select="count(child::Node) + 1"/>
        <xsl:attribute name ="rowspan">
          <xsl:value-of select="$rowspancount"/>
        </xsl:attribute>
        <xsl:attribute name="rowspancfg">
          <xsl:value-of select="$rowspancount"/>
        </xsl:attribute>
      </xsl:if>
      <xsl:if test="@Desc=''">
        <xsl:variable name="rowspancount" select="count(child::Node)"/>
        <xsl:attribute name ="rowspan">
          <xsl:value-of select="$rowspancount"/>
        </xsl:attribute>
        <xsl:attribute name="rowspancfg">
          <xsl:value-of select="$rowspancount"/>
        </xsl:attribute>
      </xsl:if>
    </td>
    <xsl:if test="@Desc!=''">
      <xsl:if test="@BOLD='YES'">
        <td width="100%" valign="top" colspan="4" bgcolor="#BDBDBD" style="font-weight: bold; font-size:11pt; border:1px solid green;">
          <xsl:value-of select="@Desc"/>
        </td>
      </xsl:if>
      <xsl:if test="@BOLD='NO'">
        <td width="100%" valign="top" colspan="4" bgcolor="#F2F2F2" style="font-size:11pt; border:1px dotted green;">
          <xsl:value-of select="@Desc"/>
        </td>
      </xsl:if>
    </xsl:if>
  </xsl:if>
  <xsl:if test ="@Type='Field'">
    <xsl:if test ="Label1[.!=''] and Data1[.!=''] and Label2[.!=''] and Label2[.!='@#$%^'] and Data2[.!=''] and Data2[.!='@#$%^']">
 <xsl:if test ="../@Type='Record' and ../@Desc=''">
  <td></td>
 </xsl:if>
      
   <td width="25%" valign="top" bgcolor="#D8D8D8" style="font-weight: bold; font-size:11pt; border:1px solid green;">
        <xsl:value-of select="Label1"/>
      </td>
      <td width="25%" valign="top" bgcolor="#F2F2F2" style="font-size:11pt; border:1px solid green;">
        <xsl:value-of select="Data1"/>
      </td>
      <td width="25%" valign="top" bgcolor="#D8D8D8" style="font-weight: bold; font-size:11pt; border:1px solid green;">
        <xsl:value-of select="Label2"/>
      </td>
      <td width="25%" valign="top" bgcolor="#F2F2F2" style="font-size:11pt; border:1px solid green;">
        <xsl:value-of select="Data2"/>
      </td>
    </xsl:if>
    <xsl:if test ="Label1[.!=''] and Data1[.!=''] and Label2[.='@#$%^'] and Data2[.='@#$%^']">
 <xsl:if test ="../@Type='Record' and ../@Desc=''">
  <td></td>
 </xsl:if>
      
   <td width="25%" valign="top" bgcolor="#D8D8D8" style="font-weight: bold; font-size:11pt; border:1px solid green;">
        <xsl:value-of select="Label1"/>
      </td>
      <td width="75%" valign="top" bgcolor="#F2F2F2" colspan="3" style="font-size:11pt; border:1px solid green;">
        <xsl:value-of select="Data1"/>
      </td>
    </xsl:if>
  </xsl:if>
  <xsl:if test ="@Type='List'">
    <td></td>
    <td width="100%" valign="top" style="font-weight: bold; font-size:11pt; border:1px solid green;" colspan="4" bgcolor="#A9BCF5">
      <xsl:value-of select="@Desc"/>
    </td>
  </xsl:if>
</xsl:template>
</xsl:stylesheet>

Wednesday, May 16, 2012

Send mail using Microsoft Outlook



Already posted how to mail using SMTP; but now in this implemenation will show how to send mail using Microsoft Outlook object.
But there a basic rule of these implementation:
  1. Create the required objects.
  2. Add recepient, main subject, mail body, attachment.
  3. Send mail and validate.
  4. Release resources.
Implementation:
using Outlook = Microsoft.Office.Interop.Outlook;

public void SendTestMail()
{
    try
    {
        Outlook.Application otApp = new Outlook.Application();// create outlook object
        Outlook.NameSpace ns = otApp.Session;
        Outlook.MailItem otMsg = (Outlook.MailItem)otApp.CreateItem(Outlook.OlItemType.olMailItem); // Create mail object
        Outlook.Inspector oInspector = otMsg.GetInspector;
        Outlook.Recipient otRecip = (Outlook.Recipient)otMsg.Recipients.Add("abc@xyz.com");
        otRecip.Resolve();// validate recipient address
        otMsg.Subject = "test subject";
        otMsg.Body = "text message";
        String sSource = "C:\\Test.txt";
        String sDisplayName = "TestAttachment";
        int iPos = (int)otMsg.Body.Length + 1;
        int iAttType = (int)Outlook.OlAttachmentType.olByValue;  
        Outlook.Attachment oAttach = otMsg.Attachments.Add(sSource,iAttType,iPos,sDisplayName); // add attachment
        otMsg.Save();
        otMsg.Send(); // Send Mail
        otRecip = null;
        otAttach = null;
        otMsg = null;
        otApp = null;
    }
    catch (Exception e)
    {
        Console.WriteLine("{0} Exception caught: ", e);
    }
    return 0;
}

Thursday, May 10, 2012

New Features in .NET Framework 3.5 for C# Form

I tried to collect all the relevant improvements regarding .NET 3.5 w.r.t. forms for my quick reference. I will be glad if someone also gets value from it. All the information below are from MSDN.

Collections
HashSet<T> provides high performance set operations to the .NET Framework. A set is a collection that contains no duplicate elements, and whose elements are in no particular order.

Diagnostics
The EventSchemaTraceListener class provides tracing of end-to-end, schema-compliant events. You can use end-to-end tracing for a system that has heterogeneous components that cross thread, AppDomain, process, and computer boundaries. A standardized event schema (see Event Representation for Event Consumers) has been defined to enable tracing across these boundaries. This schema is shared by various tracing technologies, including Windows Vista diagnostics tools such as Event Viewer. The schema also enables the addition of custom, schema-compliant elements.

The EventSchemaTraceListener class is tuned for logging performance with implicit support for lock-free tracing.

I/O and Pipes
Pipes provide inter-process communication between any processes running on the same computer, or on any other Windows computer within a network. The .NET Framework provides access to two types of pipes: anonymous pipes and named pipes. For more information about pipes.

Garbage Collection
The GCSettings class has a new LatencyMode property that you can use to adjust the time that the garbage collector intrudes in your application. You set this property to one of the values of the new GCLatencyMode enumeration.

The GC class has a new Collect(Int32, GCCollectionMode) method overload that you can use to adjust the behavior for a forced garbage collection. For example, you can use this overload to specify that the garbage collector should determine whether the current time is optimal to reclaim objects. This overload takes a value from the new GCCollectionMode enumeration.

Reflection and Reflection Emit in Partial Trust
Assemblies that run with partial trust can now emit code and execute it. Emitted code that calls only public types and methods needs no permissions beyond the permissions demanded by the types and methods that are accessed. The new DynamicMethod(String, Type, Type[]) constructor makes it easy to emit such code.

When emitted code needs to access private data, the new DynamicMethod(String, Type, Type[], Boolean) constructor allows restricted access. The host must grant ReflectionPermission with the new RestrictedMemberAccess flag to enable this feature, which gives emitted code the ability to access private data only for types and methods in assemblies with equal or lesser trust levels. See Walkthrough: Emitting Code in Partial Trust Scenarios.

For reflection, a host grant of RestrictedMemberAccess similarly allows restricted use of methods that access private properties, call private methods, and so on, but only for target assemblies with equal or lesser trust levels.

Threading
1. Better Reader/Writer Lock
The new ReaderWriterLockSlim class provides performance that is significantly better than ReaderWriterLock, and comparable with the lock statement (SyncLock in Visual Basic). Transitions between lock states have been simplified to make programming easier and to reduce the chances of deadlocks. The new class supports recursion to simplify migration from lock and fromReaderWriterLock.

2. ThreadPool Performance Enhancements
Throughput for the dispatch of work items and I/O tasks in the managed thread pool is significantly improved. Dispatch is now handled in managed code, without transitions to unmanaged code and with fewer locks. The use of ThreadPool is recommended over application-specific thread pool implementations.

3. Time Zone Improvements
Two new types, DateTimeOffset and TimeZoneInfo, improve support for time zones and make it easier to develop applications that work with dates and times in different time zones. For a discussion of which type to use in particular situations, see Choosing Between DateTime, DateTimeOffset, and TimeZoneInfo.

4. TimeZoneInfo
The new TimeZoneInfo class largely supplants the existing TimeZone class. You can use TimeZoneInfo to retrieve any time zone defined in the registry, rather than just the local time zone and Coordinated Universal Time (UTC). You can also use this class to define custom time zones, to serialize and deserialize custom time zone data, and to convert times between time zones. For more information about developing applications that use the TimeZoneInfo class, see Times and Time Zones.

5. DateTimeOffset
The new DateTimeOffset structure extends the DateTime structure to make working with times across time zones easier. The DateTimeOffset structure stores date and time information as a UTC date and time together with an offset value that indicates how much the time differs from UTC.

ClickOnce Improvements
Several improvements have been made to ClickOnce. Improvements include deployment from multiple locations and third-party branding. For more information, see Deploying ClickOnce Applications without Resigning and Creating ClickOnce Applications for Others to Deploy.

The Mage.exe tool, which is sometimes used together with ClickOnce, has been updated for the .NET Framework 3.5. For more information, see Manifest Generation and Editing Tool (Mage.exe).

ClickOnce Manifests
There are new cryptography classes for verifying and obtaining information about manifest signatures for ClickOnce applications. The ManifestSignatureInformation class obtains information about a manifest signature when you use its VerifySignature method overloads. You can use the ManifestKinds enumeration to specify which manifests to verify. The result of the verification is one of the SignatureVerificationResult enumeration values. The ManifestSignatureInformationCollection provides a read-only collection of ManifestSignatureInformation objects of the verified signatures.

In addition, the following classes provide specific signature information:
StrongNameSignatureInformation Holds the strong name signature information for a manifest.
AuthenticodeSignatureInformation Represents the Authenticode signature information for a manifest.
TimestampInformation Contains information about the time stamp on an Authenticode signature.
TrustStatus Provides a simple way to check whether an Authenticode signature is trusted.

Suite B Support
The .NET Framework 3.5 supports the Suite B set of cryptographic algorithms published by the National Security Agency (NSA). For the NSA documentation, see www.nsa.gov/ia/industry/crypto_suite_b.cfm.

The following algorithms are included:
  • Advanced Encryption Standard (AES) with key sizes of 128 and 256 bits for encryption.
  • Secure Hash Algorithm (SHA-256 and SHA-384) for hashing.
  • Elliptic Curve Digital Signature Algorithm (ECDSA) using curves of 256-bit and 384-bit prime moduli for signing. This algorithm is provided by the ECDsaCng class. It allows you to sign with a private key and verify with a public key.
  • Elliptic Curve Diffie-Hellman (ECDH) using curves of 256 and 384-bit prime moduli for key exchange/secret agreement. This algorithm is provided by the ECDiffieHellmanCng class.

Authentication, Roles, and Settings Services
Client application services are new in the .NET Framework 3.5 and enable Windows-based applications (including Windows Forms and Windows Presentation Foundation applications) to easily access the ASP.NET login, roles, and profile services. These services enable you to authenticate users and retrieve user roles and application settings from a shared server.

You can enable client application services by specifying and configuring client service providers in your application configuration file or in the Visual Studio Project Designer. These providers plug into the Web extensibility model and enable you to access the Web services through existing .NET Framework login, roles, and settings APIs. Client application services also support occasional connectivity by storing and retrieving user information from a local data cache when the application is offline.

For more information, see Client Application Services.

Windows Vista Support
Existing Windows Forms applications work seamlessly on Windows Vista, and they are upgraded to have the same appearance as applications written specifically for Windows Vista whenever possible. Common file dialog boxes are automatically updated to the Windows Vista version. The .NET Framework 3.5 also supports the User Account Control (UAC) Shield icon. For more information, see FileDialog Class and Shield.

WPF support
You can use Windows Forms to host Windows Presentation Foundation (WPF) controls and content together with Windows Forms controls. You can also open WPF windows from a Windows Form. For more information about how to use Windows Forms and WPF together, see Migration and Interoperability.

LINQ
LINQ extends powerful query capabilities to the language syntax of C# and Visual Basic in the form of standard, easily-learned query patterns. This technology can be extended to support potentially any kind of data store. The .NET Framework 3.5 includes LINQ provider assemblies that enable the use of LINQ for querying .NET Framework collections, SQL Server databases, ADO.NET Datasets, and XML documents.

The components of LINQ that are part of the .NET Framework 3.5 are:
  • The System.Linq namespace, which contains the set of standard query operators and types and interfaces that are used in the infrastructure of a LINQ query. This namespace is in the System.Core.dll assembly.
  • The System.Data.Linq namespace, which contains classes that support interaction with relational databases in LINQ to SQL applications.
  • The System.Data.Linq.Mapping namespace, which contains classes that can be used to generate a LINQ to SQL object model that represents the structure and content of a relational database.
  • The System.Xml.Linq namespace, which contains the classes for LINQ to XML. LINQ to XML is an in-memory XML programming interface that enables you to modify XML documents efficiently and easily. Using LINQ to XML, you can load XML, serialize XML, create XML trees from scratch, manipulate in-memory XML trees, and validate by using XSD. You can also use a combination of these features to transform XML trees from one shape into another.
  • New types in System.Web.UI.WebControls and System.Web.UI.Design.WebControls namespaces. These new types, such as LinqDataSource, support the use of LINQ in ASP.NET Web pages through a data source control.
  • The DataRowComparer, DataRowExtensions, and DataTableExtensions classes in the System.Data namespace support LINQ queries against ADO.NET DataSet objects.






Wednesday, May 9, 2012

Execute functions dynamically using Reflection


Using Reflection you can execute any methods or constructor dynamically. This is something on the line of functional pointer but with some improvement. One of the notable improvements is unlike functional pointer you can pass the method name in string variable to execute.

There are couple of ways to do that and both the ways are described below.

What’s the use of it – for me I had a specific requirement of retrieving list of data in List for further use. But this implementation needed and generic and user friendly approach. In future if there is a requirement of separate list then it can be added easily.

Implementation

Functionality for creating list

public class TestClass
{
      void _LIST(List<string[]> lList, string sNumber, string sAlphaData)
      {
            string[] sData = new string[2];
            sData[0] = sNumber;
            sData[1] = sAlphaData;
            lList.Add(sData);
      }
//Sample functions which will be called dynamically
//If you observe the functions for adding a new list user need to add new function in the format
//below and using a generic function call it dynamically
      public void function_1(List<string[]> lTestList)
      {
            _LIST(lTestList, "0", "Test 1");
            _LIST(lTestList, "2", "Test 2");
      }
      public void function_2(List<string[]> lTestList)
      {
            _LIST(lTestList, "A", "Alpha 1");
            _LIST(lTestList, "B", "Alpha 2");
      }
}

Generic function which has one parameter (string) in which function name will be passed.
This generic function can be implemented in couple of ways:

Using Activator
public string ExecuteFunction(string sFunationName)
{
      List<string[]> lTestList = new List<string[]>();
      Type FuncType = this.GetType();
      object[] mParam = new object[] { lTestList };
      object obj = Activator.CreateInstance(FuncType);
      FuncType.InvokeMember(sFunationName, BindingFlags.InvokeMethod, null, obj, mParam);
      // at this point lTestList get populated with data which can be used as per user discretion
}

Using Reflection
public string ExecuteFunction(string sFunationName)
{
      TestClass testClass = new TestClass();
      List<string[]> lTestList = new List<string[]>();
      Type FuncType = typeof(TestClass);
      MethodInfo miFunationName = FuncType.GetMethod(sFunationName);
      object[] mParam = new object[] { lTestList };
      miFunationName.Invoke(testClass, mParam);
      // at this point lTestList get populated with data which can be used as per user discretion
}

Thursday, May 3, 2012

WiX Research - Building Installer using WiX Sharp partially


Looking beyond WiX Sharp…

Here is another implementation of WiX as a part of on-going research. In the following implementation I have used few features of WiX Sharp and predominantly WiX to build an installer (.msi). This is unlike the previous implementation where I have user WiX Sharp only.

What is the requirement of further refining the implementation? 
And what is the requirement of moving away from WiX Sharp?

I see there are few shortcomings using WiX Sharp:
  • There is a dependency on third party tool (WiX Sharp) which may go against proprietary norms of the company.
  • There are many features implemented or can be tweaked in WiX as per user requirement; which may not possible if we are heavily dependent in WiX Sharp.
  • Having your own middle tier will give more control over your implementation.


Note :- I am really a fan of WiX Sharp and see value it has but on the other hand when thought of it rationally compelled me to look beyond WiX Sharp.

Salient features of the implementation:
  • Building .wxs file using WiX Sharp.
  • Integrating custom installation path using System.XML.LINQ.
  • Executing Candle.exe and Light.exe using System.Diagnostics.Process to build the installer.


Feedback are appreciated to improve implementation and if I am missing out anything.

ExeEXE – Standard function to call System.Diagnostics.Process to execute EXE and get the return value.

public bool ExeEXE(string sApplication, string sAppArgs)
{
    System.Diagnostics.Process AppProcess = new System.Diagnostics.Process();
    AppProcess.StartInfo.FileName = sApplication;
    AppProcess.StartInfo.Arguments = sAppArgs;
    AppProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
    AppProcess.Start();
    AppProcess.WaitForExit();
    if (AppProcess.ExitCode > 0)
        Errorobj = "Error excounter while executing " + sApplication + " in " + this.ToString();
    else
        Errorobj = null;
    return (AppProcess.ExitCode == 0) ? true : false;
}

InsertCustomPath – This function insert Custom action in .wxs file for assigning custom path

private void InsertCustomPath()
{
    XDocument Doc = XDocument.Load(Environment.CurrentDirectory + @"\Test Product.wxs");
    if (Doc.Root != null)
    {
        XNamespace xNS = Doc.Root.GetDefaultNamespace();
        XElement xCustAct = new XElement(xNS + "CustomAction",
                                         new XAttribute("Id", "CustActSetINSTALLDIR"),
                                         new XAttribute("Return", "check"),
                                         new XAttribute("Execute", "firstSequence"),
                                         new XAttribute("Property", "TARGETDIR"),
                                         new XAttribute("Value", sInstDir));
        XElement xUI = new XElement(xNS + "UI",
                                    new XElement(xNS + "InstallUISequence",
                                                 new XElement(xNS + "Custom",
                                                              new XAttribute("Action", "CustActSetINSTALLDIR"),
                                                              new XAttribute("After", "ValidateProductID"), "TARGETDIR=\"\"")));
        XElement xExeSeq = new XElement(xNS + "InstallExecuteSequence",
                                        new XElement(xNS + "Custom",
                                                     new XAttribute("Action", "CustActSetINSTALLDIR"),
                                                     new XAttribute("After", "ValidateProductID"), "TARGETDIR=\"\""));
        XNode Node = Doc.Root.FirstNode;
        while (Node != null)
        {
            if (((XElement)Node).Name.LocalName == "Product")
            {
                XNode ChildNode = ((XElement)Node).FirstNode;
                while (ChildNode != null)
                {
                    if (((XElement)ChildNode).Name.LocalName == "Directory")
                    {
                        ChildNode.AddAfterSelf(xCustAct);
                    }
                    else if (((XElement)ChildNode).Name.LocalName == "Upgrade")
                    {
                        ChildNode.AddAfterSelf(xExeSeq);
                        ChildNode.AddAfterSelf(xUI);
                    }
                    ChildNode = ChildNode.NextNode;
                }
            }
            Node = Node.NextNode;
        }
    }
    Doc.Save(Environment.CurrentDirectory + @"\Test Product.wxs");
}

BuildInstaller – Main function to build .wxs and then execute Candle.exe and Light.exe.

public object BuildInstaller()
{
    try
    {
        WixEntity[] weDir1 = new WixEntity[0];
        weDir1 = BuildDirInfo(sRootDir, weDir1); // Implementation already been described in previous post
        var project = new Project("Test Product", new Dir("Test Product", weDir1))
        {
            GUID = Guid.NewGuid(),
            UI = WUI.WixUI_InstallDir,
            Manufacturer = "Test Company",
            Version = new Version(1, 1, 1, 0),
            LicenceFile = Directory.GetCurrentDirectory() + @"\EULA.rtf",
            BannerImage = Directory.GetCurrentDirectory() + @"\Banner.bmp",
            BackgroundImage = Directory.GetCurrentDirectory() + @"\Background.bmp",
            UpgradeCode = guidUpgradeCode,
            MajorUpgradeStrategy = new MajorUpgradeStrategy
            {
                UpgradeVersions = VersionRange.OlderThanThis,
                PreventDowngradingVersions = VersionRange.NewerThanThis,
                NewerProductInstalledErrorMessage = "Newer version already installed"
            }
        };
        
        // Building .wxs file using WixSharp.dll
        Compiler.BuildWxs(project, Environment.CurrentDirectory + @"\Test Product.wxs");
        
        InsertCustomPath();// Inserting Custom action for setting custom installation path
        
        //Executing Candle.exe and Light.exe using System.Diagnostics.Process
        string sCommandLine = "\"" + Environment.CurrentDirectory + "\\Test Product.wxs\" -out \"" + Environment.CurrentDirectory + "\\Test Product.wixobj\"";
        if (ExeEXE(Environment.CurrentDirectory + @"\wixbin\bin\Candle.exe", sCommandLine))
        {
            System.IO.File.Delete(Environment.CurrentDirectory + "\\Test Product.wxs");
            sCommandLine = "\"" + Environment.CurrentDirectory + "\\Test Product.wixobj\" -ext WixUIExtension -out \"" + sInstLoc + "\\Test Product.msi\"";
            if (!ExeEXE(Environment.CurrentDirectory + @"\wixbin\bin\Light.exe", sCommandLine))
            {
                obRtn = "ERROR: " + (string)Errorobj;
                return obRtn;
            }
            System.IO.File.Delete(Environment.CurrentDirectory + "\\Test Product.wixobj");
            System.IO.File.Delete(sInstLoc + "\\Test Product.wixpdb");
        }
        else
        {
            obRtn = "ERROR: " + (string)Errorobj;
            return obRtn;
        }
        return obRtn;
    }
    catch (Exception w)
    {
        MessageBox.Show(w.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    return null;
}

Tuesday, May 1, 2012

WiX Research - Building Installer (.msi) using WiX Sharp

This is a part of series of research which I am doing for finding out a better way to create and Installer. Just building an installer is not something I am looking into, I am looking forward to build a installer which is efficient and easy to maintain (Installer as well as source code).

Objective
Using WiXSharp build installer (.msi) with following objective fulfilled:
  • Build installer of a folder location specified.
  • Installer should able to upgrade and not allow downgrade.

Set Environment Variable
Before doing anything using WiXSharp need to set environment variables which will ack as beacon for WiX bin. Call this somewhere like while initializing the class.

public void ReBuildInstallerEnvironment()
{
    Environment.SetEnvironmentVariable("WIXSHARP_WIXDIR", Environment.CurrentDirectory + @"\Wix_bin\bin", EnvironmentVariableTarget.Process);
    Environment.SetEnvironmentVariable("WIXSHARP_DIR", Environment.CurrentDirectory, EnvironmentVariableTarget.Process);
}

There are quiet a few ways to create .msi using WiXSharp but after doing some research found that rather than directly assigning data in the WiXSharp Project it is better create WixEntity objects are pass it to the Project.

Create WixEntity[] 
To build installer using WiXSharp need to create WixEntity[] of the folder and its files within. To achieve that created following recursive code.

private WixEntity[] BuildDirInfo(string sRootDir, WixEntity[] weDir)
{
    DirectoryInfo RootDirInfo = new DirectoryInfo(sRootDir);
    if (RootDirInfo.Exists)
    {
        DirectoryInfo[] DirInfo = RootDirInfo.GetDirectories();
        List<string> lMainDirs = new List<string>();
        foreach (DirectoryInfo DirInfoSub in DirInfo)
            lMainDirs.Add(DirInfoSub.FullName);
        int cnt = lMainDirs.Count;
        weDir = new WixEntity[cnt + 1];
        if (cnt == 0)
            weDir[0] = new DirFiles(RootDirInfo.FullName + @"\*.*");
        else
        {
            weDir[cnt] = new DirFiles(RootDirInfo.FullName + @"\*.*");
            for (int i = 0; i < cnt; i++)
            {
                DirectoryInfo RootSubDirInfo = new DirectoryInfo(lMainDirs[i]);
                if (!RootSubDirInfo.Exists) 
                    continue;
                WixEntity[] weSubDir = new WixEntity[0];
                weSubDir = BuildDirInfo(RootSubDirInfo.FullName, weSubDir);
                weDir[i] = new Dir(RootSubDirInfo.Name, weSubDir);
            }
        }
    }
    return weDir;
}

Build Installer
Now create your own function to build .msi using WiXSharp.
WiXSharp is a very easy to implement utility which has been built over WiX.

void BuildInstaller()
{
       WixEntity[] weDir = new WixEntity[0];
       weDir = BuildDirInfo(sRootDir, weDir);
       var project = new Project("Test Product", new Dir("TestProduct", weDir))
       {
              GUID = Guid.NewGuid(),
              UI = WUI.WixUI_InstallDir,
              Manufacturer = "TestCompany",
              Version = new Version(12, 1, 1, 0),
              LicenceFile = Directory.GetCurrentDirectory() + @"\EULA.rtf",
              BannerImage = Directory.GetCurrentDirectory() + @"\Banner.bmp",
              BackgroundImage = Directory.GetCurrentDirectory() + @"\Background.bmp",
              UpgradeCode = guidUpgradeCode, // Forwarded if upgrading existing product
              MajorUpgradeStrategy = new MajorUpgradeStrategy
              {
                     UpgradeVersions = VersionRange.OlderThanThis,
                     PreventDowngradingVersions = VersionRange.NewerThanThis,
                     NewerProductInstalledErrorMessage = "Newer version already installed"
              }
       };
       Compiler.BuildMsi(project, sInstLoc + @"\Test Product.msi");
}

There is nothing much into it just you need to know how to play with WixEntity[] and WixEntity class objects.

I will be working on different aspects if WiX in future...

Saturday, April 28, 2012

Fetch DLL information for storage (SQL Server)


Title says it all, following is the implementation I did some time back using SQL server. There are surely better implementations than this and will appreciate if you can give your feedback regarding this.

Objective:
To retrieve information of all DLLs in a specific location and store in database.

Reason:
There are many ways this information can be utilized:
  • Keep record of DLL information on a day to day basis
  • Use it to compare DLL file changes of different dates


I had a specific requirement of tracking DLLs but you can use it as per your requirement.

Implementation:
SQL table structure – following are the table columns in table

Table_1 – to store DLL infoamtion
  • UniqueIdentifier – You can use GUID for storing record at a specific instance
  • Name – DLL name
  • Version – DLL version (X.X.X.X)
  • ModifiedDt – Last modified date
  • FileSize – DLL file size


Table_2 – to store this instance details (this is something can be used to display in a list – 1 instance per import instance)
  • UniqueIdentifier – GUID (this should be same that stored in above table)
  • Date – Date of import
  • Time – Time of import
  • Product Version – something optional
  • User Name – User who performed
  • Computer Name – Network location which has been used for operation
  • Comment – User comment if any
  • Network Path – Location from where information has been retrieved


There are couple of salient features in the implementation I want to highlight. Our team is scattered across globe and need to have an implementation which is efficient
  • For storing all the file information in table individually in different rows executing only one SQL query (this reduced execution time to half). For adding multiple rows in a single query used “UNION ALL”.
  • There is a observation that using default time stamp lead to different time logged for same file accessed from different location. For that used TimeZoneInfo class object and set it to PST. Then before storing the date converted time in that time zone.


Implemented Code:


public void StoreDLLFileInfo()
{
    MainScreen Main = (MainScreen)Application.OpenForms["MainScreen"]; // Open form instance
    string sQueryLine = "", sFileSize = "";
    bool bFirstTime = false;
    // tbPath - text box which stored and display the path
    // tbComment - text box which stored and display user comment
    // SqlConnection SQLConn maintains the SQL connection instance for firing query
    if (SQLConn.State == ConnectionState.Closed)
        SQLConn.Open();
    if (Directory.Exists(tbPath.Text))// first basic validation to check directory
    {
        // Operation 1 - to add single entry of the import operation
        DateTime SysTime = DateTime.Now.Date;
        string[] sDate = SysTime.GetDateTimeFormats('s');
        string sTime11 = DateTime.Now.TimeOfDay.ToString();
        string sUID = Guid.NewGuid().ToString();
        try
        {
            // fire query to add single entry for the import instance
            SQLCommand = SQLConn.CreateCommand();
            SQLCommand.CommandText = "INSERT INTO Table_2 values ('" + sUID + "', CONVERT(datetime, '" + sDate[0] + "', 126), '" + sTime11.Substring(0, 8) + "', '" + "Product 1.0.0" + "', '" + SystemInformation.UserName.ToString() + " (" + SystemInformation.ComputerName.ToString() + ") - " + tbComment.Text + "', '" + tbPath.Text + "')";
            SQLCommand.ExecuteNonQuery();
        }
        catch (SqlException w)
        {
            MessageBox.Show(w.Message, "SQL Connection Error", MessageBoxButtons.OK, MessageBoxIcon.Hand);
            return;
        }
        // Operation 2 - to store DLL information of all DDLs in the stated directory
        string sVersion = "", sName = "";
        DateTime dtWriteDate;
        if (SQLConn.State == ConnectionState.Closed)
            SQLConn.Open();

        DirectoryInfo DirInfo = new DirectoryInfo(tbPath.Text);
        FileVersionInfo FileVerInfo;
        TimeZoneInfo hwZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");// assign specific time zone
        FileInfo[] FileDet = DirInfo.GetFiles("*.dll");// retrieving file information of DLLs only
        iFileCnt = FileDet.Count();
        iFlCntNow = 0;
        foreach (FileInfo File in FileDet)
        {
            FileVerInfo = FileVersionInfo.GetVersionInfo(File.FullName);
            sVersion = FileVerInfo.FileVersion;
            if (sVersion == null)
                sVersion = "N/A";
            sVersion.Replace(",", ".");// DLL version
            sName = File.Name;// DLL Name
            dtWriteDate = File.LastWriteTime;// DLL Last write time
            sFileSize = File.Length.ToString();// DLL file size
            if (bFirstTime)
            {
                sQueryLine += " UNION ALL ";
            }
            bFirstTime = true;
            dtWriteDate = TimeZoneInfo.ConvertTime(dtWriteDate, hwZone);
            sQueryLine += "SELECT '" + sUID + "', '" + sName.ToLower() + "', '" + sVersion.Replace(",", ".") + "', '" + dtWriteDate.GetDateTimeFormats('u')[0].Substring(0, 16) + "', '" + sFileSize + "'";
        }
        try
        {
            Main.Cursor = Cursors.WaitCursor;
            SQLCommand = SQLConn.CreateCommand();
            SQLCommand.CommandText = "INSERT INTO Table_1 (UniqueIdentifier, Name, Version, ModifiedDt, FileSize) " + sQueryLine;
            SQLCommand.ExecuteNonQuery();
            Main.Cursor = Cursors.Default;
        }
        catch (SqlException w)
        {
            MessageBox.Show(w.Message, "SQL Connection Error", MessageBoxButtons.OK, MessageBoxIcon.Hand);
        }
    }
}