Deploying Desktop Applications

Over the last couple of weeks I’ve been re-examining the desktop deployment strategy we have for Aderant applications. This installer navel gazing has resulted in augmenting our existing ClickOnce based approach with an MSI. The next couple of blog posts will drill into my ClickOnce headaches and my embracing of WiX to create MSIs.

Desktop Deployment Challenges

During the first steps into the new millennium, rich/smart/fat client applications which install binary files onto the local host fell out of fashion in the face of the centralized web deployment model. Deploying desktop applications was a delicate process: prior to the world of managed code, COM applications roamed the world and brought with them the plague of ‘DLL Hell’. Installing a new piece of software into a Windows environment could very well result in previously stable applications breaking, registries full of COM ClassIDs could become corrupt and the use of regedit and regsvr32 became almost accepted mainstream tools. The Microsoft .NET framework worked very hard overcome the fragility of the COM world but introduces its own complexities: managing versions, the global assembly cache, shadow copies and so on. Regardless of the technology, the software must be rolled out to each individual desktop and managed on each desktop. Installer technology supported the concept of repair but did not support automatic upgrades; additionally the installer often required administrator privileges to install.

A Mexican stand-off ensued: desktop applications allowed richer user experiences, a simpler programming model and the ability to run independently of a network connection; web applications have a much simpler deployment model and updates.

Of course, it is quite natural to want the best of both worlds and so various approaches have evolved to try to satisfy this, one of these approaches is ClickOnce.

Microsoft ClickOnce

As part of the .NET v2 framework, Microsoft introduced a new deployment option for desktop applications called ClickOnce. The goals of ClickOnce are to solve the following three major issues:

  1. Difficulty updating a deployed application
  2. Impact of the application on the users’ desktop
  3. Administrator privileges required to install apps

Conceptually, ClickOnce provides a central deployment model for desktop applications which does not require the installing user to have elevated privileges. It supports both the automatic repair and upgrading of applications and supports the use of an HTTP channel to distribute the installer. This last point is a game changer – desktop applications could now be installed and updated over HTTP on port 80, not requiring any additional firewall exceptions. This allows ClickOnce to be used to deploy applications over the internet as well as a corporate network. A couple of high profile, non-Microsoft examples of ClickOnce adoption demonstrates an industry acceptance: Google Chrome and Join.Me.

Corporate vs. Internet Installation

The ability to distribute software over the internet is something of a double edged sword should you choose to use ClickOnce to distribute software within a corporate network. The internet is untrustworthy, therefore mechanisms are required within ClickOnce to establish trust. These mechanisms are on by default and some cannot be switched off. To discuss some of these first requires an understanding of how an application is deployed via ClickOnce.

A ClickOnce deployment is basically managed by two XML files called manifests. There is an application manifest (.manifest) and a publisher manifest (.application). There’s the first annoyance – the application manifest does not have the .application file extension, which is so confusing. The application manifest contains a description of the files to be deployed and other settings that are controlled by the author of the application. The publisher manifest points to the application manifest, and contains information that is pertinent to the firm installing the application such as the publisher Url, update policy and the minimum required version. Each manifest is by default tamper proof, the manifests include a hash so that they cannot be altered without the use of appropriate tools such as the Manifest Generation and Editing Tool(mage.exe) – note: changing the application manifest invalidates the publisher manifest. It is possible to remove the hash from individual files within the manifest. The tamper proofing is only part of the trust story, you also need to be able to trust that the install comes from a trusted application vendor and that is was installed from a trusted publisher. Identity on the internet is established using certificates and so both of the manifests can be signed using a Code Signing certificate as evidence.

Time for the second annoyance… to install a ClickOnce application silently requires that both the manifests are signed using a certificate that is trusted as a software publisher by the machine undertaking the installation. This is a serious pain for ISVs. In our case, we can signed the application manifest with our company certificate and indeed should do so. However, if one of the manifests is signed then the other must also be signed, therefore the publisher manifest must be signed. The publisher of the application is the firm installing our software and so they must have a code signing certificate and ensure that the certificate is trusted by each desktop machine. If you choose to sign neither manifest, the installing user will see a security dialog window asking them if they trust the publisher and location – most corporate users will not know what they are being asked.

An additional step in the trust process is ensuring that the publisher URL belongs to the Trusted Site  (or Local Intranet Site) security zone of IE.

All of this protection makes sense for internet deployments but less so for intranet deployments within a corporate network which is implicitly trusted.

While the silent install is possible, if somewhat complex, a silent remove is not supported, the user will always be prompted to confirm the removal of the application. This single anomaly causes a great deal of frustration within the firms we provide software too. It is possible to achieve an automated uninstall but it requires the use of some nasty automation code:

On Error Resume Next
Set objShell = WScript.CreateObject(“WScript.Shell”)
objShell.Run “taskkill /f /im [your app process name]*”
objShell.Run “[your app uninstall key]”
Do Until Success = True
Success = objShell.AppActivate(“[your window title]”)
Wscript.Sleep 200
Loop
objShell.SendKeys “OK”

This VBScript snippet was taken from:http://social.msdn.microsoft.com/Forums/en-US/winformssetup/thread/51a44139-2477-4ebb-8567-9189063cf340/. The app uninstall key will be found in the registry:

image

Why am I so down on this? ClickOnce is a pull installation, it is expected that a user will choose to install the software. This is not so often the case within a corporate environment with a locked down desktop – applications are pushed to users using management software such as System Center Configuration Manager or via Active Directory group policies.

Additional Headaches

Beyond the heartache to perform a silent install and remove of the application, there are some other pain points of note.

ClickOnce and Citrix don’t mix well. ClickOnce permits installs by a non-privileged user, to do so the application is installed into the users local profile, this means on a Citrix environment each user has a separate install of the software taking up disk space.

The path of the application is a monster too, for example the Join.Me installer placed the files into:

C:\Users\username\AppData\Local\Apps\2.0\1A6OWNZM.ETC\EEWH8699.6PL\join..tion_43a0dbe7f0f75062_0001.0000_9871fcdc8aa605d7

If you have either long filenames or a deep file hierarchy, you can run out of characters for your file paths (260 character limit).

ClickOnce does not provide any extensibility points to run custom pre or post deployment actions.

In our particular case, we need to deploy assemblies to the application after the initial installation due to on-site firm customizations. ClickOnce does have a self-updating capability but we needed something more dynamic – more on this later…

In Defence of ClickOnce

This has been a rather negative look at ClickOnce so far so I do feel the need to provide some positive balance. ClickOnce is great at deploying apps over HTTP, it provides a self-repair and self-updating installer, it allows non-admin users to install software safely, it goes to great lengths to ensure the software you install can be trusted.

The case for ClickOnce was strong enough that we’ve used it for the Aderant Golden Gate desktop applications. The negatives are from unusual customization scenarios and field experience within corporate networks which has lead to the investigation into a MSI centric approach.

If you are pursuing a ClickOnce installer and need to dig a little deeper than the VS2010 template, I  strongly recommend getting Brian Noyes book: http://www.amazon.com/Smart-Client-Deployment-ClickOnce-Applications/dp/0321197690.

In the next post, I’ll discuss our customization requirements that lead to using ClickOnce to install a bootstrapper.

New Virtual Home

Welcome to my new home, within the year I’ll be evicted from Apples MobileMe hosting and so I’m forced to relocate.

The goal of this blog continues on from where the previous finished, to post about the Microsoft technology stack, in particular the .NET platform. Most of the content falls out of my day job as a software architect for ADERANT and hopefully will be of use to the Microsoft community. The initial set of postings will be a clean up and summary of content from my current blog. WordPress looks to have a host of features that MobileMe did not and so I look forward to playing around with the new platform too.

Creating a generic ping proxy

In the previous post we walked through the steps required to implement a service monitoring ‘Ping’ operation as a WCF endpoint behavior. This allows us to add the ping functionality to an endpoint using WCF configuration alone. Following on from this, here’s a generic implementation of a Http proxy class to call Ping on any service.

Last time we left off having created a proxy to a test service using the WcfTestClient application.

Ping operation from metadata in the WCF Test Client

Switching to the XML view lets us see the SOAP request and reply message for a ping.

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header>
    <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://aderant.com/expert/contract/TestServiceContract/IService1/Ping</Action>
  </s:Header>
  <s:Body>
    <Ping xmlns="http://aderant.com/expert/contract/TestServiceContract" />
  </s:Body>
</s:Envelope>

and the response:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Header />
  <s:Body>
    <PingResponse xmlns="http://aderant.com/expert/contract/TestServiceContract">
      <PingResult>2011-10-18T01:28:57.0222683Z</PingResult>
    </PingResponse>
  </s:Body>
</s:Envelope>

The namespace highlighted in green is the namespace we gave to our service as part of its ServiceContract attribution:

[ServiceContract(Namespace="http://aderant.com/expert/contract/TestServiceContract")]
public interface IService1 {

The SOAP message varies from service to service according to the namespace of the contract and the type of the contract. If we know these two values then we can construct the appropriate SOAP message for a generic client.

private static void CallWcfServiceUsingGenericProxy() {
    const string address = @"http://localhost/TestService/Service1.svc";
    const string serviceContractType = "IService1";
    const string serviceContractNamespace = @"http://aderant.com/expert/contract/TestServiceContract";
    Console.WriteLine("Pinging WCF service using the generic proxy...");
    DateTime utcStart = DateTime.UtcNow;
    string response = PingService(serviceContractType, serviceContractNamespace, address);
    DateTime utcFinished = DateTime.UtcNow;
    DateTime serverPingTimeUtc = ProcessPingResponse(response, serviceContractNamespace);
    WriteTimingMessage(utcStart, serverPingTimeUtc, utcFinished);
}

The calling of the generic proxy is separated from the processing of the response so that a timing can be made around just the communication.

private static string PingService(string serviceContractType, string contractNamespace, string address) {
    string pingSoapMessage = string.Format(@"", contractNamespace);
    if (!contractNamespace.EndsWith("/")) { contractNamespace = contractNamespace + "/";}
    WebClient pingClient = new WebClient();
    pingClient.Headers.Add("Content-Type", "text/xml; charset=utf-8");
    pingClient.Headers.Add("SOAPAction", string.Format(@"""{0}{1}/Ping""", contractNamespace, serviceContractType));
    string response = pingClient.UploadString(address, pingSoapMessage);
    return response;
}

To Ping the service, we construct the SOAP message and use a WebClient to make the call. The web client requires headers to be added for the content type and the SOAPAction which tells the WCF Dispatcher which method we want to call.

To process the SOAP message returned from the Ping message we use:

private static DateTime ProcessPingResponse(string response, string contractNamespace) {
    XDocument responseXml = XDocument.Parse(response);
    XElement pingTime = responseXml.Descendants(XName.Get("PingResult", contractNamespace)).Single();
    DateTime serverPingTimeUtc = DateTime.Parse(pingTime.Value).ToUniversalTime();
    return serverPingTimeUtc;
}

Now we have the UTC DateTime from the server when it processed the response.

All good, if we know the contract namespace and interface type for the service we can ping it. As we saw, this information is attributed on the service contract class. To simplify the proxy code a little, we can use reflection to determine this information given just the contract type.

public class WcfServicePinger<TServiceContract> where TServiceContract : class {
    public DateTime Ping(string addressUri) {
        string @namespace = string.Empty;
        ServiceContractAttribute attribute = typeof(TServiceContract)
            .GetCustomAttributes(typeof(ServiceContractAttribute), true)
            .FirstOrDefault() as ServiceContractAttribute;
        if(attribute == null) {
            throw new ArgumentException("The specified type {0} is not a WCF service contract.", typeof(TServiceContract).Name);
        }
        if(string.IsNullOrWhiteSpace(attribute.Namespace)) {
            @namespace = "http://tempuri.org";
        } else {
            @namespace = attribute.Namespace;
        }
        return new WcfServicePinger().Ping(addressUri, typeof(TServiceContract).Name, @namespace);
    }
}

The non-generic WcfServicePinger calls our previous code, as above:

public class WcfServicePinger {
    public DateTime Ping(string addressUri, string serviceContractTypename, string serviceContractNamespace) {
        string response = PingService(serviceContractTypename, serviceContractNamespace, addressUri);
        DateTime serverPingTimeUtc = ProcessPingResponse(response, serviceContractNamespace);
        return serverPingTimeUtc;
    }

So in the end we would use:

DateTime serverTime = new WcfServicePinger<IService1>().Ping(“http://localhost/TestService/Service1.svc”);

Note that we have constructed a generic proxy that calls an Http endpoint. I did try to construct a net.tcp generic proxy class too but it broke my time box. The Ping method can be called via net.tcp using a proxy generated from the endpoint metadata.

Checking WCF Service Availability using an Endpoint Behavior

A common operational requirement for an SOA is the ability to determine if a service is available. Just as it is common to ping a machine, we also want to be able to ping an individual service to determine that it is running and able to respond to messages.

Our first pass at solving this issue was to introduce an IPingable interface that each of our service facades would implement. The code was pushed into our service base class and the software factory updated as appropriate. However, this didn’t feel quite right. Other service extensions such as the metadata endpoint were not so invasive, it can be established by a simple addition to the service configuration file. We felt we wanted the same configurable nature for a ping mechanism and so set out to figure out how to create a custom WCF behavior.

This post walks through the creation of a WCF Endpoint Behavior which will add a Ping() method to a service. The method returns the DateTime, in UTC, stating when the server processed the request. This can be used to establish how long a basic roundtrip to the service is taking for both the request and the reply. The use of an endpoint behavior allows us to add this functionality to any service, therefore we can add it retrospectively to services that we have already created without needing to change them.

The source code for this post is available from my DropBox.

Let’s start by looking at how we would configure such an endpoint behavior. For an existing service, we would copy the assembly containing the endpoint behavior into the bin folder for the service and then add some entries into the web.config:

<configuration>
    <system.serviceModel>
        <extensions>
            <behaviorExtensions>
                <add name="Ping" type="CustomWcfBehaviors.PingEndpointBehavior, CustomWcfBehaviors" />
            </behaviorExtensions>
        </extensions>
        <behaviors>
            <endpointBehaviors>
                <behavior>
                    <Ping>
                </behavior>
            </endpointBehaviors>
        </behaviors>

We register the type [CustomWcfBehaviors.PingEndpointBehavior in assembly CustomWcfBehaviors] that is our custom behavior as a behavior extension and then declare that we want to use the extension. The configuration above sets up a default behavior that will be applied to all endpoints since we haven’t explicitly named it. That’s it, fetch the metadata for the service it will now contain a Ping() method.

The configuration of the behavior is reasonably straight forward, let’s look at the implementation…

namespace CustomWcfBehaviors {
    public class PingEndpointBehavior: BehaviorExtensionElement, IEndpointBehavior {
    private const string PingOperationName = "Ping";
    private const string PingResponse = "PingResponse";

The behavior needs to derive from BehaviorExtensionElement which allows it to be declared in the service configuration as an extension element. We need to introduce two overrides:

// factory method to construct an instance of the behavior
protected override object CreateBehavior(){
    return new PingEndpointBehavior();
}
// property used to determine the type of the behavior
public override Type BehaviorType{
    get { return typeof(PingEndpointBehavior); }
}

Now onto the real work, the IEndpointBehavior implementation:

public void Validate(ServiceEndpoint endpoint) {}
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) {}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) {}

public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) {
    if(PingOperationNotDeclaredInContract(endpoint.Contract)) {
        AddPingToContractDescription(endpoint.Contract);
    }
    UpdateContractFilter(endpointDispatcher, endpoint.Contract);
    AddPingToDispatcher(endpointDispatcher, endpoint.Contract);
}

There are four methods that we need to implement but only one that we do any work in. The ApplyDispatchBehavior is where we add our code to manipulate how the endpoint invokes the service operations.

Endpoints map to the ABC of WCF [address, binding and contract]. What we want to do is extend the contract with a new service operation. A service host may allow multiple endpoints to expose the service contract on different protocols. For example, we may choose to host our service using both http and net.tcp. If we use IIS as our service host, this is configured via the Advanced Settings… of the Manage Application… context menu option.

The ‘Enabled Protocols’ lists the protocols we want to use. If we have more than one protocol listed then we have to take care that we don’t attempt to add the new Ping operation multiple times to our contract – once per binding type. To check that we haven’t already added the operation we call a small Linq query…

private bool PingOperationNotDeclaredInContract(ContractDescription contract) {
    return ! contract
        .Operations
        .Where(operationDescription =>
            OperationDescription.Name.Equals(PingOperationName,
                StringComparison.InvariantCultureIgnoreCase))
        .Any();
}

If the Ping operation is not found then we need to add it:

private void AddPingToContractDescription(ContractDescription contractDescription) {
    OperationDescription pingOperationDescription = new
    OperationDescription(PingOperationName, contractDescription);
    MessageDescription inputMessageDescription = new MessageDescription(
        GetAction(contractDescription, PingOperationName), MessageDirection.Input);
    MessageDescription outputMessageDescription = new MessageDescription(
        GetAction(contractDescription, PingResponse), MessageDirection.Output);
    MessagePartDescription returnValue = new MessagePartDescription("PingResult",
        contractDescription.Namespace);
    returnValue.Type = typeof(DateTime);
    outputMessageDescription.Body.ReturnValue = returnValue;
    inputMessageDescription.Body.WrapperName = PingOperationName;
    inputMessageDescription.Body.WrapperNamespace = contractDescription.Namespace;
    outputMessageDescription.Body.WrapperName = PingResponse;
    outputMessageDescription.Body.WrapperNamespace = contractDescription.Namespace;
    pingOperationDescription.Messages.Add(inputMessageDescription);
    pingOperationDescription.Messages.Add(outputMessageDescription);
    pingOperationDescription.Behaviors.Add(new DataContractSerializerOperationBehavior(pingOperationDescription));
    pingOperationDescription.Behaviors.Add(new PingOperationBehavior());
    contractDescription.Operations.Add(pingOperationDescription);
}

Here we are creating an OperationDescription to add to our ContractDescription, this adds the operation specification for our Ping operation to the existing contract. The code to execute is encapsulated by the PingOperationBehavior().

public class PingOperationBehavior : IOperationBehavior {
    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation) {
        dispatchOperation.Invoker = new PingInvoker();
    }
    public void Validate(OperationDescription operationDescription) {}
    public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) {}
    public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) {}
 }

Similar to the endpoint behavior, we need to implement an interface in this case the IOperationBehavior. The method signatures are similar and we need to fill out the ApplyDispatchBehavior method to call an IOperationInvoker to execute our Ping implementation:

internal class PingInvoker : IOperationInvoker {
    public object[] AllocateInputs() {
        return new object[0];
    }
    public object Invoke(object instance, object[] inputs, out object[] outputs) {
        outputs = new object[0];
        return DateTime.UtcNow;
    }
    public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state) {
        throw new NotImplementedException();
    }
    public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result) {
        throw new NotImplementedException();
    }
    public bool IsSynchronous {
        get { return true; }
    }
 }

The ping operation is so simple that there is no asynchronous implementation. All we do is return the current date time on the server in UTC.

So where are we? Well, we’ve added the Ping method to our existing service contract and mapped it to an IOperationBehavior which will dispatch an IOperationInvoker to call the code.

Next up, we have to update the endpoint dispatcher so that it knows what to do if it receives a Ping request message. The endpoint dispatcher maintains a list of actions that it knows how to action. We need to update this list so that it includes our new ping action. To do this we just refresh the action list from the contract description operations:

private void UpdateContractFilter(EndpointDispatcher endpointDispatcher, ContractDescription contractDescription) {
    string[] actions = (from operationDescription in contractDescription.Operations
                        select GetAction(contractDescription, operationDescription.Name)
                       ).ToArray();
    endpointDispatcher.ContractFilter = new ActionMessageFilter(actions);
}

Finally we need to add a new dispatch operation to the endpoint dispatcher so that it calls our PingOperation when the Ping action is received.

private void AddPingToDispatcher(EndpointDispatcher endpointDispatcher, ContractDescription contractDescription) {
    DispatchOperation pingDispatchOperation = new DispatchOperation(endpointDispatcher.DispatchRuntime,
        PingOperationName,
        GetAction(contractDescription, PingOperationName),
        GetAction(contractDescription, PingResponse));

    pingDispatchOperation.Invoker = new PingInvoker();
    endpointDispatcher.DispatchRuntime.Operations.Add(pingDispatchOperation);
}

private string GetAction(ContractDescription contractDescription, string name) {
    string @namespace = contractDescription.Namespace;
    if(!@namespace.EndsWith("/")) { @namespace = @namespace + "/"; }
    string action = string.Format("{0}{1}/{2}", @namespace, contractDescription.Name, name);
    return action;
}

Well, we are now done on the server side. We’ve created a couple of new classes:
• PingEndpointBehavior
• PingOperationBehavior
• PingInvoker

These three classes allow us to add a Ping method to a service by adding the Ping behavior via the service configuration file.

To test this, you can use the WcfTestClient application. The sample code demonstrates this by calling Ping on a standard WCF and WF service created by Visual Studio, see my DropBox.

In the next post I’ll discuss how we create a generic service proxy to call Ping.

UPDATE: the code works for the basicHttpBinding but not for wsHttpBinding.

UPDATE: Code now available from github.com/stefsewell/WCFPing

Securing WF & WCF Services using Windows Authentication

To finish off the DEV404 session Pete and I presented at TechEd NZ, I gave a brief run through of the steps required to get Windows Authentication working in a load balanced environment using kerberos. Given the number of camera phones that appeared for snaps I’m going to assume this is a common problem with a non-intuitive solution…

The product I work on is an on-premise enterprise solution that uses the Windows Identity to provide an authenticated credential against which to authorize user requests. We host our services in IIS/Windows Server AppFabric and take advantage of the Windows Authentication provided by IIS. This allows one of two protocols to be used: kerberos and NTLM, which have quite separate characteristics.

Why Use Kerberos?
There are two main reasons we want to use kerberos over NTLM:

1. Performance: NTLM uses a challenge response pattern for authentication which leads to a high network utilization. During performance testing we saw a high volume of NTLM challenges which ultimately throttled our ability to serve requests. Kerberos uses tickets which can be cached permitted a better performing protocol.

1. Double hops: NTLM does not flow credentials – the canonical example is a user requesting serviceA on server1 to access a secured resource on server2. Server1 cannot flow the users identity to server2.

Kerberos and Load Balancing
We want to run our services within a load balanced cluster to avoid single points of failure and to be able to grow resources to meet demand as required, without having to adopt bigger tin. The default configuration of IIS does not encourage this… the Application Pools run as a local machine account. This is a significant issue for Kerberos because of the manner in which the protocol encrypts the tickets passed between client, TGS and target server. The password of the account running the service is used to encrypt tickets so that only a process running under that account can decrypt the message. The default use of a machine specific account prevents a ticket granting access to serviceX on server A also being used to access serviceX on server B.

The following steps are required to fix this:

1. Use a common domain account for the applications pools.

We use a DOMAIN\service.expert account to run our services. This domain account is granted log on as a service and log on as a batch job rights on each of the application servers.

2. Register an SPN mapping the service class to the account.

We run our services on HTTP and so register the load balancer address with the domain account used to run the services:

>setspn -a HTTP/clusteraddress serviceAccount

We are using the WCF BasicHttpBinding which does not require the client to ensure the service is running as a particular user (to prevent man in the middle attacks). If you are using any other type of binding then the client needs to state who it expects the service to be running as.

3. Configure IIS to use the application pool account rather than a machine account

system.webServer/security/authentication/windowsAuthentication useAppPoolCredentials must be set to true.

4. Configure IIS to allow kerberos authentication tokens to be cached

system.webServer/security/authentication/windowsAuthentication authPersistNonNTLM must be set to true.

See also http://support.microsoft.com/kb/954873

5. Ensure the cluster address is considered to be in the Local Intranet zone


Kerberos tokens are not supported in the Internet zone, therefore the URL for your services must be considered to be trusted. The standard way to implement this is to roll out a group policy that adds your domain to the local intranet zone settings.

The slide deck for the talk is available from http://public.me.com/stefsewell/

DEV404 – Hardcore Workflow 4

Thanks to everyone who attended the DEV404 session at TechEd NZ. We wanted to cover off some new material that we haven’t seen else where and so Pete concentrated on the extensibility of the WorkflowServiceHost and the WorkflowServiceHostFactory. Before we got there I felt we needed a lead in and so I gave a brief overview of the workflow runtime, much of the material was covered in depth at PDC09 in the session Workflow 4 Inside Out.

The key point was the single threaded nature of the workflow scheduler. There is a single thread responsible for scheduling the execution of the activities in the activity tree, you really do not want to block this thread. This is the thread that runs the Execute method of synchronous activities, to show this in action I built the following workflow:

There’s a collection of strings populated with URLs of a few well known websites.

Then, there is a ParallelForEach that iterates over the collection and fetches the contents of the web page. The FetchUrl activity was written as follows:

using System.Activities;
using System.IO;
using System.Net;

namespace WorkflowRuntime.Activities {
    /// <summary>
    /// Fetch HTTP resource synchronously 
    /// </summary>
    public sealed class FetchUrlSync: CodeActivity {
        public InArgument Address { get; set; }
        protected override string Execute(CodeActivityContext context) {
            string address = context.GetValue(Address);
            string content = string.Empty;
            WebRequest request = HttpWebRequest.Create(address); 

            using(HttpWebResponse response = request.GetResponse() as HttpWebResponse) {
                if(response != null) {
                    using (Stream stream = response.GetResponseStream()) {
                        if (stream != null) {
                            StreamReader reader = new StreamReader(stream);
                            content = reader.ReadToEnd();
                        }
                    }
                }
            }
            return content;
        }
    }
}

The HttpWebRequest class is used to fetch the page contents. Running the workflow gives the following results:

The Urls are fetched one at a time, the same behavior that you would see if the activities were scheduled in a sequence rather than in a parallel. Why? This is the single threaded scheduler, it must wait for the Execute() method of the activity to complete before it can schedule the next activity.

What we want to see is:

So how can we achieve this? Well, we have to rewrite the FetchUrl activity to perform its work asynchronously. The HttpWebRequest already has async support via the BeginGetResponse and EndGetResponse method pairs; this is a standard pattern in .NET for async programming. The FetchUrl activity becomes:

using System;
using System.Activities;
using System.IO;
using System.Net;

namespace WorkflowRuntime.Activities {
    /// <summary>
    /// Fetch HTTP resource asynchronously
    /// <summary>
    public sealed class FetchUrlAsync: AsyncCodeActivity {
        public InArgument Address { get; set; }

        protected override IAsyncResult BeginExecute(AsyncCodeActivityContext context, AsyncCallback callback, object state) {
            string address = context.GetValue(Address);
            WebRequest request = HttpWebRequest.Create(address);
            context.UserState = request;
            return request.BeginGetResponse(callback, state);
        }

        protected override string EndExecute(AsyncCodeActivityContext context, IAsyncResult result) {
            string content = string.Empty; WebRequest request = (WebRequest) context.UserState;
            using (HttpWebResponse response = request.EndGetResponse(result) as HttpWebResponse) {
                if (response != null) {
                    using (Stream stream = response.GetResponseStream()) {
                        if (stream != null) {
                            StreamReader reader = new StreamReader(stream);
                            content = reader.ReadToEnd();
                        }
                    }
                }
            }
            return content;
        }
    }
}

We call the BeginGetResponse method passing in the callback and state object given to us by the workflow runtime as part of the AsyncCodeActivity.BeginExecute method. When the fetch is completed, by a separate worker thread, the workflow runtime is called back and the EndExecute method is invoked. In this method we take the resultant stream and read the contents into a string that we return. The workflow scheduler thread is no longer responsible for fetching the content, therefore it can schedule the fetch of the next Url and we get the parallel behavior we expect. All fetches are scheduled and then the workflow runtime waits to be called back by each worker thread when complete.

The time taken for the synchronous fetches to complete is the sum total of all fetches. For the asynchronous fetches, it is the time of the longest fetch plus a little overhead.

A basic rule of workflow is to perform I/O asynchronously and not to block the scheduler thread.

The sample code and PPT deck is available from https://public.me.com/stefsewell

A solution to WinRM in a NLB cluster…

I’ve written a couple of posts discussing the remoting options for PowerShell:
• fan-out model – Windows Remote Management service (WinRM)
• fan-in model – IIS hosted PowerShell endpoint (using the IIS WinRM extension)

When running load balanced WCF services in IIS that are secured using Windows Authentication, the web applications are mapped to app pools that use a domain account. This is required by kerberos to ensure that the encrypted messages can be decoded using a common set of credentials. By default, the HTTP SPN would be registered against the machine account, however this is changed to map to the domain account. This broke WinRM which is also an HTTP endpoint but runs as the network service, therefore the kerberos authentication failed because it is expected to be running under the domain account.

PowerShell supports two machine name formats, when setting the Invoke-Command -ComputerName parameter: the NETBIOS name and the fully qualified domain name (FQDN). To be able to call the WinRM service and authenticate using kerberos, you need to use the machine name format that is not used in the SPN. For example, if

HTTP/myserver.domain.com

is the SPN registered against the domain account used by the application pools, then

PS>icm -ComputerName myserver.domain.com -scriptblock { ‘foo’}

will fail, however

PS>icm -ComputerName myserver -ScriptBlock {‘foo’}

will succeed. It works because the SPN must be an exact match for the machine name used (though case insensitive on Windows). If HTTP/myserver was registered, the command would fail. [I tried using the IP address too but PowerShell reports an error saying it does not support that scenario unless the IP address is in the TrustedHosts list]. This is still a little ‘magic’ and the better way to do this is to enable CredSSP in PowerShell.

This discovery removes the need to use the fan-in model, which we’ve found to be more problematic than the WinRM Windows Service:
• Cannot use the IIS:/AppPools/ path, returns no results
• Cannot use IIS:/Sites/, throws a COM exception
• AppPool identity must have ‘Generate Security Audit’ right on the machine
• Intermittent failures with the Windows Process Activation

Another recent discovery is around the effect of the NETBIOS name with IE zone security. If a resource is consider to be outside the local intranet or trusted sites zone, then kerberos does not work – the ticket is not issued. Therefore using the FQDN requires the domain to be added to the local intranet zone sites. The use of the NETBIOS name however is considered to be within the local intranet zone and therefore no amendment to the zones are required.

One last tangential gotcha… it is possible to extend the probe path that IIS uses when looking for assemblies beyond the standard bin directory.

<runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
        <probing privatePath="bin;SharedBin" />
    </assemblyBinding>
</runtime>

However files not in the normal bin directory are not shadow copied, therefore you can get file locking that you don’t expect when updating the files – in the case above, the SharedBin.