Welcome to MSDN Blogs Sign in | Join | Help

Microsoft Dynamics CRM UK Blog

CRM news and views from Simon Hutson
UK User Group Meeting - 29th October 2008

Love Rears Its Ugly Head...

I just received an e-mail from Rod Gordon at Gordon Associates letting me know about the next Microsoft Dynamics CRM UK User Group meeting being held on the 29th October at the Microsoft offices in Thames Valley Park, Reading from 1pm to 5pm.

Sessions include:

  • Workflow, reporting & printing in CRM 4.0
  • CTI with CRM 4.0
  • Capturing web-site leads with CRM 4.0
  • Case study of a CRM implementation

The meeting is open to all Microsoft Dynamics CRM customers and partners and is free of charge. To make a booking or for further details contact Rod Gordon; e-mail: rod@gordonassociates.co.uk; phone: 01242 529820.

This posting is provided "AS IS" with no warranties, and confers no rights.

Laughing Boy Chestnuts Pre-School

Meet Abhijit Gore (CRM Program Manager) at Convergence

Superstition...

Abhijit Gore contacted me yesterday to ask for some help arranging some 1:1 customer/partner meetings whilst he is at Convergence EMEA 2008 in Copenhagen later this year (November 19-20 2008). Abhijit is part of the CRM product team, based at the Microsoft India Development Centre (IDC) in Hyderabad, and is responsible for a number of cool new CRM5 features which I can't discuss here (for fear of waking up next to a horse's head).

Now that we are coming to the end of software development milestone 1, Abhijit is looking for feedback on upcoming CRM5 features that are being developed by the IDC team. So if you are interested in meeting up and potentially influencing the direction of the product, then drop me a line via the contact page and I'll put you in touch with Abhijit.

This posting is provided "AS IS" with no warranties, and confers no rights.

Laughing Boy Chestnuts Pre-School

September Catch-Up

Sometimes...

It's been almost 8 weeks since my last post, and a lot has happened since then. Not only did I manage a couple of week holiday with my wife and two boys, but we also filled the job vacancy in our team.

Michael Wignall joins us from Microsoft Gold Certified Partner Tubedale Communications, where he was Chief Technology Officer, responsible for expanding their technical capabilities to cover hosting, infrastructure solutions, telecommunications, CRM and enterprise project management consultancy. Michael started a couple of weeks ago, and already has been heavily involved in a number of public sector CRM opportunities. Since then, I have been able to concentrate on my new sales territory.

Over the last couple of years, Craig Steere and I have been providing CRM pre-sales support across all industry sectors for both Enterprise and Mid-market customer opportunities. However, this FY we decided to focus specifically on Enterprise Financial Service and Public Sector clients, and to that end, we re-organised our CRM pre-sales team as follows:

  • Simon Hutson - enterprise financial services clients.
  • Michael Wignall - enterprise public sector clients.
  • Craig Steere - enterprise retail, manufacturing & professional service clients plus all mid-market clients.

When I agreed to take on the financial services sector (and the associated sales quota) back in July, it looked as though the worst of the "credit crunch" had passed, and for many clients it was business as usual. How wrong was I? At the moment it is too early to know how many of my current CRM financial services opportunities will be reduced in scope, delayed or cancelled altogether, but I think things just got a whole lot harder.

On the plus side, clients will be looking hard at the total cost of ownership of any solution they purchase, and this plays well to the general Microsoft "high-volume, low cost" proposition. We proved this back in June when we sold 8,000 seats of Microsoft Dynamics CRM to a financial services customer who already had a significant investment in Salesforce.com. The real clincher, was that our costs over a three-to-six year period were significantly less than those of our competitor, even when you included infrastructure required for an on-premise deployment.

This posting is provided "AS IS" with no warranties, and confers no rights.

Laughing Boy

We Are Hiring

Microsoft Dynamics UK CRM Pre-Sales

War Inside My Head...

Following the huge growth of CRM here in the UK over the last couple of years, we have restructured our enterprise sales team and now have an opening for a CRM pre-sales consultant. If you have at least a couple of years experience selling CRM solutions into the UK Public Sector (not necessarily Microsoft Dynamics CRM), then drop me a line via my contact form and I will be happy to discuss in more detail.

This posting is provided "AS IS" with no warranties, and confers no rights.

Laughing Boy

CRM & Team Development Projects

Mississippi Moon...

I just got back from Seattle, after spending a full week at TechReady7. TechReady is an internal technical conference, where 5,000+ Microsoft field folk from around the world (pre-sales, consultants, support) get to catch up with product teams during 1,200+ breakout sessions.

Whilst there, I spent quite a bit of time with the CRM product team discussing the next release of CRM (sorry no codename this time), which is currently in the early stages of development. Andrew Bybee (Principal Program Manager, and one of the longest serving members of the CRM leadership team) mentioned that he is coming to the UK for a few days in late September, and asked me if I could arrange for him to meet some customers and partners while he is here.

He is particularly interested in understanding how people are using tools such as Visual Studio Team Foundation Server and other build tools/scripts  on complex projects. So if you are working on a project where you have multiple build environments (dev, unit test, integration, smoke test, pre-production etc), with multiple developers & testers working simultaneously in those environments, then drop me a line via my contact form and I'll schedule you in for a couple of hours with Andrew. Who knows, he might even give you some insight into CRM5.

By way of introduction, Andrew is responsible for all aspects of the CRM platform including:

  • Core runtime
  • Security
  • Event pipeline
  • Async engine
  • Authentication
  • SDK / Web services
  • Workflow
  • Email services
  • Customisation tools
  • Data model
  • Configuration DB

This posting is provided "AS IS" with no warranties, and confers no rights.

Laughing Boy

Mind Your Language

Ramble On...

Now I'm all for a bit of healthy competition in the CRM market, and highlighting your products benefits over the competition is just part of a standard sales process. However, I did have to laugh when I saw an e-mail sent by a sales rep. from another CRM vendor, to a client who had recently switched to Microsoft Dynamics CRM.

Watch out, Microsoft not just content to crash your computer, now wants to crash the Internet. This is Microsofts, long-planned, "Software as a Service"... The only problems..?

  • We would be returning to the "Mainframe-->Terminal" (High-Priest) computer architecture, which was intentionally replaced for a very large number of EXTREMELY GOOD reasons (greater speed, control, reliability, processing-power, etc.).
  • Microsoft would be able to change ("update"), alter, or LIMIT, their software whenever THEY [Microsoft] wish... NOT when the -customer- felt it was actually necessary, or appropriate.
  • Microsoft will, very effectively, be side-stepping the entire, recent, Supreme Court decision regarding -software ownership-. The purchaser would no longer have (or control) an actual "physical copy of the software". Why do you think that is..? And, what impact do you think that will have on consumer-rights..?
  • Microsoft could do (as they have always done)... and change formats, and methodologies, (and, very effectively, lock-out competition) whenever Microsoft felt like it.
  • Once Microsoft has the apps, you (or your business) depend-on, locked safely away in their servers... they can increase rates, charge "per user" (another, long-stated, Microsoft goal) at any time...and you [the customer] will simply NO SAY about it.
  • Basically, this would turn -computers- into little more than locked-down, locked-up, "Microsoft-services" delivery-devices (which are specifically designed to perpetually extract revenue, and impose absolute external-control, over ALL Computer-users).
  • And, you will, almost certainly, end-up... PAYING MORE.

In short, the entire Microsoft "cloud computing" push... fundamentally eliminates virtually ALL of the overwhelming advantages associated with the entire Personal-Computer industry (personal- power, convenience, control), and, would hand-over everything that Bill Gates/Microsoft... ever wanted... directly into the hands of a repeatedly-convicted illegal-monopoly (whose biggest problem has always been their complete failure to provide for, or even acknowledge, their customers actual needs/wants).

And, if you know anything about the history of Microsoft and the microcomputer industry, in general, then you would know just how destructive that will inevitably be to consumers (and any element of the industry that isnt directly controlled/owned/allowed... by Microsoft).

And, THAT, should scare the Hell out of anyone.

Now although this was nothing more than a tirade of anti-Microsoft sentiment devoid of coherent argument, what I really found amusing was the appalling misuse of grammar and syntax. Please, please, please, if you are going to engage in mindless diatribe and bad-mouth your competition, make sure it doesn't make you look like an inarticulate buffoon. Better still, for real inspiration as to how you should insult someone, let me remind you of the scene from Monty Python's "Holy Grail" where the French soldier is taunting King Arthur from the battlements:

"I don't want to talk to you no more, you empty headed animal food trough wiper. I fart in your general direction. Your mother was a hamster and your father smelt of elderberries"

Much better, don't you agree? Needless to say, the client was less than impressed with the vendor and replied as follows:

Slamming your competition and making disparaging comments about them is not a very professional way to sell your services. If your product is really superior, it should stand on its own merits, and there is no need for this type of scare tactic. Please do not contact me again, and try to be more professional in the future.

You see folks. Poor grammar just ain't (*sic*) good CRM :-)

This posting is provided "AS IS" with no warranties, and confers no rights.

Laughing Boy

Converting HTML E-mail To Plain Text

OK, I admit it. I've caught the CRM development bug. What started as a harmless bit of fun working on document library integration between CRM & SharePoint has now developed into an obsession. In this post I will describe how to build a plug-in that examines the body of any e-mail promoted promoted from Outlook or the e-mail router and converts the HTML into plain text.

After a bit of searching, I found a good article which showed how you could use regular expressions to remove unwanted HTML tags leaving just the plain text - Convert HTML to Plain Text. Converting this from C# to VB (my preferred choice of language) and stripping out some of the bits I didn't need, I came up with the following code which forms the basis of this plug-in.

Private Function ConvertHTMLToText(ByVal Source As String) As String
 
    Dim result As String = Source
 
    ' Remove formatting that will prevent regex from running reliably
    ' \r - Matches a carriage return \u000D.
    ' \n - Matches a line feed \u000A.
    ' \f - Matches a form feed \u000C.
    ' For more details see http://msdn.microsoft.com/en-us/library/4edbef7e.aspx
    result = Replace(result, "[\r\n\f]", String.Empty, Text.RegularExpressions.RegexOptions.IgnoreCase)
 
    ' replace the most commonly used special characters:
    result = Replace(result, "&lt;", "<", RegexOptions.IgnoreCase)
    result = Replace(result, "&gt;", ">", RegexOptions.IgnoreCase)
    result = Replace(result, "&nbsp;", " ", RegexOptions.IgnoreCase)
    result = Replace(result, "&quot;", """", RegexOptions.IgnoreCase)
    result = Replace(result, "&amp;", "&", RegexOptions.IgnoreCase)
 
    ' Remove ASCII character code sequences such as &#nn; and &#nnn;
    result = Replace(result, "&#[0-9]{2,3};", String.Empty, RegexOptions.IgnoreCase)
 
    ' Remove all other special characters. More can be added - see the following for more details:
    ' http://www.degraeve.com/reference/specialcharacters.php
    ' http://www.web-source.net/symbols.htm
    result = Replace(result, "&.{2,6};", String.Empty, RegexOptions.IgnoreCase)
 
    ' Remove all attributes and whitespace from the <head> tag
    result = Replace(result, "< *head[^>]*>", "<head>", RegexOptions.IgnoreCase)
    ' Remove all whitespace from the </head> tag
    result = Replace(result, "< */ *head *>", "</head>", RegexOptions.IgnoreCase)
    ' Delete everything between the <head> and </head> tags
    result = Replace(result, "<head>.*</head>", String.Empty, RegexOptions.IgnoreCase)
 
    ' Remove all attributes and whitespace from all <script> tags
    result = Replace(result, "< *script[^>]*>", "<script>", RegexOptions.IgnoreCase)
    ' Remove all whitespace from all </script> tags
    result = Replace(result, "< */ *script *>", "</script>", RegexOptions.IgnoreCase)
    ' Delete everything between all <script> and </script> tags
    result = Replace(result, "<script>.*</script>", String.Empty, RegexOptions.IgnoreCase)
 
    ' Remove all attributes and whitespace from all <style> tags
    result = Replace(result, "< *style[^>]*>", "<style>", RegexOptions.IgnoreCase)
    ' Remove all whitespace from all </style> tags
    result = Replace(result, "< */ *style *>", "</style>", RegexOptions.IgnoreCase)
    ' Delete everything between all <style> and </style> tags
    result = Replace(result, "<style>.*</style>", String.Empty, RegexOptions.IgnoreCase)
 
    ' Insert tabs in place of <td> tags
    result = Replace(result, "< *td[^>]*>", vbTab, RegexOptions.IgnoreCase)
 
    ' Insert single line breaks in place of <br> and <li> tags
    result = Replace(result, "< *br[^>]*>", vbCrLf, RegexOptions.IgnoreCase)
    result = Replace(result, "< *li[^>]*>", vbCrLf, RegexOptions.IgnoreCase)
 
    ' Insert double line breaks in place of <p>, <div> and <tr> tags
    result = Replace(result, "< *div[^>]*>", vbCrLf + vbCrLf, RegexOptions.IgnoreCase)
    result = Replace(result, "< *tr[^>]*>", vbCrLf + vbCrLf, RegexOptions.IgnoreCase)
    result = Replace(result, "< *p[^>]*>", vbCrLf + vbCrLf, RegexOptions.IgnoreCase)
 
    ' Remove all reminaing html tags
    result = Replace(result, "<[^>]*>", String.Empty, RegexOptions.IgnoreCase)
 
    ' Replace repeating spaces with a single space
    result = Replace(result, " +", " ")
 
    ' Remove any trailing spaces and tabs from the end of each line
    result = Replace(result, "[ \t]+\r\n", vbCrLf)
 
    ' Remove any leading whitespace characters
    result = Replace(result, "^[\s]+", String.Empty)
 
    ' Remove any trailing whitespace characters
    result = Replace(result, "[\s]+$", String.Empty)
 
    ' Remove extra line breaks if there are more than two in a row
    result = Replace(result, "\r\n\r\n(\r\n)+", vbCrLf + vbCrLf)
 
    ' Thats it.
    Return result
 
End Function

All that remains is to implement the IPlugin.Execute method. In order to be able to modify the e-mail message before the e-mail activity gets created in the database, I had to figure out which event(s) to intercept. Through a bit of trial and error, I observed that any e-mail promoted from Outlook triggers the "DeliverPromote" event, whereas any incoming e-mail handled by the e-mail router triggers the "DeliverIncoming" event. Interestingly enough, the "Create" event was also called as a child pipeline for these events, but modifying the message here didn't have any effect, even in the pre-processing stage.

Because plug-ins have the potential to introduce significant performance and scalability issues into your environment, it is important to ensure that the code is as efficient as possible. To that end I added additional checks to ensure that the even if registered on multiple events, the main code will only run if the plug-in:

  1. is running on the 'DeliverPromote' or 'DeliverIncoming' messages
  2. is running synchronously
  3. is running against the 'Email' entity
  4. is running in the 'pre-processing' stage of the pipeline
  5. is running in a 'Parent' pipeline
Public Class ConvertHtmlToText
    Implements IPlugin
 
    Public Sub Execute(ByVal context As IPluginExecutionContext) Implements IPlugin.Execute
 
        ' Exit if any of the following conditions are true:
        '  1. plug-in is not running synchronously
        '  2. plug-in is not running against the 'Email' entity
        '  3. plug-in is not running in the 'pre-processing' stage of the pipeline
        '  4. plug-in is not running in a 'Parent' pipeline
        If Not (context.Mode = 0) Or Not (context.PrimaryEntityName = "email") Or Not (context.Stage = 10) Or Not (context.InvocationSource = 0) Then
            Exit Sub
        End If
 
        If (context.MessageName = "DeliverPromote") Or (context.MessageName = "DeliverIncoming") Then
 
            For Each item In context.InputParameters.Properties
 
                If (item.Name = "Body") Then
                    context.InputParameters.Properties.Item("Body") = ConvertHTMLToText(CStr(item.Value))
                End If
 
            Next
 
        End If
 
    End Sub
 
End Class

As always, I have include the source code to my project here. Please do bear in mind that I haven't included any error handling or logging, so it's not production-ready. However, it should provide you with a good head-start.

This posting is provided "AS IS" with no warranties, and confers no rights.

Laughing Boy

Explaining CRM 4.0 System Requirements

Universally Speaking...

Every so often I get asked why we only support certain versions and/or service packs for some of the products we depend on, such as Windows or Office. Earlier this week I received the following question from one of our partners here in the UK:

I am currently involved in a large opportunity but the client's IS group are unable to upgrade to Outlook 2003 SP3. They are currently on SP2 and will not move to SP3 as it conflicts with their mainframe. Please can you let me know as soon as possible if there is any work around on the MS side that doesn’t involve loading SP3?

Obviously our partner was fully aware of our System Requirements which states that Office 2003 Service Pack 3 is the minimum supported configuration, but they had no additional information with which to engage in a sensible discussion with their client. Putting my thinking cap on I put together the following response:

The simple answer is that Office 2003 SP3 became available on 18th September 2007 which means the Office team will cease to support Office 2003 SP2 on 14th October 2008 - Support ends 12 months after the next service pack releases or at the end of the product's support lifecycle, whichever comes first. You can find all the Service Pack support dates here - http://support.microsoft.com/gp/lifesupsps

Since SP3 shipped several months before CRM 4.0 became available on 29th February 2008, the CRM team only tested fully against SP3. This is sensible strategy since you have to ask what happens if a CRM problem is caused by Office 2003 SP2, and the office team will no longer fix any SP2-related issue. We would have significant customer dis-sat.

So the deal here is:

  1. Will CRM 4.0 work on SP2? Probably, although it hasn’t been explicitly tested by the CRM team.
  2. If your customer has a post-implementation issue will they get CRM support? Probably, on a best efforts basis.
  3. If your customer has a post-implementation issue that requires a CRM or Office hotfix to be issued, will they get CRM support? No – not until they upgrade to Office 2003 SP3 (or whatever is the current supported service pack) and can reproduce the issue in this environment.

This is about risk. The risk of a problem occurring might seem low, but the impact of not being able to get a fix to a problem is potentially very high.

Although I haven't heard back as to the outcome of the discussion, our partner felt comfortable discussing the reasons behind our system requirements, and were hopeful they could help their client work through their issues.

This posting is provided "AS IS" with no warranties, and confers no rights.

Laughing Boy

Visual Studio 2008 Hangs After Adding A Web Reference

Sign Of The Times... 

A few weeks ago I came across a problem in Visual Studio 2008, whereby it seemed to hang every time I added a web reference to the CRM web service, and then tried using IntelliSense to discover the attributes of an entity. Looking at the processes in Windows Task Manager, showed that Visual Studio was consuming almost all the CPU cycles, with "devenv.exe" showing 99% CPU utilisation. After approximately 30 minutes, Visual Studio would return to normal, only to hang again the next time I tried to use IntelliSense or even just compile the project.

It turns out that a bug in the way Visual Studio 2008 handles large XML files (such as the CRM WSDL file) causes this behaviour, and simply disabling XML comment generation for each project solves the problem. I found a KB article which describes the problem and the fix in detail here - FIX: You may experience performance issues in the IDE after you use Visual Studio 2008 to build a Visual Basic project.

This posting is provided "AS IS" with no warranties, and confers no rights.

Laughing Boy

Creating A Debugger Visualizer For Dynamic Entities

Are You Gonna Be My Girl....

Over the last couple of months I've been spending quite a bit of time with the SDK, building custom workflow activities and plug-ins for CRM 4.0. One of the problems working with the IPlugin interface is that you are forced to use the DynamicEntity class when accessing the InputParameters and OutputParameters properties of the plug-in context.

This wouldn't be so bad, except that it is a real pain trying to find out what properties and values are actually contained within a Dynamic Entity whilst debugging and so I kept having to write the same "test harness" code over and over again just to examine their contents. However, last week I found out you can actually extend Visual Studio to provide this support by implementing a custom debugger visualizer.

Custom debugger visualizers were introduced in Visual Studio 2005 and enable you to view the contents of an object or variable in a meaningful way whilst debugging your application. Out of the box, Visual Studio ships with four standard visualizers, including the "Text Visualizer", "HTML Visualizer", and "XML Visualizer", all of which work on string objects, and the "DataSet Visualizer", which works for DataSet, DataView, and DataTable objects. However, none of these understand the how to work with the complexities of DynamicEntity object.

Fortunately there is plenty of information available online (how to write a visualizer, Creating Debugger Visualizers with Visual Studio 2005), and even a good Channel9 video interview with the Visual Studio program manager responsible for this feature - Scott Nonnenberg - Visualizers in VS 2005. This is well worth watching if you want to really understand how you can improve your debugging experience.

After playing around with this for a few hours I came up with a really cool solution using a TreeView and DataGridView control to provide detailed drill-down into dynamic entities. As you can see below, whenever the Visual Studio comes across an object of type Microsoft.Crm.Sdk.DynamicEntity, the visualizer is represented in the debugger by a magnifying glass icon. When you see the magnifying glass in a DataTip, in a debugger variables window or in the QuickWatch dialog box, you can click on the magnifying glass to select the DynamicEntity Visualizer.

Calling The DynamicEntity Visualizer

Once launched, the DynamicEntity Visualizer iterates through all the dynamic entity properties to build a tree view on the left side of the form, showing the property name and the property type. Clicking on each property, displays the property attributes in a table on the right side of the form.

Using The DynamicEntity Visualizer

At a basic level, a custom debugger visualizer is just a Windows Forms application that is launched by Visual Studio, with the object being debugged passed as a parameter.

The basic code you need to implement the visualizer is shown below. You will need to get a reference to "Microsoft.VisualStudio.DebuggerVisualizer.dll" which ships with Visual Studio, but you do need to make sure you are using the correct version of this assembly depending on whether you are using Visual Studio 2005 or Visual Studio 2008.

Imports System.ComponentModel
Imports System.Windows.Forms
Imports Microsoft.VisualStudio.DebuggerVisualizers
Imports Microsoft.Crm.Sdk
 
<Assembly: DebuggerVisualizer(GetType(DynamicEntityVisualizer), GetType(VisualizerObjectSource), Target:=GetType(Microsoft.Crm.Sdk.DynamicEntity), Description:="DynamicEntity Visualizer")> 
 
Public Class DynamicEntityVisualizer
    Inherits DialogDebuggerVisualizer
 
    Protected Overrides Sub Show(ByVal windowService As IDialogVisualizerService, ByVal objectProvider As IVisualizerObjectProvider)
 
        Dim de As DynamicEntity = CType(objectProvider.GetObject, DynamicEntity)
 
        ' Process dynamic entity and display windows form here
 
    End Sub
 
End Class

The <Assembly> attribute is used by Visual Studio to associate the Microsoft.Crm.Sdk.DynamicEntity type with the Visualizer, and once compiled, you can simply copy your assembly to the folder "C:\Program Files\Microsoft Visual Studio 9.0\Common7\Packages\Debugger\Visualizers\". Visual Studio will automatically use any visualizers it finds in this folder.

To build the user interface, simply create a new Windows Form in the project, add a "SplitContainer" control and place a "TreeView" control on the left side of the split and a "DataGridView" control on the right.

Next, create a "DynamicEntityProperty" class that we can use to store property attribute names and values.

Friend Class DynamicEntityProperty
 
    Private _propertyName As String
    Public Property PropertyName() As String
        Get
            Return _propertyName
        End Get
        Set(ByVal value As String)
            _propertyName = value
        End Set
    End Property
 
    Private _propertyValue As String
    Public Property PropertyValue() As String
        Get
            Return _propertyValue
        End Get
        Set(ByVal value As String)
            _propertyValue = value
        End Set
    End Property
 
    Public Sub New(ByVal name As String, ByVal value As String)
        _propertyName = name
        _propertyValue = value