Monday, July 28, 2008

Publishing Microsoft CRM 4.0 through ISA Server 2006

Henning Peterson Published Thursday, July 24, 2008 9:32 AM

Henning Petersen, CRM team support engineer points to a very popular post collaboration with the ISA team.

Last February Yuri Diogenes from the ISA/IAG team collaborated with Henning Petersen from the CRM Team on CRM 3 through ISA Server 2006. After this post, they received a lot of requests for an article on publishing CRM 4 using the Internet Facing Deployment option (IFD). This post is going to answer those requests. For this post we chose to let ISA handle the SSL Certificates as this is the common scenario for ISA deployments although other methods can be used.

We chose to focus this blog on letting CRM handle the authentication while letting ISA handle the SSL session. The main reason for using IFD despite ISA’s ability to provide forms based authentication was that the Microsoft Dynamics CRM Clients for Outlook would run into authentication problems if prompted with an ISA login. In order to get CRM running with IFD a good starting point is to study the IFD guide called How to configure an Internet-Facing Deployment for Microsoft Dynamics CRM 4.0 it can be downloaded from the Microsoft Download Center. The deployment guide will allow you to better understand the CRM 4 IFD concepts before you create any publishing rules on ISA Server.

Adjusting the CRM Server for External Publishing

To deploy this scenario the following topology was used:

Figure 1 – Topology using CRM IFD with ISA Server 2006.

We broke it down the IFD configuration in two parts.

Read more...

Henning Peterson

Microsoft Dynamics CRM Statement of Direction

This document outlines the future direction of Microsoft Dynamics CRM through to the next major release, Microsoft Dynamics CRM “V.Next”, which will be aligned with the Office 14 wave of product releases due in late 2009/early 2010.

Customer Link:

https://mbs.microsoft.com/customersource/documentation/whitepapers/MD_CRM_SOD

Partner Link:

https://mbs.microsoft.com/partnersource/marketing/statementofdirection/MD_CRM_SOD

*Note* that this document is intended to be periodically updated as details are developed and it is not intended to be a detailed specification as items may change or be dropped over the course of time. Notice of any updates will be provided.

image

Tuesday, July 22, 2008

Data Import Wizard Will not upload files past 32mb

I just hit this error on the Data Migration Wizard. My file is around 250mb in size. Well in researching this error I found that the files are being uploaded via to a website to be worked on... How interesting... Well the web.config file has the max file size in it, so you can edit it to allow for bigger sizes or break the file up into smaller chunks. I prefer to temporarily raise the limits.

Default file Location
C:\Program Files\Microsoft Dynamics CRM Data Migration Manager\DMClient\res\web

file web.config

Default data in the file

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<httpRuntime executionTimeout="1200" maxRequestLength="32768"/>
<compilation defaultLanguage="C#" debug="false">
<assemblies>
<add assembly="Microsoft.Crm, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add assembly="Microsoft.Crm.Sdk, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add assembly="Microsoft.Crm.SdkTypeProxy, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add assembly="Microsoft.Crm.Platform.Sdk, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</assemblies>
</compilation>
<authentication mode="Windows" />
<identity impersonate="true" />

</system.web>
</configuration>


I choose to modify maxRequestLength="32768" to be maxRequestLength="300000"

Microsoft Dynamics CRM E-mail Router and Exchange 2007: Keeping it Secure

Adam Bly Published Friday, July 11, 2008 6:38 AM

In the last several months, there have been increasingly greater numbers of questions from customers and partners regarding the configuration of the Dynamics CRM E-mail Router against Exchange 2007 in 'On Premise' deployments.  Specifically, how can one workaround those "invalid cert" pop-ups and the resultant error message during the Test Access phase of router configuration?  I mean, besides just un-enforcing the use of SSL…a solution more than a few have resorted to when pressed to find an easy answer.

By default, Exchange 2007 users certificates to secure client access and as such it's becoming increasingly important to familiarize oneself with the basics of PKI.  The problem is, even the basics tend to glaze the eyes of even seasoned admins despite their best efforts.  Additionally, it's often tough to find a one-size-fits-all checklist because when it comes to security, one size really can't fit all and take into account specific environments and implementation goals.  The end result is that there are lots of articles scattered about that all tell part of the story.   

Recently, I collaborated with our intrepid CRM Support Escalation team on such a case.  While I don't presume to think that my method can or will work for everyone in their production environments, it's the type of thing that I would recommend going over a couple of times in a lab or at home to de-mystify some of these concepts.  I think you'll find once you do this once or twice and are comfortable with the tools and methods, implementing cert-based security, while not always the most intuitive process, can be done.

Scenario: 

I have a lab environment the consists of 4 servers--a Domain Controller, an Exchange 2007 server, a CRM 4.0 full server install, and its back-end SQL 2005 server, all Windows 2003.  I'm trying to configure the E-mail Router on the DC but I keep getting the error: 

Incoming Status: Failure - The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel. The remote certificate is invalid according to the validation procedure. 

When I attempt to Test Access in Router Configuration Manager.  Additionally, I notice that when I try to use OWA, I get a pop error in IE regarding an invalid cert. 

This seems to be the general blueprint for the types of questions we’ve been receiving so I set out to repro the issue and document step-by-step its resolution.  I'll cite the resources that I used further below since, as mentioned, it's tough to describe a catch-all procedure--I think you'll find you can adapt these and other steps to your own lab.

Establish a standalone root CA 

In my test environment, I decided it would be overkill to get a 3rd party cert but in production this might be the way you want to go.  In any case, for my lab the first step was to create a new Standalone Root Certificate Authority (CA).  A standalone root will basically be the first and last word in my environment as to a machine or user identity.  It will, of course, not be valid in the real world.  For my purposes, though, it's perfect.  I decided to install the CA on my DC.

Open Control Panel > Add/Remove Programs > Windows Components > Certificate Services  and install Standalone Root CA.

You'll see some other options including Enterprise CA, etc.  One of the biggest differences is that requests of certificates must be *manually* approved/denied in a Standalone.  Something to think about if you intend to use this in a non-lab environment.

Create a new certificate request on the Exchange 2007 server 

The next step is the generate a request for certification by your CA from the Exchange server.  There are different ways you can go about this and you can even generate a request that can validate the cert Exchange 2007 installs by default.  For my purposes, though, I used the following command:

From Exchange Management Console:

New-ExchangeCertificate -GenerateRequest -FriendlyName "AdamCert" -Path c:\reqest.req -SubjectName "c=us, o=adam bly, cn=ablye2k7.ablydom.local" -DomainName *.ablydom.local 

FriendlyName -  This is just a simple, logical name for the cert

Path - File system location/filename for the REQ file this request will output

SubjectName - This is where a lot of people get hung up.  This is the NAME on the cert.  It should match whatever you want the name to resolve to (i.e. what you intend to enter on the router).  This field can be multi-valued…that is, you could add the NetBIOS name so that you can use the cert internally without using the FQDN.  This could also be used to enter the CNAME or alias (ex. My mail server is ablye2k7 but the CNAME is mail.ablydom.local). Also, c is the region, o is the owner/organization (I used my name but this could just as well be Contoso Ltd.  Something to be aware of if you go the 3rd party route)

DomainName - domain for which the cert is valid.  I used a wildcard, but this isn’t explicitly necessarily.

The Certificate request (.REQ) file should be created where you specified in the Path parameter.

Submit the request to your CA and approve it 

The next step is to process the request and issue the certificate.  To start, I copied the REQ file to my Certificate Authority (my DC :)) and opened the Certification Authority MMC snap-in.    

Right-click on the server name and select All Tasks > Submit a new request.  In the Open Request File dialog, specify the REQ file you copied over.   Next, right-click on the Pending Requests node, select All Tasks > Issue.  Finally, click on the Issued Certificates node and double-click the cert you JUST issued. 

Make sure the certificate information includes includes "Ensures the identity of a remote computer" and that the "Issued to" makes sense in your environment.  Mine would be ablye2k7.ablydom.local based on the request above.  The "Valid from"  should be the current date through at least a year in the future.  Now we need to export the cert in a format that Exchange can consume.

Open the certificate and click on Details tab, select Copy to File which will invoke the Certificate Export Wizard.  Click Next at the Welcome screen, and ensure the radio button corresponding to DER encoded binary X.509 (.CER) is selected, enter a filename and a location for the .CER file and click Finish. 

Almost done!  Copy the certificate back to your Exchange server. 

Import and Enable the new cert 

Back on the Exchange server, open the Exchange Management Console. 

Import-ExchangeCertificate - Path c:\adam.cer |Enable-ExchangeCertificate -Services SMTP, IIS 

If you are prompted, confirm the overwrite of existing cert (if applicable).  Just need to do a few more housekeeping chores and we're ready to go. 

Fix up OWA, etc., Virtual Directories 

In the Exchange Management Console, expand Server Configuration > Client Access > Outlook Web Access tab, right-click OWA (Default Web Site), Properties

Note the internal and external URL -- these should be the same (or whatever you specified in the SubjectName in the request; additionally, if you included the NetBIOS name of the server, you could set the Internal URL to be simply "http://ablye2k7/owa").  

Theoretically, you would perform these same steps for the OAB, POP3, IMAP4, EWS, and AutoDiscover.  

[PS] C:\Documents and Settings\Administrator.ABLYDOM>Set-WebServicesVirtualDirectory -Identity "EWS*" -InternalURL https://ablye2k7.ablydom.local

-ExternalURL https://ablye2k7.ablydom.local 

[PS] C:\Documents and Settings\Administrator.ABLYDOM>Set-ClientAccessServer -Identity "ablye2k7" -AutoDiscoverServiceInternalUri https://ablye2k7.ablydom.local/autodiscover/autodiscover.xml 

Configure the router 

Finally we're ready to configure the router.  Our cert is in place and valid per our environment's very own standalone root CA.  

My config profiles in the Router Configuration Manager look like the following:

IN - Location:  https://ablye2k7.ablydom.local

OUT - Location: https://ablye2k7.ablydom.local

Check "Use SSL"

It's very important that the URLs match what's on the cert!  This may seem like a "duh" type thing, but you'd be surprised how many people (me included) have kicked themselves when they realized they were trying to use the NetBIOS name for the Exchange server when the FQDN is what was on the cert.  :)

Test Access should succeed for BOTH methods and everything should be secure.  Perhaps a little drawn-out, but not entirely impossible, right?  Once you've run through this a few times, I think you'll agree it can be extrapolated to meet your environments needs once you understand the end-to-end process.  Additionally, the following resources helped me immensely when I was getting started.

More on Exchange 2007 and certificates - with real world scenario

http://msexchangeteam.com/archive/2007/07/02/445698.aspx 

Creating a Certificate or Certificate Request for TLS

http://technet.microsoft.com/en-us/library/aa998840(EXCHG.80,printer).aspx 

White Paper: Domain Security in Exchange 2007

http://technet.microsoft.com/en-us/library/bb266978.aspx 

A big thank-you to the folks responsible for those from me. 

Adam Bly

Client Side Scripting - Retrieving the current user information (CRM 4.0 version)

STUNNWARE POST



A while back I posted a solution about the various techniques to retrieve information about the current user in CRM 3.0. I received an email lately asking for help about how it is done in CRM 4.0, an it seemed time to update the article.

The new solution uses the Microsoft CRM web services to retrieve the user id, business unit id, organization id and the first, last and full name of the currently logged on user. I have attached a simple test form with the following OnLoad event:

var xml = "" +
"<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">" +
GenerateAuthenticationHeader() +
" <soap:Body>" +
" <RetrieveMultiple xmlns=\"http://schemas.microsoft.com/crm/2007/WebServices\">" +
" <query xmlns:q1=\"http://schemas.microsoft.com/crm/2006/Query\" xsi:type=\"q1:QueryExpression\">" +
" <q1:EntityName>systemuser</q1:EntityName>" +
" <q1:ColumnSet xsi:type=\"q1:ColumnSet\">" +
" <q1:Attributes>" +
" <q1:Attribute>businessunitid</q1:Attribute>" +
" <q1:Attribute>firstname</q1:Attribute>" +
" <q1:Attribute>fullname</q1:Attribute>" +
" <q1:Attribute>lastname</q1:Attribute>" +
" <q1:Attribute>organizationid</q1:Attribute>" +
" <q1:Attribute>systemuserid</q1:Attribute>" +
" </q1:Attributes>" +
" </q1:ColumnSet>" +
" <q1:Distinct>false</q1:Distinct>" +
" <q1:Criteria>" +
" <q1:FilterOperator>And</q1:FilterOperator>" +
" <q1:Conditions>" +
" <q1:Condition>" +
" <q1:AttributeName>systemuserid</q1:AttributeName>" +
" <q1:Operator>EqualUserId</q1:Operator>" +
" </q1:Condition>" +
" </q1:Conditions>" +
" </q1:Criteria>" +
" </query>" +
" </RetrieveMultiple>" +
" </soap:Body>" +
"</soap:Envelope>" +
"";

var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");

xmlHttpRequest.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
xmlHttpRequest.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/RetrieveMultiple");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
xmlHttpRequest.send(xml);

var resultXml = xmlHttpRequest.responseXML;
var entityNode = resultXml.selectSingleNode("//RetrieveMultipleResult/BusinessEntities/BusinessEntity");

var firstNameNode = entityNode.selectSingleNode("q1:firstname");
var lastNameNode = entityNode.selectSingleNode("q1:lastname");
var fullNameNode = entityNode.selectSingleNode("q1:fullname");
var systemUserIdNode = entityNode.selectSingleNode("q1:systemuserid");
var businessUnitIdNode = entityNode.selectSingleNode("q1:businessunitid");
var organizationIdNode = entityNode.selectSingleNode("q1:organizationid");

crmForm.all.sw_firstname.DataValue = (firstNameNode == null) ? null : firstNameNode.text;
crmForm.all.sw_lastname.DataValue = (lastNameNode == null) ? null : lastNameNode.text;
crmForm.all.sw_name.DataValue = (fullNameNode == null) ? null : fullNameNode.text;
crmForm.all.sw_systemuserid.DataValue = (systemUserIdNode == null) ? null : systemUserIdNode.text;
crmForm.all.sw_businessunitid.DataValue = (businessUnitIdNode == null) ? null : businessUnitIdNode.text;
crmForm.all.sw_organizationid.DataValue = (organizationIdNode == null) ? null : organizationIdNode.text;

And the test form shows up like this:

Besides the code dealing with the XML result, the SOAP message was of course constructed with the JavaScript Web Service Tool.

The Stunnware Tools Framework for Microsoft Dynamics CRM 4.0 - Professional Edition

STUNNWARE Tool is a must for CRM Developers!

As previously announced there will be a professional edition of the Stunnware Tools for Microsoft Dynamics CRM 4.0. It's almost complete, besides the documentation, but I wanted to show you some of the features you can expect when going for the professional edition.

I'm starting with the query builder, formerly known as the FetchXml wizard. What you have so far is the following:

  1. Create a query using a visual designer
  2. Paste an existing query, parse it and edit in the designer window
  3. Analyze the query and create .NET code executing the same query with a QueryExpression. Available for C# and VB.NET

The professional edition adds the following new features:

  1. Create .NET code for any available .NET language. Though C# and VB.NET are the most common ones, you can also create code for C++, J# or whatever you prefer.
  2. Execute the .NET code and run the query. It allows you to make changes and test before using the code in your own applications.
  3. Tracking the SOAP messages being sent and received. If you add additional .NET code, like Create, Update or Delete statements, you will get a list of all SOAP messages.
  4. Creates the JavaScript code you need in your CRM forms to access the CRM service. Again, the code is available for each request you made.

The video

I think it's easier to show the application than talking about it, so here's a short video created with Jing (awesome tool). It requires the Adobe Shockwave Player to be installed on your machine though.

 

Converting HTML E-mail To Plain Text

Posted: Thursday, July 10, 2008 10:34 PM by Simon Hutson

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.

Microsoft Dynamics CRM Online Video Gallery

Please see the link below to review a gallery of how-to videos using Microsoft Dynamics CRM Online. The videos include topics such as end-to-end scenario’s for Sales, Marketing, and Customer Service. There are also video’s for customizations, imports, and workflow.

All of the videos are the work of the CRM Online Technology Specialist team. Keep checking back for more as we will continue to add new videos. Feel free to ping us if there is something you would like to have covered.

www.democrmonline.com

CRM Online Customer Evidence Video – Total Structures

Another great testimonial from a Microsoft Dynamics CRM Online customer.

image

Total Structures manufactures structural staging systems to be used in rock concerts, trade shows, etc.  The vice-president of this company discusses the advantages to the company of an integrated CRM system, why he chose Microsoft Dynamics CRM Online and describes their successful implementation process and how Microsoft Dynamics CRM Online helps them keep the promises they make to their customers.

MSCRM 4.0 User Guide from Microsoft

Download Here
The Microsoft Dynamics CRM 4.0 User’s Guide includes all of the basic end-user information available in Help, including documentation on sales, marketing, and customer service features, as well as step-by-step instructions on working with Advanced Find and workflow. And it’s in an easily printable format.

Microsoft Partners and MVPs have told us that they want to provide customized documents for their customers. To make it easy to customize the document, we’re making a version available in Microsoft Office Word 2007 (.docx) format.

We’ve also talked to Microsoft Dynamics CRM customers, such as salespeople and customer service reps, who’ve expressed frustration at having to print out each Help page individually. Based on this feedback, we’ve included an Adobe Acrobat (PDF) version that serves as an all-in-one document you can distribute in your organization.

You can download both versions from the Microsoft Download Center.

Although the User’s Guide is currently available only for Microsoft Dynamics CRM 4.0 (on-premise) and only in English, we have plans to release the document for Microsoft Dynamics CRM Online and for additional languages. (We also plan on improving the formatting of the document over time; Word doesn’t seem to like 499 pages.)

Monday, July 21, 2008

Adding An I_Frame For An N:N Relationship

by Danny Varghese 07.10.08

I've mentioned in a previous post how to add an I_Frame to a related entity: http://crowechizek.com/cs/blogs/crm/archive/2008/03/18/adding-an-i-frame-that-contains-a-view-of-related-entity.aspx.  The steps mentioned work for any entity that has a 1:N or N:1 relationship in both CRM 3.0 and now the new CRM 4.0 (Titan).  As you all know, Titan now has the ability to create N:N relationships!  With any new feature comes some new challenges, but not to worry, you can add an I_Frame for N:N related entities.  Thanks to a user who posted a comment on my blog article, I did a little digging and found that an additional parameter is needed in the url.


The additional parameter is shown in red and has been added to the original code from my other post:

 var urlAct = ""; urlAct =  "areas.aspx?oId=" + crmFormSubmit.crmFormSubmitId.value + "&oType=" + crmFormSubmit.crmFormSubmitObjectType.value + "&security=" + crmFormSubmit.crmFormSubmitSecurity.value +"&tabSet=areaActivityHistory" + "&roleOrd=2";document.getElementById('IFRAME_History').src = urlAct;

Although I don’t have confirmation of what this parameter is, I believe it may stand for "Role Ordinal."  If you look at the definition of the word "ordinal," it means to define the order or succession of something.  This parameter appears to define which side/order of the N:N relationship to display.  In the example above, the value is "2," which represents which side of the relationship you want to view.  So if you create an N:N relationship say from the Account entity, and you're asked to fill in the "Other Entity" section, that would represent the variable "2."  Another way to look at it is, if you're on the Account form, and you want to create an I_Frame pointing to the related entity of the N:N relationship, then the "roleOrd" is 2.  The best way to find the value of this parameter is if you view the source of the page you're on, do a find for "roleOrd," and see what the value is for the related entity.


Either way, I've seen this example work.  If anyone has any comments on this parameter, please do comment.  Thank you!

CRM Custom RSS Feed in less than 30 minutes

by Mitchell Kett 07.11.08


One of the best ways to improve a client's business is to keep users better informed and up-to-date on the information provided by CRM.  A workflow could be created (and maintained) to send out an email to the appropriate parties when a specific event happens (create, update, delete of an entity), but what if we could go one step further and provide the same up-to-date information without emails (and maintaining who gets what) or without the need for a user to look in CRM?  What about using an RSS feed?

 

Thanks to a very useful tutorial provided by Jeff at uberasp.net, creating an RSS feed for CRM can be done in a matter of minutes.  For a very quick crash course in XML and the syntax for RSS, see  http://www.w3schools.com/rss/rss_syntax.asp .

 

Say I'd like to create an RSS Feed for a specific entity in CRM.  Whenever a new record is created for this entity, I want to see it in my RSS Feed.  For this example, I created a custom entity in CRM called "new_rssfeed".  The only attribute I added to new_rssfeed was an ntext field called "new_description" which will contain text describing the new record.  After publishing my new entity type, I opened up Visual Studio 2005 and started a new ASP.Net Web Site.  I renamed the Default.aspx file generated by VS to "RSS_Feed.aspx" and changed the code to the following:

 

//RSS_Feed.aspx

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="RSS_Feed.aspx.cs" Inherits="_Default" EnableViewState="false" %>

<%@ OutputCache Duration="300" VaryByParam="none" %>

 

Yup, that is all you should see in your .aspx file.  No need for any html tags or DOCTYPE declarations.  What will happen is that when a user navigates to the RSS_Feed.aspx file, the Page_Load event will generate a stream of XML code which the web browser will interpret as an RSS feed.  So there is no need for any HTML.

 

Within the code-behind file, RSS_Feed.aspx.cs, I added the following code to generate the XML for the feed within the Page_Load event.  You can use this code as a template for your own feed.

 

//RSS_Feed.aspx.cs

protected void Page_Load(object sender, EventArgs e)

    {

Response.Clear();

Response.ContentType = "text/xml";

XmlTextWriter objX = new XmlTextWriter(Response.OutputStream, Encoding.UTF8);

objX.WriteStartDocument();

objX.WriteStartElement("rss");

objX.WriteAttributeString("version","2.0");

objX.WriteStartElement("channel");

objX.WriteElementString("title", "Practice CRM RSS Feed");

objX.WriteElementString("link","http://localhost:5555/RSS/RSS_Feed.aspx");

objX.WriteElementString("description","Live, up-to-date information coming from CRM!");

objX.WriteElementString("copyright","(c) 2008. All rights reserved.");

objX.WriteElementString("ttl","5");

SqlConnection objConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["crmConnectionString"].ToString());

objConnection.Open();

string sql = "SELECT TOP 10 new_name, new_description, new_rssfeedid, createdon FROM new_rssfeed ORDER BY createdon DESC";

SqlCommand objCommand = new SqlCommand(sql, objConnection);

SqlDataReader objReader = objCommand.ExecuteReader();

while (objReader.Read())

{

objX.WriteStartElement("item");

objX.WriteElementString("title",objReader.GetString(0));

objX.WriteElementString("description",objReader.GetString(1));

objX.WriteElementString("link", "http://localhost:5555/MicrosoftCRM/userdefined/edit.aspx?id=" + objReader["new_rssfeedid"].ToString() + "&etc=10008");

objX.WriteElementString("pubDate", objReader.GetDateTime(3).ToString("R"));

objX.WriteEndElement();

}

objReader.Close();

objConnection.Close();

 

objX.WriteEndElement();

objX.WriteEndElement();

objX.WriteEndDocument();

objX.Flush();

objX.Close();

Response.End();

    }

 

Notice the bolded text within the code.  These are snippets that will differ in your code.  For my RSS feed, I gave it the title of "Practice CRM RSS Feed".  The link element is for the URL used to get to the aspx file.  For my connection to CRM, I simply created a web.config file with a connection string to my CRM DB.  Throw in your own custom SQL Query to grab the necessary info to populate the "title", "description", "link", and "pubDate" for the feed <item> element.  The above code, in a nut shell, will grab the 10 most recently added New_rssfeed elements and format them for the feed.  I built and published the web site project and the last thing to do was configure IIS to make the feed accessible.

 

In IIS, all that I needed to do was create a new virtual directory with the alias "RSS" under the Microsoft CRM web site and point it to the folder with the compiled web code.  It automatically saw the web.config file, so no other adjustments had to be made.  Do an IIS reset and navigate to the aspx page.   You should see a basic page with the feed title and a description of how to subscribe to the feed.  You will also see the feed articles listed below and search options to the right (I used IE7 -- other browsers may render differently or re-direct to an RSS Reader like Google Reader).

 

Example of RSS Feed rendering in IE7 (click to view larger image)

 

Just think of what you could use this for!  You could integrate workflows and plugins with an RSS feed in order to provide up-to-date info on what's happening in CRM to other users (or anyone within the local network).  Inform sales people of new opportunities and leads, give executives updates by the minute as opportunities close and new ones come in.  Create one generic feed and register a plugin for multiple actions which could generate a variety of updates to the feed.  You could even create multiple feeds/aspx files and give users the option of how much/ little they'd like to get updated on.  We could even throw in a couple parameters  like entity type and GUID and we've got an RSS feed for one specific record in CRM. 

 

I merely scratched the surface of RSS (you can add images and other content as well), so be creative and think of how you might be able to use this to keep users (and developers) better informed of what's going on in CRM.