Tuesday, May 26, 2009

Bulk Edit Issue on Custom Entities

A typical problem while performing a Bulk edit on any custom entity is that all the hidden fields on the form become visible. Since MS CRM does not provide a way out to let the fields remain hidden on bulk edit form, you might think of restricting users from performing bulk edits for custom entities.
Here is the simple way to proceed on this:

1. On the custom entity Form, add a hidden Iframe.
2. Create a simple .htm file and create a new virtual directory in IIS under the ISV folder where you place this htm page.
3. Specify this relative path as the URL of the iframe.

Example if your custom application is hosted at \ISV\MyCustomWebPages folder and the .htm file is MyPage.htm, the Iframe URL would be \ISV\MyCustomWebPages\MyPage.htm.

In the .htm file, check the form type for Bulk Edit. MSCRM by default provides form type an enum for form types. (See SDK:
http://technet.microsoft.com/en-us/library/cc150873.aspx ) Bulk edit type form value is 6.

if(parent.document.forms[0].FormType == 6)
// parent.document.forms[0] is the reference for the custom entity form
{
// Do required
alert("Bulk Edit is restricted for this entity");
window.close();
}

This code would check if the user wants to perform a Bulk edit for the custom entities. If so, it alerts the user and closes the form.

Note: This behavior is not reproducible for out of the box entities.

Wednesday, May 20, 2009

How to develop yahoo widgets for accessing MSCRM 4.0 data


In this blog, I’ll take you through the development of yahoo widget for accessing MSCRM 4.0 data for the on premise deployment of MSCRM. In my future blogs you may find the implementation for the hosted & Crm Live models as well.

The purpose of this article is to quickly access the MSCRM data & provide a quick navigation link to directly jump to the record in MSCRM using browser.So, what kind of data would be more relevant to a user in MSCRM? Well answer to this question depends on the role of the user but, MSCRM Activities are something which is relevant to all types of users. So, in this walkthrough we’ll retrieve the activity records of MSCRM 4.0 to display i.e fetching all the activities for the currently logged in user from the MSCRM server.

Audience Profile: The target audience should already be familiar with Yahoo widget’s development and should have little understanding of XML, JavaScript and webservice calls as well.

To start with prepare your layout for the widget by specifying some XML tags in the .kon file which is the main starting point for the widgets.
Below is an example for the kon file.

<?xml version="1.0" encoding="UTF-8"?>
<widget minimumVersion="4.0">
<settings>
<setting name="allowCustomObjectAttributes" value="true"/>
<setting name="debug" value="on"/>
</settings>
<window title="Sample Widget!">
<name>mainWindow</name>
<width>380</width>
<height>136</height>
<alignment>left</alignment>
<opacity>255</opacity>
<visible>1</visible>
<shadow>1</shadow>
<onFirstDisplay>
mainWindow.hOffset = (screen.width/2) - 114;
mainWindow.vOffset = screen.availHeight + 195;
</onFirstDisplay>
<!-- Specify images as part of layout you want to display-->
<image src="Topbar.png">
<name>top</name>
<hOffset>7</hOffset>
<vOffset>1</vOffset>
<onMouseUp>
openURL("http://www.Grapecity.com");
</onMouseUp>
</image>

<image src ="pagingFooter.png">
<name>pagingFooterImage</name>
<hOffset>7</hOffset>
<vOffset>45</vOffset>
<zindex>1</zindex>
<visible>false</visible>
</image>
</window>

<action trigger="onLoad">
<!--include the javascript file here containing code for web service calls-->
include("MSCRM_Activities.js");
</action>

<action trigger="onUnload" >
<!--Specify any action when disconnecting-->
play('Widget_Shut_disconnect.wav');
</action>

<!-- Specify the preference groups, displayed as tabs in the preferences window -->
<prefGroup>
<name>msCRM</name>
<title>MSCRM Settings</title>
<icon>Resources/CRM_Logo.gif</icon>
<order>1</order>
</prefGroup>

<!-- Specify the user configurable preferences -->
<preference name="OrganizationName">
<title>Organization Name</title>
<group>msCRM</group>
<type>Text</type>
<defaultValue>MicrosoftCRM</defaultValue>
<description>Organization's Name (Case Sensitive)
</description>
</preference>
<preference name="ServerName">
<title>Server</title>
<group>msCRM </group>
<type>Text</type>
<defaultValue>localhost:5555</defaultValue>
<description>With port number (if any). Example: ServerName:Port</description>
</preference>
</widget>


Note: The above declarations are just for example, you’ll need to design the layout for the widget.

We will be using web service calls to retrieve the xml data and that we can parse later on and display.

Below is the code to be included in the MSCRM_Activities.js file to call the MSCRM 4.0 webservices with active directory authentication.

//Poll for the target organization using discovery service and get the Url for the CrmService
function RetrieveOrganizationsRequest()
{
// Prepare the soap request 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">" +
" <soap:Body>" +
" <Execute xmlns="http://schemas.microsoft.com/crm/2007/CrmDiscoveryService">" +
" <Request xsi:type="RetrieveOrganizationsRequest" />" +
" </Execute>" +
" </soap:Body>" +
"</soap:Envelope>" +
"";

xmlHttpRequest = new XMLHttpRequest();

//Event handler to monitor the status of the request, asynchronously
xmlHttpRequest.onreadystatechange = RetrieveOrgStatusProc;

//Fire the request asynchronously
xmlHttpRequest.open("POST", "http://"+ "localhost:5555"+"/MSCRMServices/2007/AD/CrmDiscoveryService.asmx", true);
xmlHttpRequest.setRequestHeader("SOAPAction","http://schemas.microsoft.com/crm/2007/CrmDiscoveryService/Execute");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
xmlHttpRequest.send(xml);
}


// Monitor the status of the request
function RetrieveOrgStatusProc()
{

if ( this.readyState == 4 ) // request complete
{
if ( this.status == 200 ) // Success
{
var resultXml = this.responseXML;

var orgDetailNodes = resultXml.evaluate("soap:Envelope/soap:Body/ExecuteResponse/Response/OrganizationDetails/OrganizationDetail");

for( i=0 ; i < orgDetailNodes.length ;i++)
{
//Extract the single Business entity node item from the nodelist
var singleOrgDetailNode = orgDetailNodes.item(i);
var org = singleOrgDetailNode.evaluate("string(OrganizationName)");
if ( org == strOrganizationName )
{
crmServiceUrl = singleOrgDetailNode.evaluate("string(CrmServiceUrl)");
break;
}
}

if(crmServiceUrl=="")
{
alert("Organisation not found");
}
else
{
WhoAmIRequest();
}
}
}
}


Note: Some supported methods listed in konfabulator reference (SDK for yahoo widgets) have been used for xml document parsing. Please refer to SDK for more information.


// Retreive the MSCRM userid for the currently logged in user
function WhoAmIRequest()
{
//Prepare the xml soap request to be fired
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">" +

// This is the soap header containing your authentication information uses windows authentication in this case.
" <soap:Header>" +
" <CrmAuthenticationToken xmlns="http://schemas.microsoft.com/crm/2007/WebServices">" +
" <AuthenticationType xmlns="http://schemas.microsoft.com/crm/2007/CoreTypes">0</AuthenticationType>" +
" <OrganizationName xmlns="http://schemas.microsoft.com/crm/2007/CoreTypes">"+"MicrosoftCRM" +"</OrganizationName>" +
" <CallerId xmlns="http://schemas.microsoft.com/crm/2007/CoreTypes">00000000-0000-0000-0000-000000000000</CallerId>" + " </CrmAuthenticationToken>" +
" </soap:Header>" +

//Soap body starts here
" <soap:Body>" +
" <Execute xmlns="http://schemas.microsoft.com/crm/2007/WebServices">" +
" <Request xsi:type="WhoAmIRequest" />" +
" </Execute>" +
" </soap:Body>" +
"</soap:Envelope>" +
"";

xmlHttpRequest = new XMLHttpRequest();

// Event handler to monitor the status of the request, asynchronously
xmlHttpRequest.onreadystatechange = WhoAmIStatusProc;

//Fire the request asynchronously
xmlHttpRequest.open("POST", "http://" + "localhost:5555" + crmServiceUrl.substring(7).substring(crmServiceUrl.substring(7).search("/")), true);
xmlHttpRequest.setRequestHeader("SOAPAction","http://schemas.microsoft.com/crm/2007/WebServices/Execute");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", xml.length);

// Send the request
xmlHttpRequest.send(xml);
}


// Monitor the status of the request
function WhoAmIStatusProc()
{
if ( this.readyState == 4 ) // request complete
{
if ( this.status == 200 ) // Success
{
var resultXml = this.responseXML;

// retrieve userId
UserId = resultXml.evaluate("string(soap:Envelope/soap:Body/ExecuteResponse/Response[@xsi:type='WhoAmIResponse']/UserId)");

if(UserId==null UserId=="" )
{
throw "UserId is Null";
}
// Reteive the activities for the UserId Passed
GetActivitiesRequest(UserId);

}

}

}


// Now retreive all activities for the user
function GetActivitiesRequest(UserId)
{
// Prepare the soap request
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">" +
" <soap:Header>" +
" <CrmAuthenticationToken xmlns="http://schemas.microsoft.com/crm/2007/WebServices">" +
" <AuthenticationType xmlns="http://schemas.microsoft.com/crm/2007/CoreTypes">0</AuthenticationType>" +
" <OrganizationName xmlns="http://schemas.microsoft.com/crm/2007/CoreTypes">"+"MicrosoftCRM"+"</OrganizationName>" +
" <CallerId xmlns="http://schemas.microsoft.com/crm/2007/CoreTypes">00000000-0000-0000-0000-000000000000</CallerId>" +
" </CrmAuthenticationToken>" +
" </soap:Header>" +
" <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>activitypointer</q1:EntityName>" +
" <q1:ColumnSet xsi:type="q1:ColumnSet">" +
" <q1:Attributes>" +
" <q1:Attribute>activityid</q1:Attribute>" +
" <q1:Attribute>activitytypecode</q1:Attribute>" +
" <q1:Attribute>prioritycode</q1:Attribute>" +
" <q1:Attribute>scheduledend</q1:Attribute>" +
" <q1:Attribute>subject</q1:Attribute>" +
" </q1:Attributes>" +
" </q1:ColumnSet>" +
" <q1:Distinct>false</q1:Distinct>" +
" <q1:PageInfo>" +
" <q1:PageNumber>"+"1"+"</q1:PageNumber>" +
" <q1:Count>"+"10"+"</q1:Count>" +
" </q1:PageInfo>" +
" <q1:Criteria>" +
" <q1:FilterOperator>And</q1:FilterOperator>" +
" <q1:Conditions>" +
" <q1:Condition>" +
" <q1:AttributeName>ownerid</q1:AttributeName>" +
" <q1:Operator>Equal</q1:Operator>" +
" <q1:Values>" +
" <q1:Value xsi:type="xsd:string">"+ UserId + "</q1:Value>" +
" </q1:Values>" +
" </q1:Condition>" +
" </q1:Conditions>" +
" </q1:Criteria>" +
" </query>" +
" </RetrieveMultiple>" +
" </soap:Body>" +
"</soap:Envelope>" +
"";
xmlHttpRequest = new XMLHttpRequest();

// Event to monitor the status of the request, asynchronously
xmlHttpRequest.onreadystatechange = ActivitiesRequestStatusProc;
//Fire the request asynchronously
xmlHttpRequest.open("POST","http://"+"localhost:5555"+crmServiceUrl.substring(7).substring(crmServiceUrl.substring(7).search("/")), true);
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);

// Send the soap request
xmlHttpRequest.send(xml);

}


// Monitor the status of the request
function ActivitiesRequestStatusProc()
{

if ( this.readyState == 4 ) // completed
{
if ( this.status == 200 ) // success
{
try
{
//This is the final XML data retreived as a result of the webservice call
var resultXml = this.responseXML;

}

}
}

}


You can download the GrapeCity widget’s accessing the MSCRM 4.0 data from here
http://www.grapecity.com/india/widgets

Creating your custom solution for MS CRM 4.0 - Programmatically fetch Isv Config and Sitemap for an organization

The CRM SDK provides ExportXmlRequest in order to export any customizations programatically. For example, the isv config for an organization can be exported as :
 CrmServiceRef.ExportXmlRequest exportRequest = new CrmServiceRef.ExportXmlRequest();

// Define the nodes to retrieve, the site map and the isv configuration.

exportRequest.ParameterXml = @"<importexportxml>
<entities></entities>
<nodes>
<node>isvconfig</node>
</nodes>
<securityroles></securityroles>
<settings></settings>
<workflows></workflows>
</importexportxml>";

// Execute the request.
CrmServiceRef.ExportXmlResponse isvconfigXml = (CrmServiceRef.ExportXmlResponse)crmService.Execute(exportRequest);
Alternatively, in order to read the contents of the ISV Config or Sitemap for any organization using code, the "organizationid" attribute for the organization class can be used as well. This is explained as follows:

In order to read the isv.config content :
1. Use the Discovery service to get the organization id for the organization.
2. Query the "isvconfig" entity (use QueryByAtrribute based on the attribute "organizationid") and fetch the "configxml" attribute. This returns the xml content for the isv.config file of a particular organization.

To access the sitemap xml content:
1. Use the Discovery service to get the organization id for the organization.
2. Fetch the organization instance using CrmService.Retrieve based on organization id.
3. Read the "sitemapxml" attribute of the organization retrieved.
The code sample here illustrates this:
CrmServiceRef.ColumnSet cols = new CrmServiceRef.ColumnSet();
cols.Attributes = new string[] { strpSITEMAPXML };

CrmServiceRef.organization org = (CrmServiceRef.organization)crmService.Retrieve(CrmServiceRef.EntityName.organization.ToString(),organizationid, cols);
CrmServiceRef.ColumnSet cols = new CrmServiceRef.ColumnSet();
if (org != null)
{
String strSitemapBackupPath = @"C:\Sitemap Backup\orgSitemap.xml";
// Create an instance of StreamWriter to write text to a file.
// The using statement also closes the StreamWriter.
using (StreamWriter sw = new StreamWriter(strSitemapBackupPath))
{
// Write the customization XML to the file
sw.Write(org.sitemapxml);
}
}

Monday, May 18, 2009

Troubleshooting the Reports Issues in MS CRM 4.0

This article helps you troubleshoot couple of Reporting Services issues that you may encounter while running reports in MS CRM 4.0.The problems may occur even when the MS CRM 4.0 installation was done successfully and no error message was shown for reporting services.

The most common error message that is generally displayed while running the report in MS CRM 4.0 is “The report cannot be displayed”.
This message is a very general one and does not point to any specific problem. There may be various causes for it and one of those is that SQL Server Reporting Services is not running on the server where SQL is installed.

To solve this problem, locate the service in Service Manager and check its status and startup type in the Properties.
If the status is ‘Stopped’, first make the startup type of the service as ‘Automatic’; this will ensure auto-start of the service whenever required, and then start the service.

If you are not able to start the SQL Server Reporting Services and you get Error 1503: The service did not respond to the start or control request in a timely fashion, verify that multiple application pools with similar names do not exist for Reports to run.

To do away with this problem, perform the following steps:
Make sure that you are logged in to the system with administrative rights.

1. Go to IIS Manager.( Open the Run window and type ‘inetmgr’)

2. Navigate to Websites->Default Web Sites and then locate the ‘Reports’ and ‘Report Server’ virtual directory.

3. Check the Properties of each virtual directory and make note of the name of the Application Pool specified.

4. Now, navigate to ‘Application Pools’ and check if there is any other application pool with similar name apart from the one identified in Step 4.If such application pool exists, delete it.

For example, if the Application Pool name specified in the properties of ‘Reports’ and ‘Reports Server’ Virtual directory is ‘ReportServer’ and there exists application pools ‘ReportsServer’ and ‘ReportServer$SQL’ under Application Pool, then you need to delete ‘ReportServer$SQLExpress’ application pool which is additional and not used by Reports to run.

Apart from the problem specified above many a times we face the problems in deploying the reports to MS CRM 4.0 after developing in Visual Studio.

Even when the report is working fine in Visual Studio (SQL server Business Intelligence Development Studio) 2005, it shows either of the following problems:
  1. Prompts for the SQL server username and password every time you wish to deploy it and does not accept any credentials.
  2. Shows an error message -Logon failed. Logon failure: unknown user name or bad password. (Exception from HRESULT: 0x8007052E) is shown in Report Manager while running the report.

To overcome this problem, you need to ensure that the same user credentials are used by the report server in execution account. Perform the following steps to fix the issue:

1. Open the Reporting Services Configuration Manger. You can navigate to Programs->SQL Server 2005->Configuration Tools-> Reporting Services Configuration.

2. Check the ‘Execution Account’ tab in Reporting Services Configuration Manager.

3. If the execution account is set, check the account name and password entered there. It should be same as the user credentials used for logging into the system. Enter the password if it is left blank or reset it, otherwise.