Wednesday, February 15, 2012

CRM 4.0 CrmReportingException (rsExecutionNotFound)


Exception information:
Exception type: CrmReportingException
Exception message: Execution 'lrficj45gesmhz55xrmfcxuc' cannot be found (rsExecutionNotFound)

Resolution:
The report server timezone was different than the CRM web server. Update timezone and resart SSRS.

Tuesday, January 24, 2012

CRM 4.0 Web Mail Merge Macro Not Available


When using the MS CRM 4.0 web mail merge, the CRM button (macro) is not available once MS Word opens. This could be caused by a DNS resolution issue. In the case of a web farm, the client attempts to download the MS Word template that contains the macro from one of the server nodes by its DNS name. If each server name cannot be resolved by the client this issue may occur. To resolve this issue, add an entry to your local host file or have networking update the DNS catalog. To further diagnose the issue, use Fiddler while attempting to perform a mail merge and any DNS issues should be visble in Fiddler.

CRM 4.0 Web Mail merge filters out inactive records

A hotfix was released which included a new MSCRM DWORD registry entry to filter inactive mail merge records. To disable this filter see the following KB article.KB963054

Friday, October 30, 2009

Adding a New NavBar to an Entity Form

CRM 4.0 does not provide a very good story on customizing entity form navigation. It's acceptable for most configurations, but it would be nice if the left navigation within an entity form could be customized further. At least from the standpoint of adding additional NavBars, instead of having to place relationship and sitemap NavbarItems in one of the four provided by Microsoft (Details, Sales, Service, Marketing).

Below is the Account form with a 1-to-many relationship to an entity named Custom Entity. Through the relationships configuration on the Account entity, the only options we have are to display the link in the Details, Sales, Service, or Marketing areas. These areas can be renamed using the ISV Config file, but we cannot move any of the out-of-the-box entities to different sections. Below is a screen shot of the Account entity.

















What will be demonstrated in this post is how to create a new NavBar on the Account entity and move NavBarItems to it. This solution is not supported due to the form manipulation but is fully functional. Even security applies, meaning, the NavbarItems will only display if the user has proper access, and if the user has no access to any of the NavBarItems, the new NavBar will not display. Below is the end result. The Custom Entities, Sub-Accounts, and Relationships NavBarItems have been moved to a new NavBarItem.
















Below is a JavaScript class that can be used to create new NavBars using the OnLoad event of an entity form.


// =====================================================
// JsNavBar v1.0 - 10/30/2009 (CRM 4.0)
// =====================================================
JsNavBar = function(displayName) {
var navBarDisplayName = displayName;
var navBarItemArray = new Array();

JsNavBar.prototype.AddNavBarItem = function(navBarItemId) {
navBarItemArray[navBarItemArray.length] = document.getElementById(navBarItemId);
};

JsNavBar.prototype.Show = function() {
var crmNavBar = document.getElementById("crmNavBar");
if (crmNavBar != null) {

var navBarItems = document.createElement("UL");
navBarItems.style.display = "inline";
navBarItems.className = "ms-crm-Nav-Group-Subareas";

for (i = 0; i < navBarItemArray.length; i++) {
if (navBarItemArray[i] != null) {
navBarItems.appendChild(navBarItemArray[i]).parentNode;
}
}

if (navBarItems.hasChildNodes()) {
var newNavBar = document.createElement("LI");
newNavBar.className = "ms-crm-Nav-Group";
newNavBar.innerHTML = '' + navBarDisplayName + ': Expanded, click to collapse';
newNavBar.appendChild(navBarItems);

if (crmNavBar.childNodes.length > 1) {
var secondNav = crmNavBar.childNodes[1];
crmNavBar.insertBefore(newNavBar, secondNav);
} else {
crmNavBar.appendChild(newNavBar);
}
}
}
};
};


The next snippet of code shows how to use the above code.

var demoNavBar = new JsNavBar("Demo");
demoNavBar.AddNavBarItem("navSubAct");
demoNavBar.AddNavBarItem("navRelationships");
demoNavBar.Display();


To obtain the id's of the NavBarItems on the form, the IE Developers Toolbar is a great tool and can be downloaded here: (http://www.microsoft.com/downloads/details.aspx?familyid=e59c3964-672d-4511-bb3e-2d5e1db91038&displaylang=en). Once installed, to use it, open a CRM entity form, press CTRL+N. A new window will open with the address and toolbars displayed. Select Tool->Toolbars-->Explorer Bar-->IE Developer Toolbar. It can be used the NavBarItem ids. See below.

Wednesday, October 28, 2009

Querying a Filtered View in a Custom Application with a Double Hop

Have you ever needed to directly query a filtered view in CRM directly from a custom web page? On occasion it may be necessary for performance reasons. One good example is in creating a hierarchical view of accounts. To do this, recursion is needed and doing so using the CRM Service is just not performant. Simply because of all the round trips through the CRM Service. A good solution is to use a SQL Server Common Table Expression (CTE) and ADO.NET. This way all the heavy lifting is done on SQL Server and only a single round trip to the database is needed.

This won't be demonstrated in this blog, but instead a solution for getting around the double-hop rule in a multi-server deployment where CRM and SQL Server are installed on separate machines. Two additional solutions are available; first a domain account with a CRM license can be used as the Application Pool account in IIS. The web application can use this application pool with impersonation disabled, and as long as the account has permission to the data that is queried, things will work fine. However, data row-level security is thrown out the door for the calling user, as the Application Pool account is used. A second solution is to use Kerberos with trusted delegation configured in Active Directory. This is a good solution and maintains data row-level security for the calling user, but it is no easy task to set up.

Another option is to use SQL Server's context_info. More information can be found here (http://msdn.microsoft.com/en-us/library/ms180125.aspx). CRM uses the context_info to set the calling user's systemuserid in double hop scenarios and every filtered view checks the context_info for the systemcuserid of the calling user in the function [fn_FindUserGuid] located in the organization database.

When setting up the custom application in IIS, the application pool can use the Network Service account or whichever service account was used to install CRM. Impersonation needs to be disabled as the custom application will use the Network Service account to login to SQL Server. This is possible as the account is added to the SQL Access Group when CRM is installed. With this basic configuration the custom application can access SQL Server, however, since the account is not a CRM user and not occupying a license it will not have access to any data. This is where the context_info comes into play.

The query below demonstrates how to use the context_info when querying a filtered view.















Pretty simple, except how do we get the calling user's systemuserid? This is where impersonation is used but in code. The authenticated user can be impersonated prior to calling the WhoAmIRequest using the CRM Service. The WhoAmIRequest will return the userid(guid) for the calling user. Impersonation can then be undone in code and prior to the database call.

Below is a webform that demonstrates how to do this.



ASPX:









runat="server" ID="ResultsGrid"
AutoGenerateColumns="true"
DataSourceID="CrmDataSource"
HeaderStyle-Font-Bold="true">


ConnectionString="Data Source=dev-sql02;Initial Catalog=Neudesic_MSCRM;Trusted_Connection=True;">




Code Behind:

namespace PureCRM.Samples.FilteredView
{
public partial class FilteredViewExample : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}

protected void ExecuteButton_Click(object sender, EventArgs e)
{
CrmDataSource.SelectCommand = SetContextInfo(QueryText.Value);
ResultsGrid.DataBind();
}

private string SetContextInfo(string sqlQuery)
{
string contextInfoFormat = "declare @userid uniqueidentifier = '{0}' \rset context_info @userid\r{1}";
string systemUserId = GetCrmSystemUserId();
string sql = string.Format(contextInfoFormat, systemUserId, sqlQuery);

return sql;
}

private string GetCrmSystemUserId()
{
WindowsIdentity identity = (WindowsIdentity)Thread.CurrentPrincipal.Identity;
WindowsImpersonationContext impersonationContext = identity.Impersonate();

CrmAuthenticationToken token = new CrmAuthenticationToken()
{
AuthenticationType = AuthenticationType.AD,
OrganizationName = "Neudesic"
};

CrmService service = new CrmService()
{
Url = "http://dev-crm01/MSCRMServices/2007/CrmService.asmx",
UseDefaultCredentials = true,
CrmAuthenticationTokenValue = token
};

WhoAmIRequest request = new WhoAmIRequest();
WhoAmIResponse response = (WhoAmIResponse)service.Execute(request);

impersonationContext.Undo();

return response.UserId.ToString();
}
}
}



Wednesday, August 12, 2009

Leveraging ASP.NET AJAX in a CRM 4.0 Form

This is an example of how to use ASP.NET AJAX within the context of a CRM 4.0 form. Many CRM developers have been challenged with implementing complex business features within CRM. Many consist of integrating with external or public services. This post will demonstrate a simple pattern to implement ASP.NET AJAX within a CRM 4.0 form. This example demonstrates the ability to validate and gather data based on the user inputting a zip code on the Contact form.

The free public service that will be consumed is a zip service that validates and returns zip code information for USA zip codes.
Url (
http://www.webservicex.net/uszip.asmx).

Here is the solution in action.


Creating the Web Service Project

First a Web Service project is created to consume the public zip service. This project will consume the zip service and expose a new service that will take a single parameter for the zip code, and return an object that is serializable to support JSON. Using JSON simplifies and reduces the amount of JavaScript needed to attain the data in the client. The purpose of wrapping the public service is not only to abstract the service from the CRM implementation, but also provide the ability to deploy the service within the CRM application domain. In order to use AJAX, the service must reside within the application domain to avoid the cross-domain security restriction.

In Visual Studio a new ASP.NET Web Service Application project is created. This project is targeting .Net Framework 3.5. In order to use this version, it will have to be installed on the CRM server.















In order to support Json, the following config section needs to be added to the web.config.







Next a Web Reference is added to the project for the public web service.

Url: http://www.webservicex.net/uszip.asmx?WSDL

















Then a new class is added to the project for the object that will contain the data returned to the client. Below is the C# code. It has a constructor that takes an XmlNode type. The public service returns Xml, so this constructor is used to instantiate and populate the class from within the web service web method. It also must contain a default parameter-less constructor required by the serializer. Notice the class is decorated with attributes. These are required by the JsonSerializer in order to properly serialize the object. This post does not go into any more detail on using the JsonSerializer as there are plenty of articles on the web for more detail.





















Next a new web service is added to the project.

In the new web service a new method is added called LookUpZipCode. Below is the code for the web service that wraps the public service. This is a very simple method that calls the public service method (GetInfoByZIP), then returns an instance of the ZipInfo class.














Next the service is tested.




















Results:













This new service can now be consumed within the CRM application domain. One way to consume this service within a CRM form is to use Javascript and the XmlHttpRequest object. This solution is commonly used, but it's a pain. So this example demonstrates how to utilize ASP.NET AJAX and the ScriptManager control.

A new Web Form is now added to the project called ClientProxy.aspx. This page will act as the web service proxy on the client.

Next, the ScriptManager control is added to the aspx page. In the properties of this control a service reference is added to the web service that was created in the above steps. For those not familiar with the ScriptManager control can read more here (http://msdn.microsoft.com/en-us/library/bb398863.aspx). Basically, The ScriptManager control manages client script for AJAX-enabled ASP.NET Web pages. The control registers the script for the Microsoft AJAX Library with the page. This enables client script to use the type system extensions and to support Web-service calls.





Next, a Jscript file is added to the project called ClientProxyScript.js

The purpose of this script is to abstract the client web service call from the CRM form. It consists of three methods, an entry method that will be called from within the CRM form called LookupZipCode, it takes the crmForm instance as a parameter. The other two methods are callback methods the service will call when it completes or fails. Inside the complete method is where the CRM form attribute values are set. Notice the results object returned is de-serialized under the covers by MS AJAX.














Next, the script file path is added to the ScriptManager in the ClientProxy.aspx page. The ScriptManager will push the script to the client for use.







The last thing to note about the aspx page, is for this simple solution it does not require any code behind. However, if there were multiple services, functionality could be added in code behind to dynamically add the service and script references to the ScriptManager using a query string parameter as the key. This would provide a single page the client would utilize instead of a separate aspx for each service.

Deploying the Web Service Project

To deploy the web service, a new directory in the CRM website is created within the ISV directory. This example is deployed in ISV/Samples/ZipCodeLookup. The web service can now be published to this directory. Since this service requires a web.config it must run under it's own application in IIS.

Next, in IIS Manger a new application pool is created.













In IIS Manager, this new directory is converted to an IIS application by navigating, expanding and right-clicking on the ISV/Samples/ZipCodeLookup directory and selecting Convert To Application. This new application will inherit all the settings from the CRM website including Anonymous Access - Disabled, Windows Authentication - Enabled, and Impersonation - On.














In the Convert To Application dialog box, the Application Pool is changed to use the USZipCodeLookup application pool created earlier. Isolating the web service in it's own application pool protects the CRM application from any issues that may arise in the web service.
















Configuring the AJAX Service in CRM

This example uses the Contact entity, but it could be configured on any out-of-the-box or custom entity that requires an address. In CRM, the Contact entity Form is edited in the Customization area, where the zip code attribute is set to read-only. The form's OnLoad event will enable the zip code when the IFrame is loaded. Next, an IFrame is added to the bottom of the form. See the properties below. The Url is set to about:blank as it will be set dynamically in the forms OnLoad event. Also, leveraging an IFrame provides the ability to load the ClientProxy.aspx page, thus giving the form visibility to all the JavaScript the ScriptManager is configured to load.





















In the form properties the form's OnLoad event is updated to include the following code. This will obviously not work if the user is offline, so it first checks if the user is online. Then it checks if the form is a create or edit form. It should not be available on any other form type as the form will be read-only. Next, the code dynamically sets the src property of the IFrame using a relative path to the location of the ClientProxy.aspx page. And lastly, it wires the onreadystatechange event of the IFrame. When the IFrame load is completed, the zip code attribute is enabled. This prevents a user from entering a zip code prior to the IFrame loading. The IFrame usually loads very quickly, except for the first time the aspx is requested as the service application needs be JIT'ed.










Lastly, the zip code attribute's OnChange event is updated with the following code. This code gets a handle to the IFrame's contentWindow and calls the LookupZipCode method in the ClientProxyScript.js file.





Final Thoughts

Coming soon, a follow-up post with the same implementation that uses WCF REST instead of an ASMX Service.

Tuesday, August 11, 2009

CRM 4.0 ScaleGroup Job Editor

Check out the ScaleGroup Job Editor, it allows an administrator to modify the schedules for the Deletion Job and the Re-Indexing Job for CRM 4.0 deployments. It's the ScaleGroup Job Editor and it can be downloaded here:

http://code.msdn.microsoft.com/ScaleGroupJobEditor

A few things to note, the Deletion Service is no longer a separate service for CRM 4.0. The Async Service now handles the job of deleting records from CRM, and it handles a maintenance job normally handled within a SQL Server job that re-indexes the database. By default these jobs run for each individual Organization every 24 hours. The scheduled time is initially set at the time a CRM Organization is created. So keep that in mind, normally I would recommend these jobs run off hours usually late at night. This is a very handy admin tool that can be used to reset these times. It's also very useful in development. From my own experience you may at times want to delete the data that is created by developers, testers or end users. You can use this tool to reset the time for the deletions to occur immediately. Below is a screen shot, it's very easy to use.