Harin Sandhoo's Blog

August 24, 2015

Quick Tip to Triage Recycling Azure Cloud Services

Filed under: Uncategorized — Harin @ 8:25 am

If you are in an environment where your Azure Cloud Services are recycling, have RDP access to the instances, but can’t enable intellitrace; then try enabling Fusion Logs and copying fuslogvw.exe onto the server to figure out what are the missing dependencies.

In the fusion log viewer, if you filter to view only the failed bindings, then look for the RoleManager, you will see binding failure entries if a missing assembly is causing the recycling.

See Scott Hanselman’s post on how to enable the fusion logs.

Advertisements

January 16, 2015

Some Azure Internal Load Balancer (ILB) Takeaways

Filed under: Uncategorized — Harin @ 6:44 am

I’ve been working with a client that is exploring using Azure Web Roles to host enterprise services on a virtual network.  These web roles will be front ended by ILBs.

Here are a few takeaways that I thought were noteworthy:

  • Azure scopes ILB to the hosted service staging slot
  • ILB names can be the same between prod & staging
  • ILB static ip addresses cannot be the same within nor across hosted services (obviously)
  • VIP swaps cannot be done via the portal (throws an “internal error” in the portal)

I don’t think use of the Prod and Staging deployment slots is valid / makes sense if you are using ILBs as the external VIP swap is for the public load balancer.  From an operations perspective, I think it makes more sense to use a completely separate cloud service to avoid the confusion of which one is the active deployment.  However, I will caveat that if you have a large number of cloud services, you may more quickly run into the scalability limit of the number of cloud services in a subscription.

June 26, 2012

Using Windows Azure IaaS to host SharePoint 2010

Filed under: Azure, SharePoint — Harin @ 1:48 pm

This post is a follow-up to one I wrote awhile back, where we hosted SharePoint 2010 in a standalone VM Role (the Compute offering). That obviously was not supported / durable and was really just as a POC to see if it could be done.

Now that Microsoft has announced their Infrastructure as a Service (IaaS) offering (aka durable VM instances) and I’ve had some time to play with it, I thought it would be a good idea to share a powershell script I use to host SharePoint Farms in Microsoft’s Azure data centers in a way that will eventually be supported (once out of preview).

All this stuff is in preview, so proceed at your own risk.

I’ve created a sample powershell script after watching a great Tech Ed talk by Paul Stubbs. I don’t want to re-create good content for the sake of it, so I’ll just point you at his talk and give you the script to hopefully save you some time (since I couldn’t find his).

This script also assumes a lot of manual steps like rdp’ing into your domain controller box and running dcpromo, installing the SharePoint bits, configuring the SharePoint environment. It’s main purpose is to get you a physical environment where you can host SharePoint outside of your datacenter, so don’t expect to run it and have a fully configured SharePoint environment up and running.

The hands on labs are also good to get a feel of how to work with the powershell scripts.

Warning: The script sets up a lot of VMs (6 ->; 12 cores) and disks (14 ->; 6 OS disks, 7 100 GB data disks [for logs and data], and 1 15 GB data disk for the AD box). You will be charged for these, so make sure you delete the VMs, Disks, Virtual Network, services, once you are done.

In a production environment, you’ll likely want to add a redundant AD box, but it’s easy enough to do with the sample script.

I hope this saves you all some time.

Link to script: http://sdrv.ms/KBRxcf

Alt hosted on my company’s GitHub page:  https://github.com/AppliedIS/sp-azure-iaas

June 13, 2011

Integrating SharePoint and Mobile devices using Azure AppFabric Service Bus

Filed under: Azure, SharePoint — Harin @ 8:30 am

Although the WP7 for Azure toolkit has been released, I recently ran into an issue exposing SharePoint data to a WP7 app that I thought was worth sharing.  In this case, we have a SharePoint 2010 farm inside a corporate firewall, needing to expose data from the SharePoint User Profile Service to an application written on a WP7 mobile device.  To implement this, we created a Windows Service that hosts a webHttpRelayBinding endpoint so the WP7 application can make simple REST calls to get at the data.  By using a REST based endpoint, that also allowed us to write equivalent client applications for Android and iOS.  Going forward, the windows service can be brought in and managed in SharePoint using the Service Application model, but for a demo, simply installing the windows service on the SharePoint box was sufficient. 

High level architecture:

 

 

WCF Service Contract:

 For our service we have a service contract defined to as:

    [ServiceContract]
    public interface IProfileService
    {
        [OperationContract,
        WebGet(
            BodyStyle = WebMessageBodyStyle.Bare,
            ResponseFormat = WebMessageFormat.Xml,
            UriTemplate = "/FarmInfo")
        ]
        FarmInfo GetFarmInfo();

        [OperationContract, 
        WebGet(
            BodyStyle = WebMessageBodyStyle.Bare,
            ResponseFormat = WebMessageFormat.Xml,
            UriTemplate = "/Users?q={searchTerms}")
        ]
        UserList GetUsers(string searchTerms);

        [OperationContract, 
        WebGet(
            BodyStyle = WebMessageBodyStyle.Bare,
            ResponseFormat = WebMessageFormat.Xml,
            UriTemplate = "/Users/{userId}")
        ]
        Profile GetProfile(string userId);
    }
 

WCF Service Implementation:

I created a class called SPProfileReader which encapsulates the call to the SharePont UserProfileManager.  The service implementation looks something like this:

    public class ProfileService : IProfileService
    {
        private static object _synch = new object();
        private static SPProfileReader reader = null;

        private SPProfileReader Instance
        {
            get
            {
                return new SPProfileReader(System.Configuration.ConfigurationManager.AppSettings["SiteUrl"]);
            }
        }

        public FarmInfo GetFarmInfo()
        {
            FarmInfo retVal = null;
            try
            {
                retVal = new FarmInfo()
                {
                    id = "AIS Demos Farm",
                    url = "http://<demo farm url>",
                    centraladminurl = "<central admin url>",
                    description = "This farm is a demo environment meant to showcase Azure and SharePoint integration scenarios."
                };
            }
            catch (Exception e)
            {
                Trace.WriteLine(e.ToString());
            }
            return retVal;
        }

        public UserList GetUsers(string searchTerms)
        {
            UserList retVal = null;
            try
            {
                using (var rdr = Instance)
                {
                    retVal = rdr.GetUsers(searchTerms);
                }
            }
            catch (Exception e)
            {
                Trace.WriteLine(e.ToString());
            }
            return retVal;
        }

        public Profile GetProfile(string userId)
        {
            Profile retVal = null;
            try
            {
                using (var rdr = Instance)
                {
                    retVal = rdr.GetUserProfile(userId);
                }
            }
            catch (Exception e)
            {
                Trace.WriteLine(e.ToString());
            }
            return retVal;
        }
    }
 

Hosting the WCF Service:

Hosting the WCF Service is pretty straight forward and for completeness looks like this:

        public void StartHost()
        {
            try
            {
                //// Build the endpoint URI
                string serviceNamespace = System.Configuration.ConfigurationManager.AppSettings["SBNamespace"];
                string endPoint = System.Configuration.ConfigurationManager.AppSettings["SBEndpoint"];
                Uri address = ServiceBusEnvironment.CreateServiceUri("https", serviceNamespace, endPoint);

                //// Build the service bus credential
                string serviceUser = System.Configuration.ConfigurationManager.AppSettings["SBUser"];
                string serviceKey = System.Configuration.ConfigurationManager.AppSettings["SBKey"];
                TransportClientEndpointBehavior sharedSecretServiceBusCredential = new TransportClientEndpointBehavior();
                sharedSecretServiceBusCredential.CredentialType = TransportClientCredentialType.SharedSecret;
                sharedSecretServiceBusCredential.Credentials.SharedSecret.IssuerName = serviceUser;
                sharedSecretServiceBusCredential.Credentials.SharedSecret.IssuerSecret = serviceKey;

                Trace.WriteLine("Opening service bus connection: " + address.ToString());

                //// Create the wcf service host
                host = new WebServiceHost(typeof(ProfileService), address);

                //// Set the binding to WebHttpRelayBinding
                Microsoft.ServiceBus.WebHttpRelayBinding binding = new WebHttpRelayBinding();
                binding.Security.RelayClientAuthenticationType = RelayClientAuthenticationType.None;

                //// Create the endpoint
                ServiceEndpoint se = host.AddServiceEndpoint(typeof(IProfileService), binding, address);
                se.Behaviors.Add(sharedSecretServiceBusCredential);
                ServiceDebugBehavior sdb = host.Description.Behaviors.Find<ServiceDebugBehavior>();
                sdb.HttpHelpPageEnabled = false;

                //// Open host
                host.Open();
                Trace.WriteLine("Host open.");
            }
            catch(Exception e)
            {
                Trace.WriteLine("Exception occured while opening host:");
                Trace.WriteLine(e.ToString());
            }
        }

 

Sample WP7 Client Code:

Since the relay service is exposing the endpoint as via REST, integrating with mobile devices becomes trivial.  Note, if this is sensitive / production data, I would recommend utilizing the ACS to secure access to the endpoint (examples of how to do that can be seen in the Azure WP7 SDK).  Here is a bit of code showing how to access some data from the REST endpoint.

    public class ServiceProxy
    {
        private const string SBUri = "https://<myaccount>.servicebus.windows.net/SPProfileViewer/";

        public static void GetAllUsers(DownloadStringCompletedEventHandler callback)
        {
            WebClient client = new WebClient();
            string uri = string.Format("{0}Users",SBUri);
            client.DownloadStringCompleted += callback;
            client.DownloadStringAsync(new Uri(uri));
        }

        public static void SearchForUsers(string queryTerm, DownloadStringCompletedEventHandler callback)
        {
            WebClient client = new WebClient();
            string uri = string.Format("{0}Users?q={1}", SBUri, queryTerm);
            client.DownloadStringCompleted += callback;
            client.DownloadStringAsync(new Uri(uri));
        }

        public static void GetUserProfile(string userId, DownloadStringCompletedEventHandler callback)
        {
            WebClient client = new WebClient();
            string uri = string.Format("{0}Users/{1}", SBUri,userId);
            client.DownloadStringCompleted += callback;
            client.DownloadStringAsync(new Uri(uri), userId);
        }

        public static void GetFarmInfo(DownloadStringCompletedEventHandler callback)
        {
            WebClient client = new WebClient();
            string uri = string.Format("{0}FarmInfo", SBUri);
            client.DownloadStringCompleted += callback;
            client.DownloadStringAsync(new Uri(uri));
        }
    }
 

An Important ‘gotcha’ when working with SharePoint and the AppFabric Service Bus:

When hosting our relay service, we noticed that after a while the service host lost the connection to the Service Bus and our service was no longer accessible from the clients.  Working off and on with Microsoft Support for a few weeks, we finally tracked down what the issue was.  When the service creates a connection to the AppFabric Service Bus, it requests a security token.  Every ten minutes or so, the host attempts to renew that token.  The trouble comes in when you try and integrate in with SharePoint.  When you access the SPServiceContext and UserProfileManager and probably a bunch of other SharePoint API calls (as we did in this case), the ServicePointManager.ServerCertificateValidationCallback delegate is changed so that it only trusts the certificates that SharePoint knows about.  Unfortunately, the GTE CyberTrust Root certificate that ultimately signed our Service Bus certificate is not trusted by SharePoint.  If you are experiencing this problem, you may notice errors in your WCF / System.NET trace log such as:

System.Net Error: 0 : [6256] Exception in the HttpWebRequest#3038911:: – The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.

System.Net Error: 0 : [6256] Exception in the HttpWebRequest#3038911::EndGetRequestStream – The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.

 

The work around is as follows (these are from Mog Liang from Microsoft’s support team):

  1. Download the ACS server certificate’s root CA ‘GTE CyberTrust Global Root’ by accessing ACS service ‘https://<myaccount&gt;.accesscontrol.windows.net/WRAPv0.9/
  2. Open ‘SharePoint 2010 Central Administration’ tool
  3. Open ‘Security -> Manage Trust’ page  
  4. Click ‘new’ button, in popup page, input the ‘GTE CyberTrust Global Root’ certificate, give a name, and click OK

 

May 26, 2010

SharePoint 2010 Pluggable Workflow Services with Correlation and External Callbacks

Filed under: SharePoint, Workflow — Harin @ 5:35 pm

Paul Andrew recently wrote an article on MSDN outlining Pluggable Workflow Services. This blog posting extends that example to explain how to include correlation tokens as well as an external callback mechanism. The point of including correlation tokens is if you want to let multiple events of the same type wait for an external event. The point of adding a custom callback mechanism would be if you wanted to have a web part, service application, or custom web service that allows users to progress workflows directly instead of modifying a task list item. For example, if you wanted to provide users with the ability to progress the workflow just by sending an email, you could use this mechanism. Wouter van Vugt has a good Channel 9 video explaining most of these concepts which I’d recommend watching as well.

Correlation Tokens

If you’ve ever created a workflow in SharePoint, then you’ve used correlation tokens with almost every SharePoint related activity, such as CreateTask. So why is correlation needed in your custom local communication service definition? Basically it is a mechanism that allows the workflow runtime to route inbound messages to the correct instance of the persisted activities, if you have more than one activity that is waiting for external communication events. If you are writing a pluggable workflow service and have multiple HandleExternalEvent activities of the same interface type and operation, then you need to provide correlation token capabilities. If you don’t have more than one activity instance in the same workflow persisted and waiting on a particular event, then you don’t have to worry about correlation tokens. Here’s how to add them.

Add a parameter to the ExternalDataEventArgs

This parameter is used to pass the correlation token id between the host and the client.  

   1: [Serializable]

   2: public class PrimeCalculatorEventArgs : ExternalDataEventArgs

   3: {

   4:     public PrimeCalculatorEventArgs(Guid id, string tokenId) : base(id)

   5:     {

   6:         this.TokenID = tokenId;

   7:     }

   8:

   9:     public string Answer;

  10:     public string TokenID;

  11: }

Modify the ExternalDataExchange interface definition

There are a number of things you’ll need to do here.

  1. Annotate the interface with a CorrelationParameterAttribute. In this you specify the token parameter name as defined in the interface method signature.
  2. Mark each outbound method that is in the CallExternalMethod and HandleExternalEvent pair with a CorrelationInitializerAttribute. The parameters of the outbound methods need to have the token parameter name specified in Step 1.
  3. Once that’s done, you need to mark the inbound method with CorrelationAliasAttribute. The first parameter in the CorrelationAliasAttribute attribute needs to match the token parameter name in Step 1. The second parameter in the CorrelationAliasAttribute needs to match the public property specified in the ExternalDataEventArgs (see above section). Note, with the second parameter the first part (the “e”) doesn’t really matter AFAIK, it’s really the second part of the parameter (the TokenID) that needs to match.
   1: [ExternalDataExchange]

   2: [CorrelationParameter("tokenId")]  // Step 1

   3: public interface IPrimeCalculatorService

   4: {

   5:     [CorrelationInitializer()]  // Step 2

   6:     void CalculatePrimes(string tokenId, int topNumberExclusive);

   7:

   8:     [CorrelationInitializer()]  // Step 2

   9:     void RequestValidation(string tokenId);

  10:

  11:

  12:     [CorrelationAlias("tokenId","e.TokenID")]  // Step 3

  13:     event EventHandler<PrimeCalculatorEventArgs> GetAnswer;

  14:

  15:     [CorrelationAlias("tokenId", "e.TokenID")]  // Step 3

  16:     event EventHandler<PrimeCalculatorEventArgs> ValidateAnswer;

  17: }

Implement your SPWorkflowExternalDataExchangeService

The main differences related to correlation here are that you need to pass along the token id. In CallEventHandler, when you invoke your event handler, the token id is passed along with the ExternalDataEventArgs. This is used internally by the workflow runtime to route the call to the correct activity instance in your workflow.

 

   1: /// <summary>

   2: /// Implementation of ExternalDataExchangeService / Local Communication Service

   3: /// </summary>

   4: class PrimeCalculatorService: SPWorkflowExternalDataExchangeService, IPrimeCalculatorService

   5: {

   6:

   7:     public const string SubscriptionsListName = "SubscriptionsList";

   8:

   9:     #region IPrimeCalculatorService Members

  10:

  11:     public void CalculatePrimes(string tokenId, int topNumberExclusive)

  12:     {

  13:         ThreadPool.QueueUserWorkItem(

  14:             state =>

  15:             {

  16:                 FactoringState factoringState = state as FactoringState;

  17:                 PrimeCalculatorWS.Calculate ws = new PrimeCalculatorWS.Calculate();

  18:                 string answer = ws.CalculatePrimes(factoringState.topNumberExclusive);

  19:

  20:                 SPWorkflowExternalDataExchangeService.RaiseEvent(

  21:                     factoringState.web,

  22:                     factoringState.instanceId,

  23:                     typeof(IPrimeCalculatorService),

  24:                     "GetAnswer",

  25:                     new object[] { tokenId, answer }  // NOTE:  tokenId is passed along as a parameter for the callback.

  26:                 );

  27:

  28:             },

  29:             new FactoringState(WorkflowEnvironment.WorkflowInstanceId, this.CurrentWorkflow.ParentWeb, topNumberExclusive)

  30:         );

  31:     }

  32:

  33:     public void RequestValidation(string tokenId)

  34:     {

  35:     }

  36:

  37:     public event EventHandler<PrimeCalculatorEventArgs> GetAnswer;

  38:     public event EventHandler<PrimeCalculatorEventArgs> ValidateAnswer;

  39:

  40:     #endregion

  41:

  42:     #region SPWorkflowExternalDataExchangeService Overrides

  43:

  44:     public override void CallEventHandler(Type eventType, string eventName, object[] eventData, SPWorkflow workflow, string identity, IPendingWork workHandler, object workItem)

  45:     {

  46:         string tokenId = (string)eventData[0];  // NOTE:  tokenId is extracted from eventData and passed along with the ExternalDataEventArgs

  47:         var msg = new PrimeCalculatorEventArgs(workflow.InstanceId, tokenId);

  48:         msg.Answer = eventData[1].ToString();

  49:         msg.WorkHandler = workHandler;

  50:         msg.WorkItem = workItem;

  51:         msg.Identity = identity;

  52:

  53:         if (eventName == "GetAnswer" && GetAnswer != null)

  54:         {

  55:             this.GetAnswer(null, msg);

  56:         }

  57:         else if (eventName == "ValidateAnswer" && ValidateAnswer != null)

  58:         {

  59:             this.ValidateAnswer(null, msg);

  60:         }

  61:     }

  62:

  63:

  64:     #endregion

  65: }

  66:

  67: /// <summary>

  68: /// This is a utility class to store information needed by the queued thread work item.  

  69: /// This isn't relevant to correlation directly, just used by the example and included for completeness.

  70: /// </summary>

  71: class FactoringState

  72: {

  73:     public Guid instanceId;

  74:     public SPWeb web;

  75:     public int topNumberExclusive;

  76:

  77:     public FactoringState(Guid instanceId, SPWeb web, int topNumberExclusive)

  78:     {

  79:         this.instanceId = instanceId;

  80:         this.web = web;

  81:         this.topNumberExclusive = topNumberExclusive;

  82:     }

  83: }

Generate strongly typed CallExternalMethod and HandleExternalEvent activities using WCA.exe

EDIT:  I neglected to mention that the tool was called wca.exe.  It’s used to generate the CallExternalMethod and HandleExternalEvent activities that are specific to your interface.  Here is an msdn page describing the tool.

This step is optional, but generally a good idea. The tool creates activities for that are strongly typed to your interface definition.

It is located at: C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin

Add your Workflow Service / Local Communication Service to the web.config

Here is a sample snippet from Paul Andrew’s msdn article mentioned above:

<WorkflowServices><WorkflowService Assembly=”WorkflowProject1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=YOURPUBLICKEY”>
</WorkflowService></WorkflowServices>

Create a workflow and start using your correlated activites!

External Callback Mechanism

This section tries to explain how you can create an external callback mechanism that doesn’t live directly within the workflow. Examples would be a web part, custom field type, custom service application, SharePoint hosted WCF service, etc, etc. To accomplish this, you first need to make a few small modifications to your SPWorkflowExternalDataExchange service. The next step is that you need write some code to call your call backs, in this example I’ll use a web part, but it can be anything that is executed within the context of a SharePoint process. The italicized point is key. AFAIK, you can’t create a standalone application to progress the workflows since the SharePoint workflow runtime isn’t loaded in those processes. Ok, so let’s get started. In the example above there was a RequestValidation and ValidateAnswer event handler I defined in my service contract. This section uses them as defined above.

SPWorkflowExternalDataExchange modifications

SPWorkflowExternalDataExchange defines two methods called CreateSubscription and DeleteSubscription. These methods are called by the workflow runtime when you use CallExternalMethod and HandleExternalEvent activities. They allow us to write information about our workflow, the Subscription ID, Workflow ID, and any correlation parameters, to some sort of persistence store so we can access it externally when we want to progress our workflows. Note: the example here was taken from Wouter van Vugt’s Channel 9 video. In this example, I’m just writing the information to a SharePoint list, but you can write it to a database / any other persistance store you like. Just keep in mind that there are fidelity concerns that come in to play here, so a SharePoint list may not be the best place if you need high performance / have lots of workflows.

   1: /// <summary>

   2: /// This method's goal is to just persist the subscription.CorrelationProperties to a list.

   3: /// NOTE:  This method uses linq to sharepoint where I to defined my own DataContext using sqlmetal (not shown here).

   4: /// This is called automatically by the workflow runtime.

   5: /// </summary>

   6: /// <param name="subscription"></param>

   7: public override void CreateSubscription(MessageEventSubscription subscription)

   8: {

   9:     EnsureSubscriptionsList(base.CurrentWorkflow.ParentWeb);

  10:

  11:     CorrelationProperty correlationProperty = subscription.CorrelationProperties.Where(p => p.Name != string.Empty).First();

  12:     using (DataContext context = new DataContext(base.CurrentWorkflow.ParentWeb.Url))

  13:     {

  14:         EntityList<SubscriptionsListItem> subscriptions = context.GetList<SubscriptionsListItem>(SubscriptionsListName);

  15:         SubscriptionsListItem entity = new SubscriptionsListItem()

  16:         {

  17:             SubscriptionId = subscription.SubscriptionId.ToString(),

  18:             WorkflowId = subscription.WorkflowInstanceId.ToString(),

  19:             CustomId = correlationProperty.Value !=null ? correlationProperty.Value.ToString() : string.Empty

  20:         };

  21:         subscriptions.InsertOnSubmit(entity);

  22:         context.SubmitChanges();

  23:     }

  24: }

  25:

  26: /// <summary>

  27: /// This method's goal is to delete the SharePoint list item when we’re done with it.  

  28: /// NOTE:  This method uses linq to sharepoint where I to defined my own DataContext using sqlmetal (not shown here).

  29: /// This is called automatically by the workflow runtime.

  30: /// </summary>

  31: /// <param name="subscriptionId"></param>

  32: public override void DeleteSubscription(Guid subscriptionId)

  33: {

  34:

  35:     using (DataContext context = new DataContext(base.CurrentWorkflow.ParentWeb.Url))

  36:     {

  37:         EntityList<SubscriptionsListItem> subscriptions = context.GetList<SubscriptionsListItem>(SubscriptionsListName);

  38:         SubscriptionsListItem entity = subscriptions

  39:             .Where(s => s.SubscriptionId == subscriptionId.ToString())

  40:             .FirstOrDefault();

  41:         if (entity != null)

  42:         {

  43:             subscriptions.DeleteOnSubmit(entity);

  44:             context.SubmitChanges();

  45:         }

  46:     }

  47: }

  48:

  49: /// Helper method to create the SharePoint list.

  50: private void EnsureSubscriptionsList(SPWeb web)

  51: {

  52:     SPList list = web.Lists.TryGetList(SubscriptionsListName);

  53:     if (list == null)

  54:     {

  55:         Guid listId = web.Lists.Add(SubscriptionsListName, "", SPListTemplateType.GenericList);

  56:         list = web.Lists.GetList(listId, true);

  57:         string subscriptionIDFieldName = list.Fields.Add("SubscriptionId", SPFieldType.Text, true);

  58:         string workflowIDFieldName = list.Fields.Add("WorkflowId", SPFieldType.Text, true);

  59:         string customIDFieldName = list.Fields.Add("CustomId", SPFieldType.Text, true);

  60:         list.Update();

  61:

  62:         SPView view = list.DefaultView;

  63:         view.ViewFields.Add(subscriptionIDFieldName);

  64:         view.ViewFields.Add(workflowIDFieldName);

  65:         view.ViewFields.Add(customIDFieldName);

  66:         view.Update();

  67:     }

  68:

  69: }

Implement your web part / custom code to progress the workflow

The key method here is SPWorkflowExternalDataExchangeService.RaiseEvent. This calls in to the workflow as we did above, passing in the workflow instance ID and the correlation token id in the ExternalDataEventArgs if you are using correlation. These parameters can be found wherever we persisted them in the CreateSubscription method defined above. In this example, that information was persisted to a list. A more robust example could look up the correct parameter ids, but this one requires users to enter them manually.

   1: [ToolboxItemAttribute(false)]

   2: public class WebPart1 : WebPart

   3: {

   4:     protected TextBox subId;

   5:     protected TextBox wfInstId;

   6:     protected TextBox corrTokenId;

   7:     protected Button validate;

   8:     protected Button invalidate;

   9:

  10:     protected override void CreateChildControls()

  11:     {

  12:         subId = new TextBox();

  13:         wfInstId = new TextBox();

  14:         corrTokenId = new TextBox();

  15:         validate = new Button()

  16:         {

  17:             Text = "Valid"

  18:         };

  19:

  20:         validate.Click += new EventHandler((s, e) =>

  21:         {

  22:             SPWorkflowExternalDataExchangeService.RaiseEvent(

  23:                                    SPContext.Current.Web,

  24:                                    new Guid(wfInstId.Text),

  25:                                    typeof(IPrimeCalculatorService),

  26:                                    "ValidateAnswer",

  27:                                    new object[] { corrTokenId.Text, "Valid answer." }

  28:                            );

  29:

  30:         });

  31:

  32:

  33:         invalidate = new Button()

  34:         {

  35:             Text = "Invalid"

  36:         };

  37:

  38:         invalidate.Click += new EventHandler((s, e) =>

  39:         {

  40:             SPWorkflowExternalDataExchangeService.RaiseEvent(

  41:                                    SPContext.Current.Web,

  42:                                    new Guid(wfInstId.Text),

  43:                                    typeof(IPrimeCalculatorService),

  44:                                    "ValidateAnswer",

  45:                                    new object[] { corrTokenId.Text, "In valid answer." }

  46:                            );

  47:

  48:         });

  49:

  50:         Controls.Add(subId);

  51:         Controls.Add(wfInstId);

  52:         Controls.Add(corrTokenId);

  53:         Controls.Add(validate);

  54:         Controls.Add(invalidate);

  55:

  56:

  57:     }

  58:

  59: }

Deploy, create your workflow, and use your web part to progress your workflows
That’s all there is to it! Hope this helps!

November 19, 2009

Using the Service Management APIs to Automate Azure Deployments

Filed under: Azure — Harin @ 11:27 am

With the latest CTP, Microsoft announced the a ServiceManagement API that can be used to automate builds (at least to your staging environment).  Note:  Screenshots are coming soon.

Here’s a simple walkthrough on how to create a certificate for use with the Azure Service Management APIs.

Create a self signed certificate

  1. Open IIS 7
  2. Features -> Server Certificates
  3. Create Self-Signed Certificate
  4. Enter a friendly name and click ok.
  5. Export the certificate
    1. Start Certificate Manager by clicking on Start -> Run, and entering certmgr.msc
    2. Find your certificate (Mine was located in ‘Trusted Root Certification Authorities’
    3. Export the certificate

i.      Right click on -> All Tasks -> Export, click next after the welcome screen.

ii.      Do not export the private key, and click next.

iii.      Select DER encoded binary X.509 (.CER)

iv.      Select a file location

v.      Click on Finish

Associate the certificate with Azure

  1. Go to the https://windows.azure.com portal and sign in with your account credentials
  2. Navigate to the Account Tab a nd click on ‘Manage My API Certificates’
  3. Click on Browse, select the certificate that was exported previously, and then click upload.
  4. The certificate should appear in the Installed Certificates list
  5. Start using the Management Service API.
    1. Here is sample code from MSDN
HttpWebRequest CreateHttpRequest(Uri uri, string httpMethod, X509Certificate2 accessCertificate, TimeSpan timeout)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
request.Timeout = (int)timeout.TotalMilliseconds;
request.ReadWriteTimeout = (int)timeout.TotalMilliseconds * 100;
request.Method = httpMethod;
request.ClientCertificates.Add(accessCertificate);
request.Headers.Add(Constants.VersionHeader, Constants.VersionTarget);
request.ContentType = Constants.ContentTypeXml;
return request;
}

Note:  If you are calling this code from a machine where the certificate was not originally generated, then you may need to export the certificate with the private key and install it on that server.  For example, if you are automating builds using TFS, then export the certificate with the private key, and import it on the build server.

To automate deployments with TFS the general steps are:

  1. Create and associate a management certificate as outlined above.
  2. Make sure the build server has the certificate installed with the private key.
  3. Create a build for your Azure solution in TFS.
  4. Run cspack.exe to package your deployment package on the build output to package the deployment.
  5. Use the BlobStorage REST APIs / provided StorageClient to upload the package into blob storage.
private static string SendFileToContainer(BlobContainer deploymentContainer, string filePath, string versionName)
{
string retVal = string.Empty;
FileInfo info = new FileInfo(filePath);
string newFileName = DateTime.Now.ToString("yyyyMMddHHmmss") + "_" + versionName + "_" + info.Name;
BlobProperties blobProp = new BlobProperties(newFileName);
blobProp.Metadata = new System.Collections.Specialized.NameValueCollection();
blobProp.Metadata.Add("version", versionName);
if (deploymentContainer.CreateBlob(blobProp, new BlobContents(File.ReadAllBytes(filePath)), true))
{
retVal = deploymentContainer.ContainerUri.AbsoluteUri + "/" + newFileName;
}
return retVal;
}

Finally, use the ServiceManagement REST APIs to perform the deployment.  Here’s some more code that may help.

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
//using AIS.Azure.ServiceManagementWrapper.Constants;
//using AIS.Azure.ServiceManagementWrapper.HelperObjects;
using System.Security.Cryptography.X509Certificates;
namespace AIS.Azure.ServiceManagementWrapper
{
namespace HelperObjects
{
public class HostedService
{
public class HostedServiceProperties
{
public HostedServiceProperties() { }
public string Description { get; set; }
public string AffinityGroup { get; set; }
public string Location { get; set; }
public string Label { get; set; }
}

public class HostedServiceDeploymentRoleInstance
{
public string RoleName { get; set; }
public string InstanceName { get; set; }
public string InstanceStatus { get; set; }
}

public class HostedServiceDeployment
{
public string Name { get; set; }
public string DeploymentSlot { get; set; }
public string PrivateID { get; set; }
public string Status { get; set; }
public string Label { get; set; }
public string Url { get; set; }
public string Configuration { get; set; }
public IEnumerable<HostedServiceDeploymentRoleInstance> RoleInstances { get; set; }
public string UpgradeDomainCount { get; set; }
}
public HostedService() { }

public string Url { get; set; }
public string ServiceName { get; set; }
public HostedServiceProperties Properties { get; set; }
public IEnumerable<HostedServiceDeployment> Deployments { get; set; }

public override string ToString()
{
StringBuilder blder = new StringBuilder();
blder.AppendLine("ServiceName: " + ServiceName);
blder.AppendLine("Url: " + Url);
blder.AppendLine("Description: " + Properties.Description);
blder.AppendLine("AffinityGroup: " + Properties.AffinityGroup);
blder.AppendLine("Location: " + Properties.Location);
blder.AppendLine("Label: " + Properties.Label);
blder.AppendLine("Deployments:");
foreach (var deployment in Deployments)
{
blder.AppendLine("Deployment:");
blder.AppendLine("Name:\t\t" + deployment.Name);
blder.AppendLine("DeploymentSlot:\t" + deployment.DeploymentSlot);
blder.AppendLine("PrivateID:\t" + deployment.PrivateID);
blder.AppendLine("Status:\t\t" + deployment.Status);
blder.AppendLine("Label:\t\t" + deployment.Label);
blder.AppendLine("\t\tUrl:\t\t" + deployment.Url);
blder.AppendLine("Configuration:\t" + deployment.Configuration);
blder.AppendLine("UpgradeDomainCount:\t" + deployment.UpgradeDomainCount);
foreach (var roleinstance in deployment.RoleInstances)
{
blder.AppendLine("RoleInstance:");
blder.AppendLine("RoleName:\t" + roleinstance.RoleName);
blder.AppendLine("InstanceName:\t" + roleinstance.InstanceName);
blder.AppendLine("InstanceStatus:\t" + roleinstance.InstanceStatus);
}
}
return blder.ToString();
}

}

namespace Constants
{

class RequestTypes
{
public const string GET = "GET";
public const string POST = "POST";
public const string DELETE = "DELETE";
public const string LIST = "LIST";
}

class Headers
{
public const string x_ms_version_header = "x-ms-version";
public const string x_ms_version_value = "2009-10-01";
public const string x_ms_request_id_header = "x-ms-request-id";
}

class ContentTypes
{
public const string XML = "xml";
}

class HostedServices
{
public const string Namespace = "http://schemas.microsoft.com/windowsazure";
public const string ListHostedServices = "https://management.core.windows.net/{0}/services/hostedservices";
public const string GetHostedServiceProperties = "https://management.core.windows.net/{0}/services/hostedservices/{1}?embed-detail={2}";
public const string UpgradeDeploymentByDeploymentSlot = "https://management.core.windows.net/{0}/services/hostedservices/{1}/deploymentslots/{2}/?comp=upgrade";
public const string UpgradeDeploymentByDeploymentName = "https://management.core.windows.net/{0}/services/hostedservices/{1}/deployments/{2}/?comp=upgrade";
}
}

class ServiceManager
{
public enum DeploymentSlots { Staging, Production};
public enum Mode {Auto, Manual};
private static XNamespace ns = "http://schemas.microsoft.com/windowsazure";

private ServiceManager()
{
}

private static HttpWebRequest CreateHttpRequest(string requestUri, string httpMethod, X509Certificate accessCertificate, TimeSpan timeout)
{
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(requestUri);
request.Timeout = (int)timeout.TotalMilliseconds;
request.ReadWriteTimeout = (int)timeout.TotalMilliseconds * 100;
request.Method = httpMethod;
request.ClientCertificates.Add(accessCertificate);
request.Headers.Add(Constants.Headers.x_ms_version_header,
Constants.Headers.x_ms_version_value);
request.ContentType = Constants.ContentTypes.XML;
return request;
}

private static string ValueOrEmpty(XElement element)
{
return (element != null) ? element.Value : string.Empty;
}

private static XElement ProcessResponse(HttpWebRequest request)
{
XElement retVal = null;
using (WebResponse resp = request.GetResponse())
{
using (StreamReader respStream = new StreamReader(resp.GetResponseStream()))
{
string responseText = respStream.ReadToEnd();
retVal = XDocument.Parse(responseText).Root;
}
}
return retVal;
}

/// <summary>
///
/// </summary>
/// <param name="subscriptionId"></param>
/// <param name="certificatePath"></param>
/// <param name="msTimeout"></param>
/// <returns></returns>
public static IEnumerable<HostedService> ListHostedServices(string subscriptionId, string certificatePath, double msTimeout)
{
string strUrl = string.Format(Constants.HostedServices.ListHostedServices, subscriptionId);

X509Certificate2 cert = new X509Certificate2(certificatePath);

HttpWebRequest req = CreateHttpRequest(strUrl, RequestTypes.GET, cert, TimeSpan.FromMilliseconds(msTimeout));

var response = ProcessResponse(req);

var serviceList = from svc in response.Descendants(ns + "HostedService")
select new { Url = svc.Element(ns + "Url").Value, ServiceName = svc.Element(ns + "ServiceName").Value };

IEnumerable<HostedService> retVal = new List<HostedService>();
// Populate the service by just getting the values
foreach (var service in serviceList)
{
retVal = retVal.Concat<HostedService>(GetHostedServiceProperties(subscriptionId, service.ServiceName, true, certificatePath, msTimeout));
}
return retVal;
}
/// <summary>
///
/// </summary>
/// <param name="subscriptionId"></param>
/// <param name="serviceName"></param>
/// <param name="embedDetail"></param>
/// <param name="certificatePath"></param>
/// <param name="msTimeout"></param>
/// <returns></returns>
public static IEnumerable<HostedService> GetHostedServiceProperties(string subscriptionId, string serviceName, bool embedDetail, string certificatePath, double msTimeout)
{
string strUrl = string.Format(Constants.HostedServices.GetHostedServiceProperties, subscriptionId, serviceName, embedDetail.ToString().ToLower());

X509Certificate2 cert = new X509Certificate2(certificatePath);

HttpWebRequest req = CreateHttpRequest(strUrl, RequestTypes.GET, cert, TimeSpan.FromMilliseconds(msTimeout));

XElement response = ProcessResponse(req);

var serviceList = from svc in response.DescendantsAndSelf(ns + "HostedService")
select new HostedService()
{
Url = svc.Element(ns + "Url").Value,
ServiceName = svc.Element(ns + "ServiceName").Value,
Properties = new HostedService.HostedServiceProperties()
{
Description = ValueOrEmpty(svc.Element(ns + "HostedServiceProperties").Element(ns + "Description")),
AffinityGroup = ValueOrEmpty(svc.Element(ns + "HostedServiceProperties").Element(ns + "AffinityGroup")),
Location = ValueOrEmpty(svc.Element(ns + "HostedServiceProperties").Element(ns + "Location")),
Label = ValueOrEmpty(svc.Element(ns + "HostedServiceProperties").Element(ns + "Label"))
},
Deployments = from deployment in svc.Element(ns + "Deployments").Elements(ns + "Deployment")
select new HelperObjects.HostedService.HostedServiceDeployment()
{
Name = ValueOrEmpty(deployment.Element(ns + "Name")),
DeploymentSlot = ValueOrEmpty(deployment.Element(ns + "DeploymentSlot")),
PrivateID = ValueOrEmpty(deployment.Element(ns + "PrivateID")),
Status = ValueOrEmpty(deployment.Element(ns + "Status")),
Label = ValueOrEmpty(deployment.Element(ns + "Label")),
Url = ValueOrEmpty(deployment.Element(ns + "Url")),
Configuration = ValueOrEmpty(deployment.Element(ns + "Configuration")),
RoleInstances = from roleinstance in deployment.Element(ns + "RoleInstanceList").Elements(ns + "RoleInstance")
select new HelperObjects.HostedService.HostedServiceDeploymentRoleInstance()
{
RoleName = ValueOrEmpty(roleinstance.Element(ns + "RoleName")),
InstanceName = ValueOrEmpty(roleinstance.Element(ns + "InstanceName")),
InstanceStatus = ValueOrEmpty(roleinstance.Element(ns + "InstanceStatus"))
},
UpgradeDomainCount = ValueOrEmpty(deployment.Element(ns + "UpgradeDomainCount"))
}
};
return serviceList;
}
/// <summary>
///
/// </summary>
/// <param name="subscriptionId"></param>
/// <param name="serviceName"></param>
/// <param name="deploymentSlot"></param>
/// <param name="mode"></param>
/// <param name="packageUrl"></param>
/// <param name="configuration"></param>
/// <param name="label"></param>
/// <param name="roleNameToUpgrade">Optional.  Pass null or String.Empty if none.</param>
/// <param name="certificatePath"></param>
/// <param name="msTimeout"></param>
/// <returns></returns>
public static string UpgradeDeploymentByDeploymentSlot(string subscriptionId,
string serviceName,
DeploymentSlots deploymentSlot,
Mode mode,
string packageUrl,
string configuration,
string label,
string roleNameToUpgrade,
string certificatePath,
double msTimeout)
{

string strUrl = string.Format(Constants.HostedServices.UpgradeDeploymentByDeploymentSlot, subscriptionId, serviceName, Enum.GetName(typeof(DeploymentSlots),deploymentSlot).ToLower());

X509Certificate2 cert = new X509Certificate2(certificatePath);

HttpWebRequest req = CreateHttpRequest(strUrl, RequestTypes.POST, cert, TimeSpan.FromMilliseconds(msTimeout));
using (XmlTextWriter requestBodyStream = new XmlTextWriter(req.GetRequestStream(), Encoding.UTF8))
{
//requestBodyStream.Write();
XNamespace azureNS = (string)Constants.HostedServices.Namespace.Clone();
XDocument doc = new XDocument(
new XDeclaration("1.0","utf-8", "yes"),
new XElement( azureNS + "UpgradeDeployment",
new XElement(azureNS + "Mode", Enum.GetName(typeof(Mode), mode).ToLower()),
new XElement(azureNS + "PackageUrl", packageUrl),
new XElement(azureNS + "Configuration", configuration),
new XElement(azureNS + "Label", Convert.ToBase64String( Encoding.UTF8.GetBytes(label)))
));

if (!string.IsNullOrEmpty(roleNameToUpgrade))
{
doc.Root.Element(azureNS + "Label").AddAfterSelf(new XElement(azureNS + "RoleToUpgrade", roleNameToUpgrade));
}

doc.Save(requestBodyStream);
}
req.ContentType = "application/xml";

string retVal = string.Empty;
using (WebResponse response = req.GetResponse())
{
try
{
retVal = response.Headers[Constants.Headers.x_ms_request_id_header];
}
catch(NullReferenceException ex)
{
// The request header was not found
retVal = string.Empty;
}
}

return retVal;
}

}
}

July 19, 2009

SharePoint Workflow Deadlocks

Filed under: SharePoint — Tags: , , — Harin @ 11:51 pm

I’ve been working on an application built on MOSS that is extremely workflow intensive.  The application has a series of infopath forms that progress through a custom approval process workflow we developed using Visual Studio 2005.  After a couple weeks, when we were getting about 1000 forms going through this workflow a day, we noticed that we were getting about 10 or so that would just fail inexplicably.  When we terminated and restarted the workflows, they went through the approval process with no issues.

We then started to really dig into our diagnostics.  We checked our custom logging, the SharePoint logs, Event Logs, the workflow trace logs, SQL Server logs, and all the performance counters we could think of.  We noticed that under load we were seeing a number of lock escalations and periodic deadlock errors on our SQL profiler trace and sure enough, if the WF runtime process hosted inside MOSS was the one terminated by SQL we would see a corresponding entry in our workflow runtime trace file. [1

We also saw quite a few different processes involved in our deadlock graphs, but they all could be traced back to the proc_UpdateListItemWorkflowInstanceData stored procedure in the content db, which is called when the workflows start and at each persist point.

So, we did the next logical thing and tried to reproduce the error in our testing environment so we could troubleshoot and deploy a fix.  The only thing was, we couldn’t reproduce it.  It turns out, because we were performing functional testing on a virtualized environment there was never enough load put on the SQL box to see the problem.  We had to pave a machine and install a native 64 bit SQL Server 2005 Enterprise Edition to see it.  Our infrastructure was set up in a large farm topology with 2 WFEs, 1 Index, 1 Search, and a 2 Node active-passive clustered SQL Server 2005 instance.  All the MOSS boxes in production were 32 bit and the SQL nodes were 64 bit, and our testing environment was virtualized so all 32 bit.

I’ll spare you the details of the trial and error process we went through to track these down, but to get rid of most of our deadlocks we ultimately had to:

1. Ensure our lists do not contain a large number of items.
2. Set the maximum degree of parallelism on our SQL box to 1 (MAXDOP=1)
3. Periodically run DBCC FREEPROCCACHE, especially after the weekly SharePoint update statistics timer job is run.

[1] Error in the WF Trace File:
System.Workflow.Runtime.Hosting Error: 0 : DefaultWorkflowCommitWorkBatchService caught exception from commitWorkBatchCallback: System.Data.SqlClient.SqlException: Transaction (Process ID 152) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
   at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(DbAsyncResult result, String methodName, Boolean sendToPipe)
   at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   at Microsoft.SharePoint.Utilities.SqlSession.ExecuteNonQuery(SqlCommand command)
   at Microsoft.SharePoint.Workflow.SPWorkflowManager.SaveInstanceData(Guid trackingId, Stream instanceStream, DateTime wakeupTime, Boolean workflowCompleted, Boolean workflowSuspended, Boolean workflowFaulting, Boolean workflowTerminated, Boolean workflowCanceled, Boolean unlockInstance)
   at Microsoft.SharePoint.Workflow.SPWorkflowHostServiceBase.SaveInstanceData(Guid instanceId, Stream instanceStream, DateTime wakeupTime, Boolean bWorkflowCompleted, Boolean bWorkflowSuspended, Boolean bWorkflowFaulting, Boolean bWorkflowTerminated, Boolean bWorkflowCanceled, Boolean unlockInstance)
   at Microsoft.SharePoint.Workflow.SPWinOePersistenceService.Commit(Transaction transaction, ICollection items)
   at System.Workflow.Runtime.WorkBatch.PendingWorkCollection.Commit(Transaction transaction)
   at System.Workflow.Runtime.WorkBatch.Commit(Transaction transaction)
   at System.Workflow.Runtime.VolatileResourceManager.Commit()
   at System.Workflow.Runtime.WorkflowExecutor.DoResourceManagerCommit()
   at System.Workflow.Runtime.Hosting.WorkflowCommitWorkBatchService.CommitWorkBatch(CommitWorkBatchCallback commitWorkBatchCallback)
   at System.Workflow.Runtime.Hosting.DefaultWorkflowCommitWorkBatchService.CommitWorkBatch(CommitWorkBatchCallback commitWorkBatchCallback)

System.Workflow.Runtime Error: 0 : Workflow Runtime: WorkflowExecutor: Persist attempt on instance ‘1d973253-8b24-4270-9065-4f3923e87374’ threw an exception ‘Transaction (Process ID 152) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.’ 
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj)
   at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
   at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
   at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
   at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(DbAsyncResult result, String methodName, Boolean sendToPipe)
   at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
   at Microsoft.SharePoint.Utilities.SqlSession.ExecuteNonQuery(SqlCommand command)
   at Microsoft.SharePoint.Workflow.SPWorkflowManager.SaveInstanceData(Guid trackingId, Stream instanceStream, DateTime wakeupTime, Boolean workflowCompleted, Boolean workflowSuspended, Boolean workflowFaulting, Boolean workflowTerminated, Boolean workflowCanceled, Boolean unlockInstance)
   at Microsoft.SharePoint.Workflow.SPWorkflowHostServiceBase.SaveInstanceData(Guid instanceId, Stream instanceStream, DateTime wakeupTime, Boolean bWorkflowCompleted, Boolean bWorkflowSuspended, Boolean bWorkflowFaulting, Boolean bWorkflowTerminated, Boolean bWorkflowCanceled, Boolean unlockInstance)
   at Microsoft.SharePoint.Workflow.SPWinOePersistenceService.Commit(Transaction transaction, ICollection items)
   at System.Workflow.Runtime.WorkBatch.PendingWorkCollection.Commit(Transaction transaction)
   at System.Workflow.Runtime.WorkBatch.Commit(Transaction transaction)
   at System.Workflow.Runtime.VolatileResourceManager.Commit()
   at System.Workflow.Runtime.WorkflowExecutor.DoResourceManagerCommit()
   at System.Workflow.Runtime.Hosting.WorkflowCommitWorkBatchService.CommitWorkBatch(CommitWorkBatchCallback commitWorkBatchCallback)
   at System.Workflow.Runtime.Hosting.DefaultWorkflowCommitWorkBatchService.CommitWorkBatch(CommitWorkBatchCallback commitWorkBatchCallback)
   at System.Workflow.Runtime.WorkflowExecutor.CommitTransaction(Activity activityContext)
   at System.Workflow.Runtime.WorkflowExecutor.Persist(Activity dynamicActivity, Boolean unlock, Boolean needsCompensation)

Create a free website or blog at WordPress.com.