Windows Identity Foundation, first steps…

I’ve been slowly working through the excellent book Programming Windows Identity Foundation by Vittorio Bertocci. I was getting a little restless though and wanted to see some code so I found this walkthrough and decided to play along. Things didn’t go as smoothly as I had hoped but I did learn more than I bargained for…

One of the first requirements to get the sample running is to ensure that you have SSL enabled on your default web site. This is not a common task for most developers so I’ll elaborate a little:

Setting up HTTPS

IIS Manager supports the creation of a self-signed certificate which is sufficient for development purposes. The server configuration provides a‘Server Certificates’ option as below, in the Actions menu there is a ‘Create Self-Signed Certificate…’ item.

image

There’s not much to the certificate creation process, enter a friendly name for the certificate. In my case I lacked imagination and went with ‘TestCertficateForWIF’. The certificate is created in the machine certificate store so running certmgr.msc doesn’t help as it opens the user store. Instead I ran the mmc.exe directly and added the certificate manager snap-in explicitly, when asked to choose a store I went with the local machine store.

image

image

image

Looking in the Personal | Certificates node reveals the newly created certificate.

image

Setting up an HTTPS binding for the Default Web Site is now possible. Select the site in IIS manager and then choose to Edit Bindings… from the context menu.

image

The dialog allows you to add a new HTTPS binding, you just select the certificate you want to use as part of the encryption process.

image

I next ran through the various steps in the walkthrough but when I tried the run the completed sample I got a KeySet error.

Additional Notes:

  • The certificate name for the DemoSTS web.config only requires CN=, not two.

image

  • As we are using Windows authentication the console client does not need to pass credentials explicitly. When I set the credentials manually to a local test account I would see my domain account as the name in the returned claim.

image

Troubleshooting the Sample

A quick search suggested that the AppPool account my services were running as did not have access to the private key of the certificate. OK, back into the machine certificate store and ‘Manage Private Keys…’ for the certificate.

image

The web applications for the services were mapped to the ApplicationPoolIdentity (I’m running IIS7.5) so I tried adding the read right to the ‘IIS AppPool\DefaultAppPool’ account. This didn’t seem to help so I resorted to creating a specific service account and assigning it the read permission for the certificate.

image

I created a new application pool to run as this new ‘service.sts’ user and set the web applications use this application pool. This was good and resolved by KeySet error but I was now getting a fault back from my secure WCF service. After a little head scratching I fired up Fiddler to watch the traffic:

image

OK – I could see the secure WCF service calling the DemoSTS, the DemoSTS doing it’s work and then calling back to the secure service, then a 500 failure. Looking at the response message for the 500:

image

For some reason I was getting an ‘Invalid Security Token’ error. I knew the error was in the secure WCF service but not much more. While looking through the web.config for the service, I found commented out trace configuration:

image

So I enabled the tracing and re-ran the client. The WIFTrace.e2e file popped into the service directory and I used the Microsoft Service Trace Viewer to look at the log:

image

Looking at the error detail:

image

‘The issuer of the security token was not recognized by the IssuerNameRegistry…’, that looked familiar so back to the web.config.

<microsoft.identityModel>

<service name=”SecureWCFService.Service”>

<audienceUris>

<add value=”http://wsakl0001013.ap.aderant.com/SecureWCFService/Service.svc&#8221; />

</audienceUris>

<issuerNameRegistry type=”Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35″>

<trustedIssuers>

<add thumbprint=”?????????????????????????????” name=”http://wsakl0001013.ap.aderant.com/DemoSTS/Service.svc&#8221; />

</trustedIssuers>

</issuerNameRegistry>

</service>

</microsoft.identityModel>

I’ve removed the actual thumbprint, but here was where the service was configured to accept tokens from a STS using a particular certificate identified by it’s thumbprint. I needed the thumbprint of the certificate I had created, easily done via PowerShell:

> $certificate = Get-ChildItem -Path Cert:\LocalMachine\My | where { $_.Subject -match ‘CN\=WSAKL0001013.ap.aderant.com’ }

>$certificate.thumbprint

The thumbprint provided by PowerShell did not match my web.config so I updated the config.

Happy days, the sample now ran:

image

Workflow Services & MSMQ Revisited

I’ recently dusted off a WCF sample I’d written and blogged about a year or two ago. During the process of getting it to work again, I discovered the blog posting is incorrect so I’m reposting with corrections and additional explanation.

Tom Hollander published a great set of posts on this topic which I needed…

http://blogs.msdn.com/b/tomholl/archive/2008/07/12/msmq-wcf-and-iis-getting-them-to-play-nice-part-1.aspx

http://blogs.msdn.com/b/tomholl/archive/2008/07/12/msmq-wcf-and-iis-getting-them-to-play-nice-part-2.aspx

http://blogs.msdn.com/b/tomholl/archive/2008/07/12/msmq-wcf-and-iis-getting-them-to-play-nice-part-3.aspx

We needed a quick proof of concept to show that a workflow service could be activated via a message sent over MSMQ. First part was workflow design and coding, this was the easy part. All I wanted to do was accept a custom type, in this case a TimeEntry, from a SubmitTime service operation that belonged to an ITimeEntryContract. On receiving the time entry I would simple log the fact that it arrived into the event log. This is pretty much the “Hello, World!” of the services demos. The second part was getting the configuration correct…

One of the promises of WCF is that it gives us a unified communication model regardless of the protocol: net.tcp, http, msmq, net.pipe and it does. The best description I’ve heard for WCF is that it is a channel factory, and you configure the channels declaratively in the .config file (you can of course use code too if you prefer). The key benefit is that the service contract and implementation, for the main part, can be channel agnostic. Of course there are the exceptions to prove the rule such as a void return being required by an MSMQ channel but for the most part it holds. As it turns out, it was true that I needed to make no changes to code to move from a default http endpoint to a MSMQ endpoint. I need need to do a lot of configuration and setup though which is not that well documented. This post hopes to correct that is some small way.

First up the easy part, writing the code.

In Visual Studio 2010 I started a new ‘WCF Workflow Service Application’ project. First I define my TimeEntry model class:

using System;

namespace QueuedWorkflowService.Service {
    public class TimeEntry {
        public Guid TimekeeperId { get; set; }
        public Guid MatterId { get; set; }
        public TimeSpan Duration { get; set; }

        public override string ToString() {
            return string.Format(“Timekeeper: {0}, Matter: {1}, Duration: {2}”, TimekeeperId, MatterId, Duration);
        }
    }
}

Then I defined a code activity to write to the time entry provided into the event log:

using System;
using System.Diagnostics;
using System.Activities;

namespace QueuedWorkflowService.Service {
    public sealed class DebugLog : CodeActivity {
        public InArgument<string> Text { get; set; }

        protected override void Execute(CodeActivityContext context) {
            string message = string.Format(“Server [{0}] – Queued Workflow Service – debug :{1}”, DateTime.Now, context.GetValue(this.Text));
            Debug.WriteLine(message);
            EventLog.WriteEntry(“Queued Service Example”, message, EventLogEntryType.Information);
        }
    }
}

All the C# code is now written and I create my workflow:

I need a variable to hold my time entry so I define one at the scope of the service:

The project template creates the CorrelationHandle for me but we won’t be using it.

The receive activity is configured as follows:

With the Content specified as:

This is such a simple service that I don’t need any correlation between messages, it just receives and processes the message without communicating back to the sender of the message. Therefore I also cleared out the CorrelatesOn and the CorrelationInitializer properties.

Finally I set up the Debug activity to write the time entry to the event log:

That’s it! I’m done. This now runs using the default binding introduced in WCF4 (http and net.tcp). Starting up the project launches my service and the WCF test client is also opened pointing to my new service. The service is running in Cassini, the local web server built into the Visual Studio debugging environment.

15 minutes, or there about, to build a workflow service. What follows a summary of the steps discovered over the next 4 hours try to convert this sample from using an http endpoint to an msmq endpoint.

Default Behaviour
One of the key messages Microsoft heard from the WCF 3 community was that configuration was too hard. To even get started using WCF you had to understand a mountain of new terms and concepts including: channels, address, binding, contract, behaviours,… the response to this in .NET 4 is defaults. If you don’t specify an endpoint, binding etc then WCF creates a default one for you based upon your machine configuration settings. This makes getting a service up and running a very straightforward experience. BUT as soon as you want to step outside of the defaults, you need the same knowledge that you needed in the WCF 3 world.

Here’s the web.config I ended up with after a couple of hours, the MSMQ settings were a voyage of personal discovery… (http://msdn.microsoft.com/en-us/library/ms731380.aspx)

<?xml version=”1.0″ encoding=”utf-8″?>
<configuration>
  <system.web>
    <compilation debug=”true” targetFramework=”4.0″ />
  </system.web>
  <system.serviceModel>
    <services>
      <service name=”TimeEntryService”>
        <endpoint
            binding=”netMsmqBinding”
            bindingConfiguration=”nonTxnMsmqBinding”
            address=”net.msmq://localhost/private/QueuedWorkflowService/TimeEntryService.xamlx”
            contract=”ITimeEntryContract” />

        <endpoint
            binding=”netMsmqBinding”
            bindingConfiguration=”txnMsmqBinding”
            address=”net.msmq://localhost/private/QueuedWorkflowServiceTxn/TimeEntryService.xamlx”
            contract=”ITimeEntryContract” />
        <endpoint
            address=”mex”
            binding=”mexHttpBinding”
            contract=”IMetadataExchange” />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled=”true” />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <netMsmqBinding>
        <binding
            name=”nonTxnMsmqBinding”
            durable=”false”
            exactlyOnce=”false”
            useActiveDirectory=”false
            queueTransferProtocol=”Native”>
          <security mode=”None”>
            <message clientCredentialType=”None” />
            <transport
                msmqAuthenticationMode=”None
                msmqProtectionLevel=”None” />
          </security>
        </binding>

        <binding
            name=”txnMsmqBinding”
            durable=”true”
            exactlyOnce=”true”
            useActiveDirectory=”false”
            queueTransferProtocol=”Native”>
          <security mode=”None”>
            <message clientCredentialType=”None” />
            <transport
                msmqAuthenticationMode=”None”
                msmqProtectionLevel=”None” />
          </security>
        </binding>
      </netMsmqBinding>
    </bindings>
  </system.serviceModel>
    <microsoft.applicationServer>
        <hosting>
            <serviceAutoStart>
                <add relativeVirtualPath=”TimeEntryService.xamlx” />
            </serviceAutoStart>
        </hosting>
    </microsoft.applicationServer>
</configuration>

The two important sections are the endpoint and the netMsmqBinding sections. A single service is defined that exposes two MSMQ endpoints, a transactional endpoint and a non-transactional one. This was done to demonstrate the changes required in the netMsmqBinding to support a transactional queue over a non-transactional queue; namely the durable and exactlyOnce attributes. In both cases no security is enabled. I had to do this to get the simplest example to work. Note that the WCF address for the queue does not include a $ suffix on the private queue name and matches the Uri of the service.

We still have some way to go to get this to work, we need a number of services to be installed and running on the workstation:

Services
• Message Queuing (MSMQ)
• Net.Msmq Listener Adapter (NetMsmqActivator)
• Windows Process Activation Service (WAS)

I also ensured that AppFabric was running as this is the easiest way to start the debugging process:
• AppFabric Event Collection Service (AppFabricEventCollectionService)

If you don’t have these services registered on your workstation you will need to go into the ‘Programs and Features’ control panel, then ‘Turn Windows features on or off’ to enable them (Windows 7).

With the services installed and started you need to create a private message queue to map the endpoint to (see : http://msdn.microsoft.com/en-us/library/ms789025.aspx ). The queue name must match the Uri of the service.

image

The sample is configured to run without security on the queues, i.e. the queues are not authorized. You must allow the anonymous login ‘send’ rights on the queues. If you don’t, the messages will be delivered but the WAS listener will not be able to pick up the messages from the queue.

image

If you have problems and do not see the message delivered to the correct queue, have a look in the system Dead Letter queues.

image

You also need to change your VS2010 project to use IIS as the host rather than Cassini. On the project properties dialog, open the Web tab:

As I wanted events from this service to be added to my AppFabric monitoring store, I also added a connection string to the mapped web application and then configured AppFAbric monitoring to use that connection.


And in AppFabric configuration:

Finally you also need to enable the correct protocols on the web application (Manage Application… | Advanced Settings):

I’ve added in net.msmq for queuing support and also net.pipe for the workflow control endpoint.

Make sure that the user the application pool is running as has access to read and write to the queue.

With all the server configured I then wrote a simple WPF test application that used a service reference generated by VS2010, this creates the appropriate client side WCF configuration. The button click handler called the service proxy directly:

private void submitTimeEntryButton_Click(object sender, RoutedEventArgs e) {
    using (TimeEntryContractClient proxy = new TimeEntryContractClient(“QueuedTimeEntryContract”)) {
        TimeEntry timeEntry = new TimeEntry {
                                            TimekeeperId = Guid.NewGuid(),
                                            MatterId = Guid.NewGuid(),
                                            Duration = new TimeSpan(0, 4, 0, 0)
                                            };

        string message = string.Format(“Client [{3}]- TimekeeperId: {0}, MatterId: {1}, Duration: {2}”,
            timeEntry.TimekeeperId,
            timeEntry.MatterId,
            timeEntry.Duration,
            DateTime.Now);

        proxy.SubmitTimeEntry(timeEntry);
        EventLog.WriteEntry(“Queued Service Example”, message, EventLogEntryType.Information);
    }
}

And the awesome UI:

Click the button and you get entries in the event log, a client event and the server event:

image

I made no changes to the code to move from an http endpoint to a MSMQ endpoint, but it’s not as simple as tweaking the config and you’re good to go. I’d love to see some tooling in VS2010 or VS vNext to take some of the pain away from WCF config, similar to the tooling AppFabric adds into IIS. Until that happens, there are plenty of angle brackets to deal with.

WiXing Lyrical (Part 2)

Picking up from where we left off previously with the product.wxs file, next we come to the Media element.

<Media Id=”1″ Cabinet=”media1.cab” EmbedCab=”yes” />

This is the default media entry created by Votive. The files to be installed are constructed into a single cabinet file which is embedded within the MSI.

Specifying the Install Location

Following the media is the directory structure we want to install the application into. A set of directory elements are nested to describe the required structure.

<Directory Id=”TARGETDIR” Name=”SourceDir”>
  <Directory Id=”dirAderant” Name=”AderantExpert”>
    <Directory Id=”dirEnvironmentFolder” Name=”[EXPERTENVIRONMENTNAME]” >
      <Directory Id=”INSTALLLOCATION” Name=”ExpertAssistantInstaller”>
      <!–additional components go here –> 

      </Directory>
      <Directory Id=”ProgramMenuFolder”>
        <Directory Id=”ApplicationProgramsFolder” Name=”Aderant”>
        </Directory>
      </Directory>
    </Directory>
  </Directory>
</Directory>

The TARGETDIR is the root directory for the installation and by default is set to the SourceDir. We then set-up the directory structure underneath \AderantExpert\Environment\ExpertAssistantInstaller. The environment folder is set to the value in the EXPERTENVIRONMENTNAME property. The INSTALLLOCATION Id specifies where the files will be installed to. If you want to install into the Program Files folder, see here.

In addition to specifying the target location for the install files, a folder is added to the Program Menu folder for the current user, the ApplicationProgramsFolder reference is used later in the script when setting up the start menu items.

Updating an XML File

It is possible to use components to action additional steps and tie the KeyPath to the containing directory structure. The KeyPath is used by the installer to determine if an item exists so if the containing directory structure exists the actions do not run. In my sample the red comment above is a place holder for a couple of components similar to.

<Component Id=”cmpConfigEnvironmentName” Guid=”????” KeyPath=”yes”>
<util:XmlFile Id=”xmlConfigEnvironmentName”
Action=”setValue”
ElementPath=”/configuration/appSettings/add[\[]@key=’EnvironmentName'[\]]/@value”
File=”[INSTALLLOCATION]\ExpertAssistantCO.exe.config”
Value=”[EXPERTENVIRONMENTNAME]”
/>
</Component>

The component is responsible for updating the ExpertAssistant.exe.config Xml file with a property from the MSI. The util extension library provides a XmlFile function which can read and write to a specified Xml file. The element path is a formatted field and therefore, square brackets in the XPath must be escaped. We have three updates to the exe.config to make and so end up with three components, for ease of management these are then wrapped in a ComponentGroup:

<ComponentGroup Id=”ExpertAssistantCO.ConfigSettings”>
  <ComponentRef Id=”cmpConfigEnvironmentName”/>
  <ComponentRef Id=”cmpConfigExpertSharePath”/>
  <ComponentRef Id=”cmpConfigLocalInstallationPath”/>
</ComponentGroup>

Adding a Start Menu Item

A common requirement is to add a shortcut for the installed application to the Start Menu. There is an odd twist here as we are using a perUser install. The StartMenu item is unique to each user installing the software and therefore a registry key is required to track the KeyPath. The registry key must be in the HKEY_CURRENT_USER hive and a logical location is Software\VendorName\ApplicationName\.

<DirectoryRef Id=”ApplicationProgramsFolder”>
  <Component Id=”ApplicationShortcut” Guid=”????” >
    <Shortcut Id=”ApplicationStartMenuShortcut”
       Name=”Expert Assistant”
       Description=”Expert Assistant”
       Target=”[INSTALLLOCATION]\ExpertAssistantCO.exe”
       />
    <RegistryValue Root=”HKCU” Key=”Software\Aderant\ExpertAssistant_[EXPERTENVIRONMENTNAME]” Name=”installed” Type=”integer” Value=”1″ KeyPath=”yes”/>
    <RemoveFolder Id=”ApplicationProgramsFolder” On=”uninstall”/>
  </Component>
</DirectoryRef>

Along with the Shortcut, we also tie a RemoveFolder action to the registry KeyPath so that the folder containing the shortcut is removed during an uninstall.

Remove Custom Registry Key On Uninstall

It is possible to have specific actions occur only during an uninstall to clean up, we have this need to remove a registry key that maybe set-up by the application. To achieve this we schedule a Registry action to ‘removeKeyOnUninstall’. Again, this action is perUser and therefore tied to a KeyPath in the HKEY_CURRENT_USER registry hive.

<DirectoryRef Id=”ApplicationProgramsFolder”>
  <Component Id=”RemoveRunRegistryEntry” Guid=”????”>
    <RegistryValue Root=”HKCU” Key=”Software\Microsoft\Windows\CurrentVersion\Run” Name=”ADERANT.ExpertAssistant” KeyPath=”yes” Type=”string” Value=””/>
    <Registry Action=”removeKeyOnUninstall” Root=”HKCU” Key=”Software\Microsoft\Windows\CurrentVersion\Run\ADERANT.ExpertAssistant” />
  </Component>
</DirectoryRef>

Launch an Exe after Installation

After the installation or repair of the MSI is complete, we’d like the MSI to run the executable that we’ve just installed on to the machine. To do this we need to invoke a custom action.

<CustomAction Id=”LaunchApplication” BinaryKey=”WixCA” DllEntry=”WixShellExec” Impersonate=”yes” />

The custom action executes the command held in the WixShellExecTarget property that we specified near the beginning of the wxs file. The custom action then needs to be scheduled to run after InstallFinalize:

<InstallExecuteSequence>
  <Custom Action=”LaunchApplication” After=”InstallFinalize” >NOT(REMOVE ~=”ALL”)</Custom>
</InstallExecuteSequence>

In our case we don’t want the action to execute if we are removing the software, only on install and repair. Therefore we specify the condition ‘NOT(REMOVE ~=”ALL”)’, more details can be found here.

NOTE: Before setting this condition, the entry in the Add Remove Programs control panel would not automatically be deleted on uninstall, it would only disappear after a manual refresh. If the uninstall process returns a code other than 0, an error has occurred and so the refresh is not triggered. To see this was the case, I enabled verbose logging via msiexec and removed the MSI using the command line. The log showed that the custom action was failing because the path didn’t exist – because we had just removed it. The non-zero return code was logged.

Pulling It All Together

The final part of the script declares a feature – an installable unit. In our case we have a single feature which installs everything.

    <Feature Id=”ExpertAssistant”
             Title=”Expert Assistant”
             Level=”1″>
      <ComponentGroupRef Id=”ExpertAssistantCO.Binaries” />
      <ComponentGroupRef Id=”ExpertAssistantCO.Content” />
      <ComponentGroupRef Id=”ExpertAssistantCO.ConfigSettings” />
      <ComponentRef Id=”ApplicationShortcut” />
      <ComponentRef Id=”RemoveRunRegistryEntry” />
    </Feature>
  </Product>
</Wix>

Here the various component groups we’ve declared come into play, rather than listing out every feature individually we can reference the logical component group.

And we are done. We don’t require a UI for our installer and so I’ve not looked into that in any depth. WiX does fully support defining an installation wizard UI and even supports custom UI.

The last piece of the WiX tooling is the Deployment Foundation Toolkit which I’ll save for the next post.

Full product.wxs script for reference (with the GUIDs taken out) to close…

<?xml version=”1.0″ encoding=”UTF-8″?>
<Wix xmlns=”
http://schemas.microsoft.com/wix/2006/wi” xmlns:util=”http://schemas.microsoft.com/wix/UtilExtension” RequiredVersion=”3.5.0.0″>
  <Product Id=”?”
           Name=”ExpertAssistant”
           Language=”1033″
           Codepage=”1252″
           Version=”8.0.0.0″
           Manufacturer=”Aderant”
           UpgradeCode=”?”>
    <Package InstallerVersion=”200″
             Compressed=”yes”
             Manufacturer=”Aderant”
             Description=”Expert Assistant Installer”
             InstallScope=”perUser”
    />

    <Property Id=”EXPERTSHAREPATH” Value=”\\MyShare\ExpertShare” />
    <Property Id=”EXPERTENVIRONMENTNAME” Value=”MyEnvironment” />
    <Property Id=”EXPERTLOCALINSTALLPATH” Value=”C:\AderantExpert\Environment\Applications” />
    <Property Id=”ARPPRODUCTICON” Value=”icon.ico” />
    <Property Id=”ARPNOMODIFY” Value=”1″ />
    <Property Id=”WixShellExecTarget” Value=”[#filExpertAssistantCOexe]”/>

    <SetProperty Id=”dirEnvironmentFolder” Value=”C:\AderantExpert\[EXPERTENVIRONMENTNAME]” After=”CostInitialize”/>
    <SetProperty Id=”EXPERTLOCALINSTALLPATH” Value=”C:\AderantExpert\[EXPERTENVIRONMENTNAME]\Applications” After=”CostInitialize” />
   
    <Property Id=”EnableUserControl” Value=”1″ />

    <Icon Id=”icon.ico” SourceFile=”$(var.ExpertAssistantCO.TargetDir)\Expert_Assistant_Icon.ico”/>

    <Media Id=”1″ Cabinet=”media1.cab” EmbedCab=”yes” />

    <Directory Id=”TARGETDIR” Name=”SourceDir”>
      <Directory Id=”dirAderant” Name=”AderantExpert”>
        <Directory Id=”dirEnvironmentFolder” Name=”[EXPERTENVIRONMENTNAME]” >
          <Directory Id=”INSTALLLOCATION” Name=”ExpertAssistantInstaller”>
            <Component Id=”cmpConfigEnvironmentName” Guid=”?” KeyPath=”yes”>
              <util:XmlFile Id=”xmlConfigEnvironmentName”
                            Action=”setValue”
                            ElementPath=”/configuration/appSettings/add[\[]@key=’EnvironmentName'[\]]/@value”
                            File=”[INSTALLLOCATION]\ExpertAssistantCO.exe.config”
                            Value=”[EXPERTENVIRONMENTNAME]”
                            />
            </Component>
            <Component Id=”cmpConfigExpertSharePath” Guid=”?” KeyPath=”yes”>
              <util:XmlFile Id=”xmlConfigExpertSharePath”
                            Action=”setValue”
                            ElementPath=”/configuration/appSettings/add[\[]@key=’ExpertSharePath'[\]]/@value”
                            File=”[INSTALLLOCATION]\ExpertAssistantCO.exe.config”
                            Value=”[EXPERTSHAREPATH]”
                            />
            </Component>
            <Component Id=”cmpConfigLocalInstallationPath” Guid=”?” KeyPath=”yes”>
              <util:XmlFile Id=”xmlConfigLocalInstallationPath”
                            Action=”setValue”
                            ElementPath=”/configuration/appSettings/add[\[]@key=’LocalInstallationPath'[\]]/@value”
                            File=”[INSTALLLOCATION]\ExpertAssistantCO.exe.config”
                            Value=”[EXPERTLOCALINSTALLPATH]”
                            />
            </Component>
          </Directory>
          <Directory Id=”ProgramMenuFolder”>
            <Directory Id=”ApplicationProgramsFolder” Name=”Aderant”>
            </Directory>
          </Directory>
        </Directory>
      </Directory>
    </Directory>

    <ComponentGroup Id=”ExpertAssistantCO.ConfigSettings”>
      <ComponentRef Id=”cmpConfigEnvironmentName”/>
      <ComponentRef Id=”cmpConfigExpertSharePath”/>
      <ComponentRef Id=”cmpConfigLocalInstallationPath”/>
    </ComponentGroup>

    <DirectoryRef Id=”ApplicationProgramsFolder”>
      <Component Id=”ApplicationShortcut” Guid=”?” >
        <Shortcut Id=”ApplicationStartMenuShortcut”
           Name=”Expert Assistant”
           Description=”Expert Assistant”
           Target=”[INSTALLLOCATION]\ExpertAssistantCO.exe”
           />
        <RegistryValue Root=”HKCU” Key=”Software\Aderant\ExpertAssistant_[EXPERTENVIRONMENTNAME]” Name=”installed” Type=”integer” Value=”1″ KeyPath=”yes”/>
        <RemoveFolder Id=”ApplicationProgramsFolder” On=”uninstall”/>
      </Component>
    </DirectoryRef>

    <DirectoryRef Id=”ApplicationProgramsFolder”>
      <Component Id=”RemoveRunRegistryEntry” Guid=”?”>
        <RegistryValue Root=”HKCU” Key=”Software\Microsoft\Windows\CurrentVersion\Run” Name=”ADERANT.ExpertAssistant” KeyPath=”yes” Type=”string” Value=””/>
        <Registry Action=”removeKeyOnUninstall” Root=”HKCU” Key=”Software\Microsoft\Windows\CurrentVersion\Run\ADERANT.ExpertAssistant” />
      </Component>
    </DirectoryRef>
   
    <CustomAction Id=”LaunchApplication” BinaryKey=”WixCA” DllEntry=”WixShellExec” Impersonate=”yes” />
    <InstallExecuteSequence>
      <Custom Action=”LaunchApplication” After=”InstallFinalize” >NOT(REMOVE ~=”ALL”)</Custom>
    </InstallExecuteSequence>

    <Feature Id=”ExpertAssistant”
             Title=”Expert Assistant”
             Level=”1″>
      <ComponentGroupRef Id=”ExpertAssistantCO.Binaries” />
      <ComponentGroupRef Id=”ExpertAssistantCO.Content” />
      <ComponentGroupRef Id=”ExpertAssistantCO.ConfigSettings” />
      <ComponentRef Id=”ApplicationShortcut” />
      <ComponentRef Id=”RemoveRunRegistryEntry” />
    </Feature>
  </Product>
</Wix>

WiXing Lyrical (part 1)

Continuing on from the previous post, it’s time to take a look at the customization requirements that brought about the creation of a custom bootstrap process for our desktop installations.

The customization capabilities within the Expert product are extensive, they support changes to the  domain model, business process and user interface. Many of these changes result in the need to deploy custom assemblies to the workstations running the Expert applications. If the out-of-the-box ClickOnce manifests were used to manage these changes, they would need to be updated by the customization process. Instead of doing this, we chose a solution similar to Google Chrome and created our own bootstrap mechanism to manage updating the client software. The ClickOnce infrastructure is used to ‘install an installer’. I’m not going to drill into the bootstrapper, Pete has already discussed some of the performance aspects here. Instead we’ll walk through the process of creating an MSI to replace the ClickOnce based installation.

This was not my first MSI authoring, in the past I’d been exposed to InstallShield, Wise and WiX, but I hadn’t done anything in the area for around 4-5 years. The last installer I wrote was using Windows Installer Xml (WiX) when it had just been publicly released from Microsoft and the memory was not a pleasant one. The good news is that in the intervening years, my biggest issue with WiX has been resolved – there is now good documentation and a healthy community supporting it. Rather than waiting until the end to list a couple of resources, here are the main references I used:

The first thing to note is that WiX is a free, open source toolkit that is fully supported by Microsoft. It does not ship with any Visual Studio version and must be downloaded. The current version, and the version I used, is v3.5 though there is a v3.6 RC0 available. The actual download of the bits is available from CodePlex here, the SourceForge site links to this.

There are three components in the installer:

  1. WiX – command line tools, Xml schemas, extensions
  2. Votive – a Visual Studio plug-in
  3. Deployment Tools Foundation (DTF) – a managed library for programming against MSIs.

In addition to the WiX Toolkit, another tool to have is Orca which is available in the Windows SDK. Orca is an editor for the MSI database format, allowing an MSI to be easily inspected and edited.

The WiX toolset is summarized concisely by the following diagram (taken from http://wix.sourceforge.net/coretoolset.html)

clip_image001

While it is possible to work directly from the command line, the Visual Studio integration is a compelling option. While the Extension Manager Online gallery contains a WiX download:

image

This did not work for me. Instead I downloaded and installed the MSI from the CodePlex site.

Votive, the Visual Studio plug-in, encourages making the installer part of your solution. It adds a number of project types to the product:

image

You can add the set-up project alongside the project containing the source code and maintain the whole solution together – installation should not be a last minute scramble.

image

Getting started is straightforward, you just add a reference in your set-up project to the project containing the application that you want to install. In our case, we want to install the bootstrapper contained in the ExpertAssistantCO project:

image

The set-up project is configured out of the box to generate a couple of WXS files for you based on the VS project file. The command line tool HEAT can create a WXS file from a number of different sources including a directory, VS project or an existing MSI. To enable HEAT, set the Harvest property to true and this will re-create the WXS files on each build based upon the project file. By simply adding a project reference to the set-up project you will have a MSI on the next solution compile. The intermediary files can be found by choosing to show all files in the Solution Explorer:

image

The obj/Debug subfolder will contain the generated wxs file and the wixobj files compiled from them. The bin\Debug subfolder is the default location for the MSI. The two files of interest are the ExpertAssisantCO.wxs, which contains the files from the referenced ExpertAssistantCO project and the Product.wxs which contains configuration information for the installation process. Building the project will invoke the WiX tooling: candle, preprocesser that transforms .wix into .wixobj, and light, processes wixobj files to create an MSI.

Of course, the out-of-the-box experience can only go so far and so the generated WXS likely needs to be augmented. Our approach was to use HEAT (via the Harvest option) to generate the initial WXS files and then the generation was disabled. The ExpertAssistantCO.wxs file was moved into the project for manual editing and the product.wxs file contains the bulk of the custom code.

The ExpertAssistantCO.wxs contains the list of files involved in the project, this includes source files, built files and documentation. Each file is wrapped in a separate Fragment and given a unique component Id:

<Fragment>
<DirectoryRef Id=”INSTALLLOCATION”>
<Component Id=”cmpExpertAssistantCOexe”
Guid=”????????-????-????-????-????????????” KeyPath=”yes”>
<File Id=”filExpertAssistantCOexe” Source=”$(var.ExpertAssistantCO.TargetDir)\ExpertAssistantCO.exe” />
</Component>
</DirectoryRef>
</Fragment>

The generated code has been changed to specify an explicit GUID and to set the KeyPath attribute. The actual GUID has been replaced with ?s, the KeyPath attribute is used to determine if the component already exists – more here. The shouting INSTALLLOCATION is an example of a public property, in the MSI world public properties are declared in full uppercase. The directory the file will be installed into is declared in the product.wxs file and referenced here. The $(var.ExpertAssistantCO.TargetDir) demonstrates a pre-processor directive that allows VS solution properties to be accessed, in this case to determine the source location of the file.

Components can be grouped together to provide more manageable units, for example:

<Fragment>
<ComponentGroup Id=”ExpertAssistantCO.Binaries”>
<ComponentRef Id=”cmpExpertAssistantCOexe” />
<ComponentRef Id=”cmpICSharpCodeZipLib” />
<ComponentRef Id=”cmpAderantDeploymentClient”/>
</ComponentGroup>
</Fragment>

The generated referencedProject.wxs file contains each file declared within a component and then logical groupings for the components. The more interesting aspects of WiX belong to the product.wix file. This is where registry keys, short cuts, remove actions and other items are set-up.

On to the product.wix then and the first line:

<Wix xmlns=”http://schemas.microsoft.com/wix/2006/wi”
xmlns:util=”
http://schemas.microsoft.com/wix/UtilExtension”
RequiredVersion=”3.5.0.0″>

Here we are referencing two namespaces, the default namespace is the standard WiX schema and the second util namespace enables the use of a WiX extension library. Extension libraries contain additional functionality usually grouped by a common thread. The library needs to be added as a project reference:

image

Available extension libraries can be found in C:\Program Files (x86)\Windows Installer XML v3.5\bin :

image

A drill-down into the various extension schemas is provided here. The utility extension referenced above allows Xml file manipulation which we will see later. The requiredVersion sets the version of WiX we are depending on to compile the file.

Next we define the product:

<Product Id=”????????-????-????-????-????????????”
Name=”ExpertAssistant”
Language=”1033″
Codepage=”1252″
Version=”8.0.0.0″
Manufacturer=”Aderant”
UpgradeCode=”????????-????-????-????-????????????”>

The GUIDs are used to uniquely identify the product installer and so you want to ensure you create a valid GUID using tooling such as GuidGen.exe. Following the product, we define the package:

<Package InstallerVersion=”200″
Compressed=”yes”
Manufacturer=”Aderant”
Description=”Expert Assistant Installer”
InstallScope=”perUser”
/>

The attribute worth calling out here is the InstallScope. This can be set to perMachine or perUser, setting to perMachine requires elevated privileges to install. We want to offer an install to all users without requiring elevated privilege and so have a per user install. This has implications later on when we have to set-up user specific items such as Start Menu shortcuts.

Next we move onto properties:

<!– Properties –>
<Property Id=”EXPERTSHAREPATH” Value=”\\MyShare\ExpertShare” />
<Property Id=”EXPERTENVIRONMENTNAME” Value=”MyEnvironment” />
<Property Id=”EXPERTLOCALINSTALLPATH” Value=”C:\AderantExpert\” />

<Property Id=”ARPPRODUCTICON” Value=”icon.ico” />
<Property Id=”ARPNOMODIFY” Value=”1″ />
<Property Id=”WixShellExecTarget” Value=”[#filExpertAssistantCOexe]”/>

The first three properties are custom public properties defined for this installer, public properties must be declared all upper case. A public property can be provided on the msiexec command line or via an MST file to customize the property value.

The properties with a prefix of ARP relate to the Add Remove Programs control panel, now Programs and Features in Windows 7. The ARPPRODUCTICON is used to set the icon that appears in the installed programs list. The ARPNOMODIFY property removes the Change option from the control panel options:

image

In contrast, Visual Studio SP1 supports the Change option:

image

Other ARP properties can be found here.

The final property WixShellExecTarget specifies the file to be executed when the install completes. This is a required parameter of a custom action that we will come to later. The [#filExpertAssistantCOexe] is a reference to a file declared in the ExpertAssistantCO.wxs file.

<File Id=”filExpertAssistantCOexe” Source=”$(var.ExpertAssistantCO.TargetDir)\ExpertAssistantCO.exe” />

Next up we come to one of the areas that stumped me for a while, how to set a property value. Some attributes can directly reference a property by surrounding the property name in [] and have the value swapped in, e.g.

<Directory Id=”dirEnvironmentFolder” Name=”[EXPERTENVIRONMENTNAME]” >

However this is not supported when setting properties, therefore the following does not result in substitution:

<Property Id=”Composite” Value=”[PROPERTY1] and [PROPERTY2]” />

Instead the SetProperty element is used:

<!– Set-up environment specific properties–>
<SetProperty Id=”dirEnvironmentFolder” Value=”C:\AderantExpert\[EXPERTENVIRONMENTNAME]” After=”CostInitialize”/>
<SetProperty Id=”EXPERTLOCALINSTALLPATH” Value=”C:\AderantExpert\[EXPERTENVIRONMENTNAME]\Applications” After=”CostInitialize” />

When setting the property, the appropriate time to perform the action needs to be set using either the Before or After attribute. This injects the action into the appropriate place in the list of actions to perform. To determine the order, I used Orca to view the InstallExecuteSequence:

image

A final property that is set is the EnableUserControl, which allows the installer to pass all public properties to the server side during a managed install.

<Property Id=”EnableUserControl” Value=”1″ />

Note: the preferred approach is to set the Secure attribute individually to Yes on each property declaration that supports user control (I have only just learnt this while writing up the posting).

A final element for this post is Icon which specifies an icon file.

<Icon Id=”icon.ico” SourceFile=”$(var.ExpertAssistantCO.TargetDir)\Expert_Assistant_Icon.ico”/>

The icon Id was used by the ARPPRODUCTICON to set the icon seen in the install programs control panel.

There’s more to come, however this post has become long enough in its own right. In the next post I’ll drill into setting registry keys, determining the install location, Start Menu settings and more.

So far we’ve walked through the following WiX code:

<?xml version=”1.0″ encoding=”UTF-8″?>
<Wix xmlns=”
http://schemas.microsoft.com/wix/2006/wi” xmlns:util=”http://schemas.microsoft.com/wix/UtilExtension” RequiredVersion=”3.5.0.0″>
<Product Id=”????????-????-????-????-????????????”
Name=”ExpertAssistant”
Language=”1033″
Codepage=”1252″
Version=”8.0.0.0″
Manufacturer=”Aderant”
UpgradeCode=”????????-????-????-????-????????????”>
<Package InstallerVersion=”200″
Compressed=”yes”
Manufacturer=”Aderant”
Description=”Expert Assistant Installer”
InstallScope=”perUser”
/>

    <!– Public Properties –>
<Property Id=”EXPERTSHAREPATH” Value=”\\MyShare\ExpertShare” />
<Property Id=”EXPERTENVIRONMENTNAME” Value=”MyEnvironment” />
<Property Id=”EXPERTLOCALINSTALLPATH” Value=”C:\AderantExpert\Environment\Applications” />
<Property Id=”ARPPRODUCTICON” Value=”icon.ico” />
<Property Id=”ARPNOMODIFY” Value=”1″ />
<Property Id=”WixShellExecTarget” Value=”[#filExpertAssistantCOexe]”/>

    <!– Set-up environment specific properties–>
<SetProperty Id=”dirEnvironmentFolder” Value=”C:\AderantExpert\[EXPERTENVIRONMENTNAME]” After=”CostInitialize”/>
<SetProperty Id=”EXPERTLOCALINSTALLPATH” Value=”C:\AderantExpert\[EXPERTENVIRONMENTNAME]\Applications” After=”CostInitialize” />

<!– All users to access the properties, not just elevated users–>
<Property Id=”EnableUserControl” Value=”1″ />

    <!– Icons –>
<Icon Id=”icon.ico” SourceFile=”$(var.ExpertAssistantCO.TargetDir)\Expert_Assistant_Icon.ico”/>