Thursday, October 17, 2013

Using WiX installers the right way

Wix is the way to go for creating installers for your visual studio projects. Because Wix is rather complex to start with, I want to give you a working example so you can start building your own installers. You can also skip this short description and go directly to the source code.

What we will setup in this post is:
  1. Heat.exe (automatically include your dll files)
  2. Transformations (automatically edit the file generated by heat.exe)
  3. Service installers (register your windows service)
  4. Custom forms (manipulate your config file)
First off, we need to setup head.exe so it automatically detects when new dll's are added to your service. To do that, we need to edit the pre-build event:
$(WIX)bin\heat.exe" dir "$(SolutionDir)MyWindowsService\$(OutDir)." -srd -sreg -nologo -scom -srd -sfrag -ag -cg "Binaries" -dr "INSTALLDIR" -t "$(ProjectDir)XslTransform.xslt" -var var.MyWindowsService.TargetDir -o "../../Binaries.wxs

This will generate a Binaries.wxs in your installer dir.
The next trick is to use transformations to edit the generated file. For example: we want to install a windows service and remove vshost.exe files. This is done with the XslTransform.xslt file.

The following example creates a service:
<xsl:template match="wix:Component[wix:File[@Source='$(var.MyWindowsService.TargetDir)\MyWindowsService.exe']]">
 <xsl:copy>
  <xsl:apply-templates select="node() | @*" />
  <wix:ServiceInstall Id="MyServiceInstall" DisplayName="[SERVICENAME]" Description="[SERVICENAME]" Name="[SERVICENAME]" ErrorControl="ignore" Start="auto" Type="ownProcess" Vital="yes" Interactive="no" Account="[SERVICEUSER]" Password="[SERVICEPSWD]" />
  <wix:ServiceControl Id="MyServiceControl" Name="[SERVICENAME]" Start="install" Stop="both" Remove="uninstall" Wait="yes"/>
  <util:User Id="user" CreateUser="no" Name ="[SERVICEUSER]" Password="[SERVICEPSWD]" LogonAsService="yes" />
 </xsl:copy>
</xsl:template>
After installation, the service is running with the parameters from the product.wxs file.

The following example manipulates the app.config file:
<xsl:template match="wix:Component[wix:File[@Source='$(var.MyWindowsService.TargetDir)\MyWindowsService.exe.config']]">
 <xsl:copy>
  <xsl:apply-templates select="node() | @*" />
  <util:XmlFile Id="AppConfigSetSetting1" File="[INSTALLDIR]MyWindowsService.exe.config" Action="setValue" Name="value" Value="[SETTING1]" ElementPath="//configuration/appSettings/add[\[]@key='setting1'[\]]" Sequence="1" />
  <util:XmlFile Id="AppConfigSetSetting2" File="[INSTALLDIR]MyWindowsService.exe.config" Action="setValue" Name="value" Value="[SETTING2]" ElementPath="//configuration/appSettings/add[\[]@key='setting2'[\]]" Sequence="1" />
 </xsl:copy>
</xsl:template>
Custom forms is a whole different deal. You need to copy code from the original source and add it to your installer. No need to worry, I've got a good basic installer flow defined below. It is based on the WixUI_Common template without the license window.
<Fragment>
 <UI Id="CustomInstallerUI">
  <TextStyle Id="WixUI_Font_Normal" FaceName="Tahoma" Size="8" />
  <TextStyle Id="WixUI_Font_Bigger" FaceName="Tahoma" Size="12" />
  <TextStyle Id="WixUI_Font_Title" FaceName="Tahoma" Size="9" Bold="yes" />

  <Property Id="DefaultUIFont" Value="WixUI_Font_Normal" />
  <Property Id="WixUI_Mode" Value="InstallDir" />

  <DialogRef Id="SettingsDlg" />
  <DialogRef Id="BrowseDlg" />
  <DialogRef Id="DiskCostDlg" />
  <DialogRef Id="ErrorDlg" />
  <DialogRef Id="FatalError" />
  <DialogRef Id="FilesInUse" />
  <DialogRef Id="MsiRMFilesInUse" />
  <DialogRef Id="PrepareDlg" />
  <DialogRef Id="ProgressDlg" />
  <DialogRef Id="ResumeDlg" />
  <DialogRef Id="UserExit" />

  <Publish Dialog="BrowseDlg" Control="OK" Event="DoAction" Value="WixUIValidatePath" Order="3">1</Publish>
  <Publish Dialog="BrowseDlg" Control="OK" Event="SpawnDialog" Value="InvalidDirDlg" Order="4"><![CDATA[WIXUI_INSTALLDIR_VALID<>"1"]]></Publish>

  <Publish Dialog="ExitDialog" Control="Finish" Event="EndDialog" Value="Return" Order="999">1</Publish>

  <Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg">NOT Installed</Publish>
  <Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg">Installed AND PATCH</Publish>

  <Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg">1</Publish>
  <Publish Dialog="InstallDirDlg" Control="Next" Event="SetTargetPath" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish>
  <Publish Dialog="InstallDirDlg" Control="Next" Event="DoAction" Value="WixUIValidatePath" Order="2">NOT WIXUI_DONTVALIDATEPATH</Publish>
  <Publish Dialog="InstallDirDlg" Control="Next" Event="SpawnDialog" Value="InvalidDirDlg" Order="3"><![CDATA[NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"]]></Publish>
  <Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="SettingsDlg" Order="4">WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1"</Publish>
  <Publish Dialog="InstallDirDlg" Control="ChangeFolder" Property="_BrowseProperty" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish>
  <Publish Dialog="InstallDirDlg" Control="ChangeFolder" Event="SpawnDialog" Value="BrowseDlg" Order="2">1</Publish>

  <Publish Dialog="SettingsDlg" Control="Back" Event="NewDialog" Value="InstallDirDlg">1</Publish>
  <Publish Dialog="SettingsDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg"></Publish>

  <Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="InstallDirDlg" Order="1">NOT Installed</Publish>
  <Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="MaintenanceTypeDlg" Order="2">Installed AND NOT PATCH</Publish>
  <Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2">Installed AND PATCH</Publish>

  <Publish Dialog="MaintenanceWelcomeDlg" Control="Next" Event="NewDialog" Value="MaintenanceTypeDlg">1</Publish>

  <Publish Dialog="MaintenanceTypeDlg" Control="RepairButton" Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
  <Publish Dialog="MaintenanceTypeDlg" Control="RemoveButton" Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
  <Publish Dialog="MaintenanceTypeDlg" Control="Back" Event="NewDialog" Value="MaintenanceWelcomeDlg">1</Publish>

  <Property Id="ARPNOMODIFY" Value="1" />
 </UI>

 <UIRef Id="WixUI_Common" />
</Fragment>
The custom SettingsDlg.wxs looks like this:


Last but not least, to fix 'ICE80: This 32BitComponent '' uses 64BitDirectory INSTALLDIR' you can add '-arch x64' to the Compiler parameters in the project properies' Tool Settings.
The heat.exe searches for all binaries in the output folder. This causes problems with build servers. In TFS2012 you can enable that the build server puts the binaries in seperate folders. I don't have TFS2012 running so I can't try this.

Full source is available at GitHub, so you can simply test and edit it yourself: https://github.com/luuksommers/WixInstallerDemo

Sources:
http://ahordijk.wordpress.com/2013/03/26/automatically-add-references-and-content-to-the-wix-installer/
http://www.chriskonces.com/using-xslt-with-heat-exe-wix-to-create-windows-service-installs/
http://kentie.net/article/wixtipstricks/

Cheers,
Luuk

PS: Next blog post is also in progress, Caliburn Micro and Attribute binding :)

Sunday, July 14, 2013

PetaPoco as it's meant to be; with repository and fluent mappings

When your domain objects get decorated with DataLayer column mappings, you create an unwanted dependency with your DayaLayer. To prevent this, you can create a mapping. This blog describes how I've done it. Comments are always welcome in the comment section below.

In the recently updated PetaPoco for Informix, I've created a FluentMapper. With this you can create a mapping of your domain object, without decorating them.

Here's how you can also benefit from this seperation. First create the domain object (DO):
public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public double Revenue { get;set; }
}
Followed by the mapper of the DO
public class CustomerMapper : FluentMapper<Customer>
{
    public CustomerMapper() : base("tblCustomer", "CustomerId")
    {
        this.Property(a=>a.Id, "CustomerId")
            .Property(a=>a.Name, "CustomerName")
            .Property(a=>a.Revenue, "Revenue", fromDbValue=>(int)fromDbValue / 10d, toDbValue => (int)((double)toDbValue * 10));
    }
}
Once the mapper is in place, we want to make sure we hook it up every time. This is why I make a DataContext class (which is used in the repository):
public class MyDataContext : Database
{
    //Because the PetaPoco.Mappers is static, we also hook up our mappers static
    static MyDataContext()
    {
        PetaPoco.Mappers.Register(typeof(Customer), new CustomerMapper());
    }
    public MyDataContext()
        : base(ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString))
    {
        
    }
}
Now we can create the repository for the customer related DataLayer functions (CRUD operations):
public class CustomerRepository
{
    private MyDataContext _dataContext
    public CustomerRepository(MyDataContext dataConext)
    {
        _dataContext = dataConext;
    }

    public Customer GetById(int customerId)
    {
        _dataContext.Get<Customer>(customerId);
    }

    public void Update(Customer customer)
    {
        _dataContext.Update(customer);
    }

    // etc
}
We can use it all together in the following way:
using(var dataContext = new MyDataContext())
{
    var customerRepository = new CustomerRepository(dataContext);
    var customer = customerRepository.GetById(1234);
}
As you can see the usage of the repository is super clean. Nobody knows where the data comes from, and nobody cares.

Let me know what you think of this solution!

Cheers,
Luuk

Friday, July 12, 2013

Introducing PetaPoco for Informix

I've created a fork of PetaPoco, with support for Informix and named parameters. I've also added multiple key support.

The code is available on GitHub.

An example:
[TableName("ship")]
public class Ship
{
    [PrimaryKey]
    [Column("shipid")]
    public int Id { get; set; }

    [Column("name")]
    public string Name { get; set; }
}

[TableName("positionlog")]
public class PositionLog
{
    [PrimaryKey]
    [Column("shipid")]
    public int ShipId { get; set; }

    [PrimaryKey]
    [Column("epochdate")]
    public int EpochDate { get; set; }

    [Column("lat")]
    public int Latitude {get;set;}

    [Column("lon")]
    public int Longitude {get;set;}
}

var db = new Database(new IfxConnection("<ConnectionString>");
int shipId = 1234;
var ship = db.Query<Ship>("select first 1 * from ship where shipid = @id", new { id = shipId }).FirstOrDefault();

ship.Name = "Submarine";

db.Update(ship);

The code will be updated with support of more features as we move along with our project (which uses Informix).

Cheers,
Luuk

Monday, July 1, 2013

Looping through your e-mails with outlook interop with c#

Last week we needed a small tool that checked the body of an e-mail. I found out that it only takes a couple of lines using Outlook Interop to do so. Super fast, super easy:
using System;
using Microsoft.Office.Interop.Outlook;

namespace OutlookReader
{
    class Program
    {
        static void Main(string[] args)
        {
            Microsoft.Office.Interop.Outlook.Application app = null;
            Microsoft.Office.Interop.Outlook._NameSpace ns = null;
            Microsoft.Office.Interop.Outlook.MailItem item = null;
            Microsoft.Office.Interop.Outlook.MAPIFolder inboxFolder = null;
            Microsoft.Office.Interop.Outlook.MAPIFolder subFolder = null;

            try
            {
                app = new Application();
                ns = app.GetNamespace("MAPI");
                ns.Logon(null, null, false, false);

                inboxFolder = ns.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderInbox);
                subFolder = inboxFolder; // or use folder.Folders["Folder Name"];

                Console.WriteLine("Folder Name: {0}, EntryId: {1}", subFolder.Name, subFolder.EntryID);
                Console.WriteLine("Num Items: {0}", subFolder.Items.Count.ToString());

                for (int i = 1; i <= subFolder.Items.Count; i++)
                {
                    item = (MailItem)subFolder.Items[i];
                    Console.WriteLine("Subject: {0}", item.Subject);
                }
            }
            catch (System.Runtime.InteropServices.COMException ex)
            {
                Console.WriteLine(ex.ToString());
            }
            finally
            {
                ns = null;
                app = null;
                inboxFolder = null;
            }
        }
    }
}
This only works with outlook installed, so if you want to use it on your server, you'll have to install outlook. Does anybody know any other good way to access mail without the native MAPI32?

Cheers,
Luuk

Tuesday, June 4, 2013

Beyond Compare is Beyond Awesome with Source Control Integration

Just found out the Source Control Integration with Beyond Compare Pro, this is awesome! First install the TFS  SCC provider and click on Tools -> Source Controls Integration.


Now see a window where you can add the same folders as your mapped TFS workspaces. But when you now start to compare, merge or edit a file which is checked in, you get the following popup:


Nice, isn't it? So now you don't have to go to VS to checkout the item manually and this works a lot faster!

Cheers,
Luuk

Tuesday, May 28, 2013

Manager for the Service Bus for Windows Server

I'm currently working on a manager for the Service Bus for Windows Server.

The purpose of the manager is that you can simply view which queues are currently available on the server and which users have access rights on them. Because I don't want to do this in the workers who are actually using the service bus. It is build in VS2012 with the help of caliburn micro and mahapps for the nice metro look.

Things that need to be done include the update of currently running queues and peek into messages from the queue. The source code is fully available on GitHub and here is what it currently looks like:



There are currently no good managers for the service bus so I have the inspiration to make this one the starting point for managing and understanding the service bus.

When there are any updates on this I'll let you know.

Cheers,

Luuk

Saturday, May 11, 2013

Monitor your Server IP with Python and Pushover

Since I'm running my own web / plex server, I want to be notified when my public IP changes. This is super-easy using python and pushover.

The base of the application is really simple; fetch the IP from http://checkip.dyndns.org/ and check if it's the same as the last time you've runned. If it has changed, send a message with pushover to your mobile phone.

I use crontab every 5 minutes to validate the IP.


Here's the script:

import re
import os
import uuid
import json
import socket
import urllib, urllib2
import time

def get_current_ip():
    req = urllib2.Request('http://checkip.dyndns.org/')
    response = urllib2.urlopen(req)
    the_page = response.read()

    p = re.compile(r'(?P >ip<(?:[0-9]{1,3}\.){3}[0-9]{1,3})')
    m = p.search(the_page)
    return m.group('ip')

# load settings
current_ip = get_current_ip()
uid = ''
computer_name = ''
last_ip = ''

if(os.path.exists('/srv/crontab/oswos.settings')):
    with open('/srv/crontab/oswos.settings', 'r') as f:
        content = f.read()
        decoded = json.loads(content)
        uid = decoded.get('guid')
        computer_name = decoded.get('computer_name')
        last_ip = decoded.get('last_ip')
else:
    uid = str(uuid.uuid1())
    computer_name = socket.gethostname()

data = { 'guid': uid, 'computer_name': computer_name, 'last_ip': current_ip }
with open('/srv/crontab/oswos.settings', 'w') as f:
    json.dump(data, f)

print 'uid          : {0}'.format(uid)
print 'computer_name: {0}'.format(computer_name)
print 'last_ip      : {0}'.format(last_ip)
print 'current ip   : {0}'.format(current_ip)

if(current_ip != last_ip):
    API_URL = "https://api.pushover.net/1/messages.json"
    API_KEY = "api-key-here"
    curUrl = API_URL

    data = urllib.urlencode({
        'token': API_KEY,
        'title': 'Oswos Notification',
        'user': 'user-key-here',
        'message': 'IP Changed from {0} to {1}'.format(last_ip,current_ip).encode('utf-8'),
        'timestamp': int(time.time())
            })

    req = urllib2.Request(curUrl)
    handle = urllib2.urlopen(req, data)
    handle.close()

You see how easy it is to connect to Pushover? Building your own good working Android application is hard, so why not use the tools which are already there!

Just let me know if you have any tips / suggestions to extend this further.

Luuk