11 December 2008

CRM 4.0 Get attribute value from entity's GUID using JScript

Recently I have been asked many times about how to get attribute value from entity's GUID using JScript?
The following code demonstrate how to get a user's internalemailaddress by giving user's GUID.
*It has been modified to support multi-tenent deployment.


alert(GetAttributeValueFromID("systemuser", "09DF2AB7-E16D-DD11-88F3-0003FF884968", "internalemailaddress", "systemuserid"));

function GetAttributeValueFromID(sEntityName, sGUID, sAttributeName, sID)
{
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>"+sEntityName+"</q1:EntityName>" +
" <q1:ColumnSet xsi:type=\"q1:ColumnSet\">" +
" <q1:Attributes>" +
" <q1:Attribute>"+sAttributeName+"</q1:Attribute>" +
" </q1:Attributes>" +
" </q1:ColumnSet>" +
" <q1:Distinct>false</q1:Distinct>" +
" <q1:PageInfo>" +
" <q1:PageNumber>1</q1:PageNumber>" +
" <q1:Count>1</q1:Count>" +
" </q1:PageInfo>" +
" <q1:Criteria>" +
" <q1:FilterOperator>And</q1:FilterOperator>" +
" <q1:Conditions>" +
" <q1:Condition>" +
" <q1:AttributeName>"+sID+"</q1:AttributeName>" +
" <q1:Operator>Equal</q1:Operator>" +
" <q1:Values>" +
" <q1:Value xsi:type=\"xsd:string\">"+sGUID+"</q1:Value>" +
" </q1:Values>" +
" </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);

// retrieve response and find attribute value
var result = xmlHttpRequest.responseXML.selectSingleNode("//q1:" + sAttributeName);
if (result == null)
return "";
else
return result.text;
}

03 December 2008

Quick Find for Inactive Records

CRM Quick Find Active [Entity] view only return active records. It's a common requirement to return both Active/Inactive records. I use a easy way to allow Quick Find view to return both Active/Inactive records, here it is (unsupported!):

1. Export the entity's customization to a xml file;
2. Edit the xml file, search 'Quick Find Active' then locate to the right code piece;
3. Delete the filter which is:





4. Save the xml, import to CRM, then Publish.

Now the Quick Find view can return both Active and Inactive records. ;-)

28 November 2008

Code: CRM 4.0 Dashboard Intergate with FusionCharts

I have been asked many times through email/blog for sharing code about the CRM Dashboard with FusionChart integration. So I decide to extract some code and build a demo project to share, it's a simple work for demo, so please don't expect too much. :)

The demo dashboard supports:
• CRM 4.0, multi-tenants(one URL for different organizations);
• IFD and On-Premise deployment;
• CRM user security(users only see relevant data which their have privileges)

You may deploy the solution under ISV folder, you also need to change sitemap to show the Dashboard:




Download the solution

25 October 2008

Microsoft Outlook is not set as the default mail client?


Today I install CRM Outlook on my Vista(with Outlook 2007), it gives me a strange error:"Microsoft Outlook is not set as the default mail client. Please set Microsoft Outlook as the default mail client from Control Panel\Internet Options\Programs, and then re-run the check."

I'm sure the Outlook is my default mail client, just in case I reset it again. but the error still exist! I'm aware of CRM always query Registry to get information in such cases, so open the default application key: HKLM\SOFTWARE\Clients\Mail, the Default value is Windows Live Mail, so change it to Microsoft Outlook, the error gone!



19 October 2008

Ways to build up a CRM dashboard

There are servel ways to build up a dashboard to integrate with Dynamics CRM. Let's take a look(projects I have done):

1. Windows Sharepoint Service (WSS 3.0) + Office web part
In the previous version of WSS(v2.0), there is a free Office 2003 Add-in: Web Parts and Components, which is not available for WSS 3.0, however you can still add it manually. It's easy to use and config, but not flexible enough, it also requires the client PC has to have Office 2003/2007 software intsalled to see the graphic.



2. Windows Sharepoint Service (WSS3.0) + Reporting Service web part
You can also use Reporting Services Add-in in WSS 3.0, then create a rdl report and call it via Report Viewer web part. Because it's a rdl report, so it's very flexible .



3. ASP.Net + FusionCharts
FusionCharts is a flash charting component that can be used to render data-driven & animated charts for your web applications and presentations. It does provide a free version. So if you want to build up a flexable(let's say: multi-tenants etc.) dashboard to integrate with CRM, it's a good idea to build up your own ASP.Net page with FusionChart. I would say it won't disappoint you.



4. CRM Analytics Accelerator
This well-known add-ons is not available for CRM 4.0 at the moment, but it coming soon. More details see: CRM Accelerators – Part I – Analytics Accelerator



11 October 2008

MVP Award

I have been awarded Microsoft MVP - Dynamics CRM.
Thanks for Microsoft and my friends, colleagues and family. :)

See my MVP profile here: https://mvp.support.microsoft.com/profile/Jim.Wang

30 September 2008

Customize the crmForm

There are some useful crmForm customization skills which I want to share:

1. Change a filed label style

/* change new_button field label style */
if (crmForm.all.new_button != null)
{
var field = crmForm.all.new_button_c;
field.style.fontWeight = 'bold'; // change font to bold
field.style.fontSize = '12px'; // change font size
field.style.color = '#ff0000'; //change font color
}

2. Replace a field to a button, and attach the onclick() event

/* replace new_button_d to a button */
if (crmForm.all.new_button != null)
{
var field = crmForm.all.new_button_d;
var html = "<table border='0' cellspacing='0' cellpadding='0'><tr><img width='32' height='32' style='cursor:hand' src='/_imgs/ico_32_134.gif' alt='Click button' onclick='Button_OnClick()' /></tr></table>";
field.innerHTML = html;
}
Button_OnClick = function()
{
alert("button clicked!");
}

3. Replace a field to a lable (use replaceNode())

/* replace new_button_d to a label */
if (crmForm.all.new_button != null)
{
var html = document.createElement( "<TD id='new_button_d'>");
html.innerText = "this is a lable";
crmForm.all.new_button_d.replaceNode(buttonText);
}

4. Append text under a field (you don't need to create an attribute for that)

/* append text under new_button */
if(crmForm.all.new_button != null)
{
var html= document.createElement( "<LABEL>");
html.innerText = "this is a text field";
crmForm.all.new_button.parentNode.appendChild(html);
}

18 September 2008

Double Click EmailAddress to Open in Outlook

There is a question on Microsoft CRM Forum asking how to: Double Click EmailAddress to Open in Outlook

I think it's worth to bring it here because it's also demonstrate how to send parameters in attachEvent method, so here is the answer:


/* Double Click EmailAddress to Open in Outlook, put into entity.OnLoad */
function CreateEmail(emailAddress)
{
return function()
{
if (emailAddress != null && emailAddress.value.length > 0)
{
window.navigate("mailto:" + emailAddress.value);
}
}
}

crmForm.all.emailaddress1.attachEvent('ondblclick', CreateEmail(crmForm.all.emailaddress1));
crmForm.all.emailaddress2.attachEvent('ondblclick', CreateEmail(crmForm.all.emailaddress2));
crmForm.all.emailaddress3.attachEvent('ondblclick', CreateEmail(crmForm.all.emailaddress3));

06 September 2008

FilteredView and CrmImpersonator?!

I was curious about how to use FilteredView in the CrmImpersnator class for a IFD(Internet-Facing Deployment) solution? Thanks to David Jennaway who gave me a clue.

Ok, the reason for that is because I'm building a CRM dashboard for an On-Premise/IFD CRM deployment. In order to use the Web services from an APSX page, I have to use the Microsoft.Crm.Sdk.CrmImpersonator class, however by doing that it's not possible to get relevant data from the FilteredView, because the CrmImpersnator() will authenticate as [NT AUTHORITY\SYSTEM] account(which is a system account in CRM).

The solution is to use SQL Execute As statement, see the sample code below:


using (new CrmImpersonator())
{
........
string username = "domain\\Guest";
string sqlQuery = "SELECT name FROM FilteredAccount";
string queryString = "GRANT IMPERSONATE ON USER::[NT AUTHORITY\\SYSTEM] TO [" + username + "] EXECUTE AS USER='" + username + "' " + sqlQuery + " REVERT";

........
}

In this example, you have to give user the Impersonate permission first, then use Execute As statement to impersonate the user.
After running the code, if you have a look the CRM database, under the Security\Users folder, the impersonated user account is added in the list, and it grant the 'Impersonate' permission to user [NT AUTHORITY\SYSTEM], see picture below:



By using this technic, you also need to map the user [NT AUTHORITY\SYSTEM] to the CRM database(e.g: Contoso_MSCRM).

24 August 2008

Change CRM 4.0 website port number after installation


Microsoft has a KB to describe how to update the Microsoft Dynamics CRM Web site port after you install Microsoft Dynamics CRM 4.0

Today I had to change the port number from 80 to 5555, however after following those steps, I can't run CRM reports. In the event log, it says: Error 19968 Web service request ListExtensions to Report Server http://crm/ReportServer/ReportService2005.asmx failed. Error: Unable to connect to the remote server

I have noticed that the virtual directory: ReportServer and Reports are located under the CRM website, so these config information needs change too.

There are extra two steps if your SRS is running under the CRM website:

Regedit:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSCRM\SQLRSServerURL

Deployment Manager:

Disable Organization >> Edit Organization >> change the SRS Url >> Enable Organization.

27 July 2008

CRM 4.0 upgrade: There is already an object named 'AttributeTypes' in the database.

This week I was helping a customer to upgrade their CRM 3.0 system to CRM 4.0.
The upgrade pre-check looks fine with all passed, no errors, no warnings.
But during the upgrade process, it first alert an error regarding C360, then I fix the problem, click the "Retry" button, it then generates another error:

" Microsoft.Crm.Setup.Server.InstallDatabaseAction failed.
System.Data.SqlClient.SqlException: There is already an object named 'AttributeTypes' in the database. "

That's very strange. But finally I found it's a design lack of upgrade process: The 'Retry' doesn't actually do 'Retry'!
So I had to:
1. Stop the upgrade process and uninstall CRM 4.0;
2. Restore CRM 3.0 databases, then install CRM 3.0 with existing databases;
3. Fix the problem which generated the first error;
4. Re-run the CRM 4.0 upgrade.

I did know there are some complains about C360 add-ons stop the CRM upgrade process, these could be the reasons:
1. The C360 uninstall doesn't do a clean uninstall all the time, so you may still see C360 icons on the CRM form, but no function;
2. Some C360 products create tables inside the CRM database which is not a recommended method, and again once you uninstall the product, the tables are still there.
3. C360 has a cleanup tool which does cleanup the remained infomation in the onLoad, onSave, and isv.cofig.xml etc. However, it doesn't cleanup the database. And the tool is hard to find on C360 website.

C360 provides some great add-ons for MSCRM, but we wish it could be better!

20 July 2008

CRM 4.0 E-mail access type bulk setting tool


(Update: You can use CRM 4.0 Workflow to do it as well, it's up to you to choose which one you feel comfortable.)
Microsoft Dynamics CRM 4.0 doesn't provide a tool for CRM User's email access type bulk setting. So I write this application to helping CRM Administrators for this purpose. It takes me 2 hours work so don't expect too much, however it does work. ;-)


Usage:
1. Launch the application, type in the following information:
CRM Server(the CRM discover server address): e.g http://localhost:5555
Organisation(the Organisation's name, not Friendly name): e.g AdventureWorksCycle
SQL Server: e.g localhost
Database: e.g Adventure_Works_Cycle_MSCRM
CRM User: e.g CRMAdmin
CRM User's Password

2. Click the 'Load' button, it will list all enabled CRM users whom belonging to this organisation with their email settings.

3. Select users your want to modify, then select Incoming E-mail and Outgoing E-mail setting from the picklist. If you don't want to change both of those, then just select blank in the picklist.

4. Click the 'Update' button, it will update user settings you selected.

Download it here: http://code.msdn.microsoft.com/crm


Enjoy it!

Cheers,
Jim Wang
http://jianwang.blogspot.com
http://mscrm.cnblogs.com

13 July 2008

CRM 4.0 E-Mail Router for hosted Exchange

I have a client who uses a hosted Exchange for their email system (also hosted AD) and my client uses CRM 4.0 On-Premise edition. So the CRM E-Mail Router solution is:
  • Outgoing: local SMTP service, use local system account
  • Incoming: hosted Exchange, use a service account which can access the remote Exchange mailbox
  • Both Outgoing and Incoming are configured to use Email Router

When test the connection, the outgoing STMP works fine, however, the incoming Exchange get the following error:
"Incoming Status: Failure - The remote Microsoft Exchange e-mail server returned the error "(404) Not Found". This user or queue does not have a mailbox. Create a mailbox and try again. The remote server returned an error: (404) Not Found. "

It is strange because I'm sure the user has mailbox! Thanks for Tony Iadarola who is a senior engineer works for the hosting company , after analyzing the Exchange server logs, he pointed out that the CRM E-Mail router service try to query the mailbox by using the 'half' SMTP address!( which is not recommend, MS recommend either SamAccount or Legacy DN, or mailbox GUID is used)

e.g.: In our case, we have a test user in CRM, the user's SamAccount name is: TU341-NBG125, SMTP mailbox: TUser@domain.com which is the user's primary email address in CRM. The CRM E-Mail Router service should be making a request to Exchange mail server for the Test users mailbox using http://ExchangeMailServer/Exchange/TU341-NBG125 instead it is using http://ExchangeMailServer/Exchange/TUser

If I change the user's primary email address to TU341-NBG125@domain.com, the problem will disappear! However the email address doesn't mean anything to customers, so I can't change the email address.

Finally I found a workaround: edit ..\Microsoft CRM Email\Service\Microsoft.Crm.Tools.EmailAgent.xml
Search the test user section(within the tag: <ProviderConfiguration>), edit it's <EmailAddress> tag, change the email address from TUser@domain.com to TU341-NBG125@domain.com, then save the xml file, modify it's property to Read-Only, restart the CRM E-mail Router Service.(You have to make the file read-only first, otherwise the service will re-write the <EmailAddress> tag by using the user's primary email address)

The problem is solved, all users still use their normal email address, and the CRM E-Mail Router is able to create an Email activity for incoming emails with correct email address. However I think the CRM E-mail Router should be improved by query mailbox via SAM, instead of using SMTP address which may not unique for a hosted environment.



16/07/2008, add to my post: the service account needs to have permission to access 'Root' and 'Inbox' folder of user's mailbox. e.g. if the service account has such permission, it can find user's mailbox by using the 'half' SMTP address (http://ExchangeMailServer/Exchange/TUser ). However it's might be a problem for a hosted company who doesn't want to grand the permission for the service account.

02 July 2008

CRM 4.0: Use JavaScript execute/call/launch CRM Workflow

I have a question from my colleague: How to use JavaScript execute workflow in CRM 4.0? The question also repeats very often in CRM Forums, no answer so far.

In CRM 3.0, Mitch Milam has described how to Launching a Workflow Rule from JavaScript, it works great. However, in CRM 4.0, the class: ExecuteWFProcessRequest has been deprecated, so it won’t work in CRM 4.0. Although there are many ways to launch a workflow, if you want to run it through JavaScript, here’s the trick:

/* the function */
ExecuteWorkflow = function(entityId, workflowId)
{
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>" +
" <Execute xmlns=\"http://schemas.microsoft.com/crm/2007/WebServices\">" +
" <Request xsi:type=\"ExecuteWorkflowRequest\">" +
" <EntityId>" + entityId + "</EntityId>" +
" <WorkflowId>" + workflowId + "</WorkflowId>" +
" </Request>" +
" </Execute>" +
" </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/Execute");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
xmlHttpRequest.send(xml);
var resultXml = xmlHttpRequest.responseXML;
return(resultXml.xml);
}

/* call */
var theWorkflowId = "3FD2DD58-4708-43D7-A21B-F0F90A0AA9F2"; //change to your workflow Id
ExecuteWorkflow(crmForm.ObjectId, theWorkflowId);

Enjoy coding! :)

01 July 2008

Failure: The full-text indexes on the Microsoft Dynamics CRM database are not consistent with Microsoft Dynamics CRM 3.0 full-text indexes

Last week I had a chance to help a London company to upgrade CRM 3.0 to 4.0. As you can see there are some warnings and error, I can easily fix all warnings instead of ignore them(in fact, most warnings are safe to ignored). The error is an interesting one, regarding Microsoft KB(927226), it’s a custom indexes error, when I check the installation log, it tells me the table name: DocumentIndex



C:\Documents and Settings\user\Application Data\Microsoft\Mscrm\Logs\crm40svrsetup.log
13:51:00| Info| The following full-text indexes are not consistent with Microsoft Dynamics CRM 3.0 full-text indexes:
13:51:00| Info| tableowner: dbo
13:51:00| Info| tablename: documentindex
13:51:00| Info| fulltextkeyindexname: cndx_primarykey_documentindex
13:51:00| Error| remark: modified full-text index
13:51:00| Error|
13:51:00| Error| The full-text indexes on the Microsoft Dynamics CRM database are not consistent with Microsoft Dynamics CRM 3.0 full-text indexes.
13:51:00| Error| Check SqlFullTextIndexValidator : Failure: The full-text indexes on the Microsoft Dynamics CRM database are not consistent with Microsoft Dynamics CRM 3.0 full-text indexes.


I saw another post here which has the same error message. However I found that my case is slightly different, in fact I can’t see any full-text catalog against this table. So I just create a new ftcat_documentindex against DocumentIndex table(after compare with my VPC), then the error gone. :)
 
The full-text catalog is call: ftcat_documentindex, see below:

21 June 2008

CRM Mobile for Windows Mobile

CRM Mobile is always an interesting topic. Some people love it, some hate it.

I built up a Windows Mobile software company about 4 years ago when I study my master course in the University College London. It's quite successful and I'm proud of it so much. And I am a Windows Mobile Specialist since year 2005, in the same year I started working on Microsoft CRM, now I am a Microsoft Certificate Business Management Solutions Professional. So I am an expert on both products, quite comfortable. I'd like to talk about my thoughts about CRM Mobile in this post.

Microsoft hasn’t release CRM 4.0 Mobile software which I don't think they will in a short time. As you may know that Microsoft has a product called: Dynamics Mobile which supports Dynamics AX and NAV, and the good news is: it will support CRM 5 too. With Dynamics Mobile framework, ISVs can build up more and better Windows Mobile software for Microsoft Dynamics.

CWR Mobility is the most popular CRM Mobile provider, even C360 uses (and sales) CWR’s product: I think they are partners. I have to say this software is one of the most complicated CRM Add-Ons. So how good is it? I can tell you my opinion.

The Environment:

I have a fresh CRM 4.0 installed on a Windows Server 2003 R2, with SQL Server 2005(it can be use Virtual PC which needs setup ActiveSync with your mobile device).
I also have a Windows Mobile device (Orange HTC Touch, OS: WM6.0) which has been recently upgraded to Windows Mobile 6.1(WM6.1, the latest version of Windows Mobile OS)


The Server Side:

First you need to download Mobile CRM software from CWR website, install it on the server (it doesn't have to be installed on the CRM server; however my environment is all-in-one box). The installation process will create a Virtual Directory under Microsoft CRM folder on IIS, and a CWR Mobile CRM Website on IIS. It also creates two shortcuts: Mobile CRM Server Configurator and Mobile CRM Server Administrator

You need to run Mobile CRM Server Configurator and select your Organisation and License file (I have an Organisation call Contoso which can use a demo license which also downloadable from CWR website). The configuration process will create two databases just like CRM's: one Config database and one Organisation database.

If everything going well, open CRM, navigate to [Settings] section, you may see a new link call [CWR Mobile CRM] (because the configuration process also modified the CRM Sitemap), then you may import a Profile (a sample Sales profile can be found on CWR website). After that, you can add Users with this Profile.


The Windows Mobile Side:

You can either install Mobile client software through your PC (CWRDesktopInstaller.exe) or through the Device (CWRPDAInstaller.exe), the installation file can be found in the CWR Mobile CRM installation folder. You may follow the steps when first time running the software, it will initialize CRM Mobile on this mobile device and creates a CF database (for syncing data) on it.


Some problems I know:
a. There is an error: "Error while updating Sitemap" when run Mobile CRM Server Configurator;
b. The alert/error message doesn't make much sense, e.g. "......, see log file for details";
c. The interface of mobile device doesn't design very well.

As I said early, this software is one of the most complicated CRM Add-Ons, which I can see there are some problems, hopefully CWR will fix it soon. Frankly, I know the .Net CF, I think Microsoft should put more money and top exports on it. Mobile device isn't the same as PC(which is almost universal), it is quite hardware specialised and it has a long way to go. So if you are going to deploy CRM Mobile for your company, you may need an expert.

19 May 2008

Use Shared Data Source for custom CRM report

Recently I have been asked to modify some reports. In my current project we have 4 environments (Development, Training, UAT, and Production) and we have custom reports for each environment. The reports for each environment are different because the data source is different. So the deploy administrator complain about numbers of report. They come and ask me if there is a way to have one report for all environments.

So this is how I managed to do it:

If you have a look the CRM reports in report manager, there is a Data Source call: MSCRM_DataSource
I have noticed that actually all CRM reports use this data source, so why can’t I use it for our custom reports? I think it shouldn’t be a problem.



In Visual Studio, open the report project, Add a Shared Data Sources call: MSCRM_DataSource, it is important to keep it as same as CRM’s.
The connection string just uses the development environment, e.g.: Data Source=crm;Initial Catalog=org_MSCRM



Once finish it, upload the report rdl file by Report Manager, e.g http://crm/reports
Notice that you shouldn't upload it from CRM directly because CRM doesn’t support this technique. (You may get an error if you do it from CRM: Object reference not set to an instance of an object.)

So do it from Report Manager. After I upload the report, it appears in CRM Report area. That's good, however I can't edit the report! The error message is: String was not recognized as a valid DateTime.

Microsoft actually has a KB (920125) for this error: http://support.microsoft.com/kb/920125. The KB says: "This problem occurs because the Microsoft SQL Server Reporting Services report contains functionalities that are specific to Microsoft Dynamics CRM. For example, the Microsoft Dynamics CRM Pre-filtering functionality is specific to Microsoft Dynamics CRM."

Ok, I see. I remember the default CRM pre-filtering function is 'modified on' which is a datetime data type. If you upload the report from CRM, the report property may different with what you do from Report Manager.

So where is the pre-filtering values saved? If you open SQL Server Management Studio, expend ReportServer database, take a look the Catalog table. All reports information is saved in this table. I noticed there is a property element for all CRM reports: <ModifiedOnDateTime>2008-03-29T17:59:15</ModifiedOnDateTime>

It is a datetime data type and it doesn’t exist in the custom report! Also I noticed that the <OriginalName> element needs to be added as well to make the CRM Download Report function working properly.

So, this is how I add these elements into the custom report, the SQL query needs to run against the ReportServer database:



DECLARE @ReportName AS varchar(50)
SET @ReportName = 'My CRM Report' -- Change to the report name

DECLARE @ModifiedDate AS datetime
SET @ModifiedDate =(SELECT ModifiedDate FROM Catalog WHERE Name = @ReportName)

UPDATE Catalog
SET Property = REPLACE(CONVERT(varchar(MAX), Property), '</Properties>',
'<ModifiedOnDateTime>'+ CONVERT(char(19), @ModifiedDate, 126) +'</ModifiedOnDateTime><OriginalName>'+ @ReportName +'.rdl</OriginalName></Properties>')
WHERE (Name = @ReportName)



It works like a dream after run a iisreset on the CRM server. Now I can edit the report without any problem!

By using this technique the advantage is: all environments will share the default CRM data source, developers don’t have to create different copies of report for each environment.

18 May 2008

The mysterious CRM Lookup (III)

5. Let's have a look another common used filtered lookup example: I want the regarding field is set to open cases.

a. CRM 3.0

/* set the regarding to open case : Form.onLoad() */
crmForm.all.regardingobjectid.lookuptypes = "112";
crmForm.all.regardingobjectid.lookuptypeIcons = "/_imgs/ico_16_112.gif";

/* only show the active cases : Form.onLoad() */
if (crmForm.ObjectId != null)
{
crmForm.all.regardingobjectid.lookupbrowse = 1;
crmForm.all.regardingobjectid.additionalparams = "fetchXml="
+ "<fetch mapping='logical'><entity name='incident'><all-attributes /><filter>"
+ "<condition attribute='statecode' operator='eq' value='0' />"
+ "</filter></entity></fetch>";
}


b. CRM 4.0

In 4.0, the above feature is not supported anymore which means not working any more, but we can still use the method which mentioned in my previous post. Set 'statecode' as a find column of the Case Lookup View, then add the following code to the entity's onLoad():

/* set the regarding to open case : Form.onLoad() */
crmForm.all.regardingobjectid.lookuptypes = "112";
crmForm.all.regardingobjectid.lookuptypeIcons = "/_imgs/ico_16_112.gif";
crmForm.all.regardingobjectid.additionalparams = 'search=Active';


All good, now we are changing the requirement to: I want the regarding field is set to open cases which owned by the current user!

Ok, this simple and clean approach will not work for this a little complicated search. We need to build up a fetch query first. It's easy to use advanced find:

Look for: cases
-Status Equals Active
-Owner Equals Current User'

And then click Find, we get results.

Now, how can we get benefit from this Advanced Find and get the FetchXml statements from the query? Thanks Ronald Lemmen who first find a trick, in the result page, paste it in the IE Address Bar:

javascript:prompt("", resultRender.FetchXml.value);

Then you will get a prompt window, the value is the FetchXml statements which we need.

<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false"><entity name="incident"><attribute name="title"/><attribute name="ticketnumber"/><attribute name="createdon"/><attribute name="incidentid"/><order attribute="title" descending="false"/><filter type="and"><condition attribute="statecode" operator="eq" value="0"/><condition attribute="ownerid" operator="eq-userid"/></filter></entity></fetch>

Thanks Adi Katz who found a brilliant way to do it, and George modified it slightly. Basicly it overwrites the code-behind function, which I think it could be a risk. However so far so good after three months since it has been first released, I will post updates if it occurs any issue.(There is a supported filtered lookup product from Michael Höhne which is not free but great product.)

You need to modify lookupsingle.aspx file in the path \CRMWeb\_controls\lookup\lookupsingle.aspx
Add the following codes:
 
<script runat="server">
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
crmGrid.PreRender += new EventHandler(crmGrid_PreRender);
}
void crmGrid_PreRender(object sender, EventArgs e)
{
if (crmGrid.Parameters["search"] != null && crmGrid.Parameters["search"].StartsWith("<fetch"))
{
crmGrid.Parameters.Add("fetchxml", crmGrid.Parameters["search"]);
crmGrid.Parameters.Remove("searchvalue");
this._showNewButton = false;
}
}
</script>


And then you can filter it from entity's onLoad() just like what we did in CRM 3.0. Notice that we have pasted the FetchXml statements below(fetchStr).

/* set the regarding to open case which owned by current user : Form.onLoad() */
crmForm.all.regardingobjectid.lookuptypes = "112";
crmForm.all.regardingobjectid.lookuptypeIcons = "/_imgs/ico_16_112.gif";

var fetchStr = "<fetch version="1.0" output-format="xml-platform" mapping="logical" distinct="false"><entity name="incident"><attribute name="title"/><attribute name="ticketnumber"/><attribute name="createdon"/><attribute name="incidentid"/><order attribute="title" descending="false"/><filter type="and"><condition attribute="statecode" operator="eq" value="0"/><condition attribute="ownerid" operator="eq-userid"/></filter></entity></fetch>";
crmForm.all.regardingobjectid.lookupbrowse = 1;
crmForm.all.regardingobjectid.additionalparams = "search=" + fetchStr;


That's it, now we reach the end. :)

11 May 2008

The mysterious CRM Lookup (II)

4. Now, how can we set/filter the lookup content? Because CRM3 and CRM4 are so different in lookup, so the solution is different. Let’s see a common example: In the Account record, only show the account owned contacts in the Primary Contact (primarycontactid) lookup.

a. CRM 3.0


/* CRM 3.0: only show account owned contacts in the primarycontactid lookup : Form.onLoad() */
if (crmForm.FormType == 2 && crmForm.ObjectId != null)
{
crmForm.all.primarycontactid.lookupbrowse = 1;
crmForm.all.primarycontactid.additionalparams = "fetchXml="
+ "<fetch mapping='logical'><entity name='contact'><all-attributes /><filter>"
+ "<condition attribute='accountid' operator='eq' value='" + crmForm.ObjectId + "' />"
+ "</filter></entity></fetch>";
}


b. CRM 4.0

As far as I know, there are two unsupported ways to do that. Because we don't need a complicated fetchxml in this case, we so could use the first approach:

1. Customize Contact entity, open Contacts Lookup View, click 'Add Find Column', add the Parent Customer (parentcustomerid), save and publish it.
2. Customize Account entity, put the following code into Form.onLoad() :


/* CRM 4.0: only show account owned contacts in the primarycontactid lookup : Form.onLoad() */
if (crmForm.FormType == 2 && crmForm.ObjectId != null)
{
var name = crmForm.all.name.DataValue;
crmForm.all.primarycontactid.additionalparams = 'search=' + name;
}

It's a nice approach which used the 'search' feature of the CRM lookup. When this parameter is specified it defaults the search string in the lookup dialog and applies the search when the dialog is opened.


It's great, now let's change the requirement:
How about: Only show the Parent Account (parentaccountid) owned contacts in the Primary Contact (primarycontactid) lookup.

We still need to repeat step (1), and then in the step (2):


/* CRM 4.0: only show parent account owned contacts in the primarycontactid lookup: Form.onLoad() */
FilterLookup = function(source, target)
{
if (IsNull(source) IsNull(target)) { return; }
var name = IsNull(source.DataValue) ? '' : source.DataValue[0].name;
target.additionalparams = 'search=' + name;
}

Also, we need to put the following code into parentaccountid.onChange():


/* CRM 4.0: only show parent account owned contacts in the primarycontactid lookup : parentaccountid.onChange() */
FilterLookup(crmForm.all.parentaccountid, crmForm.all.primarycontactid);


It's great too, now how about we add another requirement on the above example:
The Primary Contact (primarycontactid) should be automatically selected when this contact is the primary contact of the selected Parent Account (parentaccountid).

Although we could do it through AJAX, we can also do it through CRM 4.0 lookup field automatic resolutions technique. Thanks for Adi Katz, let's start from begin:

(1) Turn off the Parent Account (parentaccountid) "automatic resolutions in field" feature by double click the field on the Form.
(2) Put the following codes in Account.onLoad():


function OnAfterAccountSelect()
{
var contactLookup = crmForm.all.primarycontactid;
if( contactLookup.DataValue != null ) {return;}

contactLookup.AutoResolve = 1;
var accountLookup = crmForm.all.parentaccountid;
primaryContact = accountLookup.items[0].keyValues.primarycontactid;

contactLookup.SetFocus();
contactDiv = document.all.primarycontactid_d.getElementsByTagName("DIV")[0];
contactDiv.innerText = primaryContact.value;
contactLookup.Lookup( true , true , primaryContact.value , true );
}

function OnCrmPageLoad()
{
crmForm.all.parentaccountid.attachEvent( "onafterselect" , OnAfterAccountSelect );
}

OnCrmPageLoad();


The second approach which can deal with some complicated conditions will be introduced in the next post. :)

04 May 2008

The mysterious CRM Lookup (I)

1. As many of you already know that the CRM lookup field saves the GUID of the related entity. Let's have a deep view of the lookup field. When you create a relationship between two entities, one entity can reference the other entity through a lookup field. However it's not just create one attribute in the database, it means although you can only see one relationship created in CRM interface, there are some invisible attributes for internal/customizer use. Let's see an example, we know that we can reference some values from a lookup fields:


crmForm.all.regardingobjectid.DataValue[0].id; // The GUID of the lookup.
crmForm.all.regardingobjectid.DataValue[0].name; // The text value of the lookup.
crmForm.all.regardingobjectid.DataValue[0].typename; // The entity type name.


But how CRM get those values? Actually when user open a CRM record, those attributes are downloaded from CRM database to the 'containers' which can be seen from entity's customization.xml, it's a complex structure which I don't want to explain in this post. So when you create a relationship between two entities, the CRM system will create more than 2 'containers'(attributes) in the database to keep other information about the lookup field(e.g. id, typename etc).

2. Set the default value for a lookup field, let's take a look at the special lookup field again: regardingobjectid
In many cases, the regardingobjectid is default to Account, but how can we change the default value to Contact?

In the onLoad() event, you can set the default attributes by using setAttribute method which is unsupported by Microsoft, however it's a standard XML DOM method.

Let's see some examples:


crmForm.all.regardingobjectid.setAttribute("lookuptypes", "1,2"); //only show account and contact
crmForm.all.regardingobjectid.setAttribute("lookuptypeIcons", "/_imgs/ico_16_1.gif :/_imgs/ico_16_2.gif"); //set the icons
crmForm.all.regardingobjectid.setAttribute("defaulttype", "2"); //default to contact

Instead of using setAttribute method, you can also use CRM method directly:

crmForm.all.regardingobjectid.lookuptypes = "1,2";
crmForm.all.regardingobjectid.lookuptypeIcons = "/_imgs/ico_16_1.gif:/_imgs/ico_16_2.gif";
crmForm.all.regardingobjectid.defaulttype = "2";



3. If you have a look of the URL when you open a lookup window, you may see how CRM calls a lookup:



/lookupsingle.aspx?class=ActivityRegarding&objecttypes=1,2,3,4&browse=0&ShowNewButton=1&ShowPropButton=1&DefaultType=0

lookupsingle.aspx has some parameters which can be referenced by our developers, those parameters are:

Objecttypes : Entity code, e.g. Objecttypes = "1, 2" //show account and contact
DefaultType : the default lookup entity, e.g. DefaultType = "2" //default to contact
Browse : bool, 0 = show the "Look for" bar; 1 = browse model, hide the "Look for " bar.
ShowNewButton : bool, 0 = hide the "New" button; 1 = show the "New" button.
ShowPropButton : bool, 0 = hide the "Properties" button; 1 = show the "Properties" button.

In an IFRAME or a ISV solution, if you don't want users to see the 'New' Button, you can just set the URL to:
/lookupsingle.aspx?class=ActivityRegarding&objecttypes=1,2,3,4&browse=0&ShowNewButton=0&ShowPropButton=1&DefaultType=0

It's cool so far! But how about if want to hide the 'New' button in CRM?
You can't just say: crmForm.all.regardingobjectid.ShowNewButton = 0; it doesn't work. But what you can do is in the onLoad() event, use attachEvent method to attach a setadditionalparams event for the regardingobjectid. Again, those methods are all unsupported customisations, however, those are widely used in the Web development.

/*
Function: show/hide the 'New' button of lookup
bShow = 0 : hide the New Button
bShow = 1 : show the New Buton
*/
function NewButton(bShow)
{
return function()
{
crmForm.all.regardingobjectid.AddParam("ShowNewButton", bShow);
}
}
crmForm.all.regardingobjectid.attachEvent("setadditionalparams",NewButton(0));

20 April 2008

Microsoft Dynamics Sure Step Methodology

I'm one of the first students in UK of the Microsoft Dynamics Sure Step Methodology, the instructor was Mr. Shaun Letley who is a brilliant guy works for Microsoft UK. So in the last year I had his Sure Step training course in Reading, United Kingdom. The course was very helpful; it does help me to have a better understanding of the project methodology.

The Microsoft Dynamics Sure Step Methodology is a comprehensive implementation methodology describing the processes and disciplines necessary to implement Microsoft Dynamics AX, Microsoft Dynamics CRM, Microsoft Dynamics GP, Microsoft Dynamics NAV and Microsoft Dynamics SL. The implementation of all these products has many similarities which is why the methodology has been structured as a general methodology with a product specific layer for each product. The general content is mostly the prescriptive phase by phase, activity by activity descriptions on how to carry out the implementation plus the Project Management discipline. The product specific content is typically represented as tools, templates and hyperlinks to product specific materials found outside the methodology.

The methodology is actually good for everyone in the project, not only for project manager/functional manager. As I'm a technical consultant, my major job is consultant/development a solution. I can still learn some helpful tips to make sure the project is going well. Also, the Sure Step provides a full process for both big and small projects so don't make misunderstanding about it. The Sure Step is actually suitable for all dynamics projects. It also provides a lot of templates which could be use in your project! Of cause you can still using your own templates with Sure Steps, it's flexible and powerful.

I strongly recommend the Sure Step methodology to all Dynamics people because it does make sure your project is well organised, delivery on time and easily maintained.

Take this link on PartnerSource to see the full details about Microsoft Dynamics Sure Step Methodology.

19 April 2008

"You have exceeded the maximum number of 2000 characters in this field; it will be truncated."


When you create a campaign in CRM 4.0, the Office field(attribute name: objective) has a 2,000 characters limit. How to get rid of the limitation?

It's a system field which you can't change its MaxLength from CRM customisation interface, however, you could make change from the customisation.xml file.

What you need to do is: export the campaign entity as a xml file, open the field, search "objective", you may find it's property: 2000, change it as you like(In CRM 4.0 the ntext data type has the MaxLength limitation up to 100,000 which is much better than CRM 3.0). Then save the file, import into CRM, and publish it. It works as you wish.

Although it's an unsupported customisation, since CRM 4.0 does allow you extend the field length(using DMM), so I think it's safe and nearly supported. :)

By the way, this technique can be used in many places, not just for this attribute. It also works in CRM 3.0.

08 April 2008

How to format a number field(integer) without showing commas?

There is an interesting topic on the CRM Forum about how to format an integer field without commas. For example, if you type: 123456 in an integer field, you may see 123,456 once it loses focus. CRM automatically add a ',' between 3 numbers. There's a global setting to get rid of it, however, it will get rid of all integer field format. How about if you just want to remove the format for just one field?

As you may know, MSCRM uses htc files to format the different type of input fields as same as email address etc, see my another post.

If you want to get the value of an attribute, in CRM we use: DataValue, e.g: crmForm.all.new_number.DataValue;
Notice that the DataValue is the real data saved in the database.

However, if you want to get the formatted value, you may use this:
crmForm.all.new_number.value;

So the trick is give the DataValue overwrites the value property.

Put the following code into the entity's onLoad() event, and the same code puts into the field's onChange() event.

if(crmForm.all.new_number != null && crmForm.all.new_number.DataValue != null)
{
crmForm.all.new_number.value = crmForm.all.new_number.DataValue;
}

Enjoy it!

05 April 2008

Add client-side Microsoft Office Word Spell Checker in CRM Email entity


There are many CRM spell checker add-ons on the Internet, most of those are free, but they are all server-side spell checkers.

Today let's introduce the new client-side Word Spell Checker which uses Microsoft Office Word spell check function. It does give users a familiar feeling and larger database. It also uses Word's spell check settings. So I think users will love it. It has been used for one of our clients live environment for half year, and it has great feedbacks.

As you can see, the client machine must have Microsoft Word installed for using this technique.(My clients are using Microsoft Office Word 2003, I'm not sure if it's compatible with Word 2007, please feel free to test this solution)

The file needs to be modified is: CRMWeb\Activities\email\edit.aspx

You may already recognized that it's an unsupported customization, so please make backups just in case your customization could damage the system and also could be overwrite by Hotfixs/Rollups.

Just simply add a function:


/*  Microsoft Office Word Spelling Check*/
 
function SpellCheck(field)
{
window.frames[field].document.execCommand("Copy");
 textRange = window.frames[field].document.body.createTextRange();
 textRange.execCommand("Copy");
 
 try
 {
   var oWord = new ActiveXObject("Word.Application");
   oWord.Visible = false;
   oWord.Documents.Add();
   oWord.Top = -2000;
   oWord.Selection.Paste();
   oWord.ActiveDocument.CheckSpelling();
   oWord.Selection.WholeStory();
   oWord.Selection.Copy();
   oWord.ActiveDocument.Close(0);
   window.frames[field].focus();
   window.frames[field].document.execCommand("SelectAll");
   window.frames[field].document.execCommand("Paste");
 }
 catch(err)
 {
   alert("Error loading Microsoft Word Spelling Check: " + err);
 }
 finally
 {
   oWord.Quit(0);
 }
 
 alert("Spelling Check Finished!");
 
}

You also need to modify the isv.config.xml fie to call this function:


<Entity name="email">
<ToolBar ValidForCreate="1" ValidForUpdate="1">
<Button Title="Spell Check" ToolTip="Spell Check" Icon="/_imgs/ico_18_home.gif" JavaScript="SpellCheck('descriptionIFrame');" />
</ToolBar>
</Entity>

What the function does is copy the text user typed in the Email body(descriptionIFrame), and paste it to a Word document, the document is unseenable because it has been moved out of the screen(oWord.Top = -2000) , then the function call Word.CheckSpelling() method to check the text just pasted. After correct all words, it will paste the whole text back to the Email body, and close the Word process.

08 March 2008

Mail Merge function for custom entities upgraded from CRM 3.0

CRM 4.0 has a great function which provides the Mail Merge function for custom entities.

However, it only seems to work for those entities you created in CRM 4.0, it doesn't work for entities which upgraded from CRM 3.0.


There's a new flag in CRM 4.0 customizations.xml, calls: IsMailMergeEnabled which wasn't exist on CRM 3.0 customizations.xml. When we upgrade CRM from 3.0 to 4.0, it's supposed to be upgraded as well, but it doesn't.


I write a small command tool to do so, how to use the tool?

1. Export all entities from CRM 4.0, we may just call it: customizations.xml

2. Run the command line tool:

CrmTool.exe customizations.xml

3. What it does just loop all custom entities, and add the flag onto it. It saves time if you had lots of custom entities just as I do.


Enjoy it!


Download CrmTool.exe from MSDN


23 February 2008

Show how many activities/history associated with a record


Sometimes we want to see how many activities/history associated with a record, it's nice to have a number just like the Outlook Inbox.

Here's the code, it works fine on both CRM 3.0 and 4.0 (slightly different). Please notice that you have to reload the record to get the current number after you add/close an activity.


var buXml = GetRegardingActivity();

if(buXml != null)
{
var buNodes = buXml.selectNodes("//BusinessEntity/statecode"); // CRM 3.0

//var buNodes = buXml.selectNodes("//BusinessEntity/q1:statecode"); // CRM 4.0
var iActivity = 0;
var iHistory = 0;

if(buNodes != null )
{
/*get values*/
for( i = 0; i < buNodes.length; i++)
{
switch(buNodes[i].text)
{
case "Open" : iActivity++; break;
case "Scheduled" : iActivity++; break;
case "Completed" : iHistory++; break;
case "Canceled" : iHistory++; break;
}
}

if(document.getElementById('navActivities') != null)
{
document.getElementById('navActivities').getElementsByTagName('NOBR')[0].innerText = document.getElementById('navActivities').getElementsByTagName('NOBR')[0].innerText + " (" + iActivity + ")";
}

if(document.getElementById('navActivityHistory') != null)
{
document.getElementById('navActivityHistory').getElementsByTagName('NOBR')[0].innerText = document.getElementById('navActivityHistory').getElementsByTagName('NOBR')[0].innerText + " (" + iHistory + ")";
}
}
}

function GetRegardingActivity()
{
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>" +
" <query xmlns:q1=\"http://schemas.microsoft.com/crm/2006/Query\" xsi:type=\"q1:QueryExpression\" xmlns=\"http://schemas.microsoft.com/crm/2006/WebServices\">" +
" <q1:EntityName>activitypointer</q1:EntityName>" +
" <q1:ColumnSet xsi:type=\"q1:ColumnSet\">" +
" <q1:Attributes>" +
" <q1:Attribute>statecode</q1:Attribute>" +
" </q1:Attributes>" +
" </q1:ColumnSet>" +
" <q1:Distinct>false</q1:Distinct>" +
" <q1:Criteria>" +
" <q1:FilterOperator>And</q1:FilterOperator>" +
" <q1:Conditions>" +
" <q1:Condition>" +
" <q1:AttributeName>regardingobjectid</q1:AttributeName>" +
" <q1:Operator>Equal</q1:Operator>" +
" <q1:Values>" +
" <q1:Value xsi:type=\"xsd:string\">" + crmForm.ObjectId + "</q1:Value>" +
" </q1:Values>" +
" </q1:Condition>" +
" </q1:Conditions>" +
" </q1:Criteria>" +
" </query>" +
" </soap:Body>" +
"</soap:Envelope>" +
"";

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

var resultXml = xmlHttpRequest.responseXML;
return resultXml;
}

01 February 2008

Happy Chinese New Year: 2008 - The Year of The Rat


7th Feb 2008: Chinese New Year or Spring Festival (simplified Chinese: ; traditional Chinese: ; pinyin: Chūnjié), or the Lunar New Year (simplified Chinese: ; traditional Chinese: ; pinyin: Nónglì xīnnián), is the most important of the traditional Chinese holidays. It is an important holiday in East Asia. The festival traditionally begins on the first day of the first lunar month (Chinese: 正月; pinyin: zhēng yuè) in the Chinese calendar and ends on the 15th; this day is called Lantern Festival (simplified Chinese: 元宵; traditional Chinese: 元宵; pinyin: yuánxiāojié). Chinese New Year's Eve is known as Chúxī (除夕). Chu literally means "change" and xi means "Eve".

The Chinese calendar is based on a combination of lunar and solar movements. The lunar cycle is about 29.5 days. In order to "catch up" with the solar calendar the Chinese insert an extra month once every few years (seven years out of a 19-yearcycle). This is the same as adding an extra day on leap year. This is why, according to the solar calendar, the Chinese New Year falls on a different date each year.

31 January 2008

Install CRM 4.0 and Outlook client on the same box



As you may know that Exchange Server isn't the only mail option in Microsoft Dynamics CRM v4.0, Furthermore in CRM 4.0, you can install Outlook client and CRM Server on the same box, it doesn't have limitation anymore. Plus, you don't have to have an Outlook profile setup before you install the Outlook client.


So, it's pretty easy now, let's start building a CRM 4.0 all-in-one-box VPC.



  1. Build and setup a Windows Server 2003 R2 with SQL Server 2005 VPC

  1. Install CRM 4.0 Server

  1. Install Outlook 2007

  1. Install CRM 4.0 Client for Outlook

During the installation, if you get a pre-check error message say:"Microsoft Dynamics CRM for Outlook cannot be installed on a computer where Microsoft Exchange Server versions prior to Exchange Server 2007 are installed." It does mean as it's displayed. But how about if you don’t have Exchange 2003 installed on that box? Well, in that case you may run 'regedit.exe', and go to [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\], find the [Exchange] folder, export it & delete it. And then try the installation again.


  1. After the installation, open Outlook(if you don't have a Outlook profile setup yet, do it now), you will see a brand new toolbar, click it, go to 'Next', it will alert you to close Outlook, do it, and then click 'Next'

  1. On the next window, you may select either 'My company' or 'An online service provider', select yours, click 'Next'

  1. Fill out the CRM server URL, then click 'Next', it will check the system requirement, click 'Next' again when it's finished, and then it will run the installation process.

  1. After the installation, every time you open Outlook, it will initialize some tasks (you can see it from the CRM Toolbar), so just leave it to finish.

26 January 2008

CRM 4.0 : get UserId, BusinessUnitId, OrganizationId on client-side JScript (WhoAmIRequest)

I'm sure it's in SDK, but to be clear I made it as a JScript function, so you can easly get UserId, BusinessUnitId and OrganisationId from client-side WhoAmIRequest, it's a good example.


function GetCurrentUserInfo()
{
var SERVER_URL = "http://CRM";
var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
xmlhttp.open("POST", SERVER_URL + "/mscrmservices/2007/crmservice.asmx", false);
xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlhttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/Execute");

var soapBody = "<soap:Body>"+
"<Execute xmlns='http://schemas.microsoft.com/crm/2007/WebServices'>"+
"<Request xsi:type='WhoAmIRequest' />"+
"</Execute></soap:Body>";

var soapXml = "<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'>";

soapXml += GenerateAuthenticationHeader();
soapXml += soapBody;
soapXml += "</soap:Envelope>";

xmlhttp.send(soapXml);
xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async=false;
xmlDoc.loadXML(xmlhttp.responseXML.xml);

var userid = xmlDoc.getElementsByTagName("UserId")[0].childNodes[0].nodeValue;
var buid = xmlDoc.getElementsByTagName("BusinessUnitId")[0].childNodes[0].nodeValue;
var orgid = xmlDoc.getElementsByTagName("OrganizationId")[0].childNodes[0].nodeValue;

alert("UserId: " + userid + "\r\nBusinessUnitId: " + buid + "\r\nOrganizationId: " + orgid);

}

19 January 2008

CRM 4.0 : Check current user's security role using JavaScript

It's a common question about how to show/hide fields based on user's security roles.
Ronald Lemmen had a very popular post on his blog about how to use 'RemoteCommand' to achieve that in CRM 3.0. Because 'RemoteCommand' is for internal use and unsupported, it doesn't work in CRM 4.0.
Michael H?hne also had a great post about how to access web service using client-side JavaScript. Since CRM 4.0 web service EndPoint changed, some people get 401 authorization error.

Here's code which works great in CRM 4.0, the function UserHasRole("ROLE_NAME") returns true if the current has the role, returns false if it doesn't. GetCurrentUserRoles() function generated by Michael H?hne's tool with some changes. Thanks for Regan who point out that using GenerateAuthenticationHeader() instead of hard-coding the Organization's Name.



//check if the current user has the 'System Administrator' role
alert(UserHasRole("System Administrator"));

function UserHasRole(roleName)
{
//get Current User Roles, oXml is an object
var oXml = GetCurrentUserRoles();
if(oXml != null)
{
//select the node text
var roles = oXml.selectNodes("//BusinessEntity/q1:name");
if(roles != null)
{
for( i = 0; i < roles.length; i++)
{
if(roles[i].text == roleName)
{
//return true if user has this role
return true;
}
}
}
}
//otherwise return false
return false;
}

function GetCurrentUserRoles()
{
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>role</q1:EntityName>" +
" <q1:ColumnSet xsi:type=\"q1:ColumnSet\">" +
" <q1:Attributes>" +
" <q1:Attribute>name</q1:Attribute>" +
" </q1:Attributes>" +
" </q1:ColumnSet>" +
" <q1:Distinct>false</q1:Distinct>" +
" <q1:LinkEntities>" +
" <q1:LinkEntity>" +
" <q1:LinkFromAttributeName>roleid</q1:LinkFromAttributeName>" +
" <q1:LinkFromEntityName>role</q1:LinkFromEntityName>" +
" <q1:LinkToEntityName>systemuserroles</q1:LinkToEntityName>" +
" <q1:LinkToAttributeName>roleid</q1:LinkToAttributeName>" +
" <q1:JoinOperator>Inner</q1:JoinOperator>" +
" <q1:LinkEntities>" +
" <q1:LinkEntity>" +
" <q1:LinkFromAttributeName>systemuserid</q1:LinkFromAttributeName>" +
" <q1:LinkFromEntityName>systemuserroles</q1:LinkFromEntityName>" +
" <q1:LinkToEntityName>systemuser</q1:LinkToEntityName>" +
" <q1:LinkToAttributeName>systemuserid</q1:LinkToAttributeName>" +
" <q1:JoinOperator>Inner</q1:JoinOperator>" +
" <q1:LinkCriteria>" +
" <q1:FilterOperator>And</q1:FilterOperator>" +
" <q1:Conditions>" +
" <q1:Condition>" +
" <q1:AttributeName>systemuserid</q1:AttributeName>" +
" <q1:Operator>EqualUserId</q1:Operator>" +
" </q1:Condition>" +
" </q1:Conditions>" +
" </q1:LinkCriteria>" +
" </q1:LinkEntity>" +
" </q1:LinkEntities>" +
" </q1:LinkEntity>" +
" </q1:LinkEntities>" +
" </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;
return(resultXml);
}



Due to Bloger's format issue, I have rewrite the code, it should work for everyone now.:)

12 January 2008

Dynamic Picklist, load values from a XML file



There are some dynamicpicklist examples on the Internet. You may find it from CRM SDK as well, and there's A (slightly) different approach to dynamic picklists from Greg Owens.

But none of those can meet my client's requirement. We have 2 picklists(which is normal), but picklist2 has hundreds of items. And those picklists repeat 3 times on all activites. Furthermore, they don't want to use another entity. So I create a XML file to keep the data, and using it dynamicly fill out picklist2 when picklist1 has value selected. It works great, and you only need to maintain one XML file.

This is a very simple XML to do the demonstration, put it ('section.xml') into the root folder of CRMWeb. The idea is: when user selects type1 in picklist1, then picklist2 only shows item1, item2, item3; when user selects type2 in picklist1,the picklist2 only shows item4, item5, item6.


<?xml version="1.0" encoding="utf-8" ?>
<Section>
<type1>
<item>item1</item>
<item>item2</item>
<item>item3</item>
</type1>
<type2>
<item>item4</item>
<item>item5</item>
<item>item6</item>
</type2>
</Section>


Suppose there are two picklists on crmForm, picklist2's value depends on picklist1's selection.
In my example:
picklist1 is: new_type1, it has two values: 'typeA/typeB' and 'typeC/typeD'
picklist2 is: new_section1, it doesn't have any value, and I add another nvarchar attribute: new_section1Text to save it's value(see example)



/*
Form.onLoad() event
GetItems() is a global function to get section's list items based on type's selection
typeValue: picklist1's DataValue
section: picklist2(object)
sectionText: picklist2.SelectedText
*/

GetItems = function(typeValue, section, sectionText)
{
//clean the section object
section.length = 0;

//it is the index of picklist2.SelectedText in XML file
var sectionTextIndex = 0;

//get the typeName, used for XML node
var typeName = 0;

switch(typeValue)
{
case "1" : typeName = "type1"; break;
case "2" : typeName = "type2"; break;
case "0" : return;
}

//load XML file
var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async = false;
xmlDoc.load("/section.xml");

//get all items under this type
xmlDoc = xmlDoc.getElementsByTagName(typeName)[0];
var items = xmlDoc.getElementsByTagName('item');

//insert all items into section object
for(var i=0; i<items.length; i++)
{
section.AddOption(items(i).firstChild.nodeValue, i+1);
if((sectionText != null)&&(sectionText.DataValue == items(i).firstChild.nodeValue))
{
sectionTextIndex = i+1;
}
}

return sectionTextIndex;
}

GetPicklist(crmForm.all.new_type1, crmForm.all.new_section1, crmForm.all.new_section1text);

function GetPicklist(type, section, sectionText)
{
if(sectionText.DataValue != null)
{
//select the right one
section.DataValue = GetItems(type.DataValue, section, sectionText);
}
}

/*
Form.onSave() event, save the current new_section1's selectedText
Becasue we need to add this option to new_section1 on Form.onLoad()
*/

SetPicklist(crmForm.all.new_section1 , crmForm.all.new_section1text);

function SetPicklist(section, sectionText)
{
if(section.SelectedText != "")
{
sectionText.DataValue = section.SelectedText;
section.length = 0;
}
}

/*
new_type1.onChange() event to call GetItem() funciton, passing new_type1.DataValue and new_section1, sectionText as parameters
*/
if(crmForm.all.new_type1.DataValue != null)
{
GetItems(crmForm.all.new_type1.DataValue, crmForm.all.new_section1, null);
}
else
{
crmForm.all.new_section1.options.length = 0;
}

06 January 2008

Microsoft Dynamics CRM 4.0 SDK Released!

Microsoft Dynamics CRM 4.0 SDK

This package contains the complete software development kit for Microsoft Dynamics CRM 4.0.


Microsoft Dynamics CRM 4.0 Language Pack Readme

This document provides important late-breaking information.


Microsoft Dynamics CRM 4.0: Planning and Deployment Guidance for Service Providers

These guides and tool provide additional information for Service Providers to plan and deploy Microsoft Dynamics CRM 4.0.


Microsoft Dynamics CRM 4.0

Microsoft Dynamics CRM 4.0 for released languages.


Microsoft Dynamics CRM 4.0 Data Migration Manager

Using the Microsoft Dynamics CRM 4.0 Data Migration Manager, you can convert and upload data from another CRM system to Microsoft Dynamics CRM 4.0.


Microsoft Dynamics CRM 4.0 Trial Versions

Microsoft Dynamics CRM 4.0 90-day trial versions for released languages.


Microsoft Dynamics CRM for Outlook (For On-Premise and Hosted Editions)

Install Microsoft Dynamics CRM 4.0 for Outlook and Microsoft Dynamics CRM 4.0 for Outlook with Offline Access. For on-premise and hosted editions of Microsoft Dynamics CRM 4.0 only.


Microsoft Dynamics CRM 4.0 E-mail Router (On-Premise and Hosted Editions)

The E-mail Router is an interface between the Microsoft Dynamics CRM system and an e-mail system.


Examples of how to configure the Microsoft Dynamics CRM 4.0 on-premise E-mail Router in different deployment scenarios

This document lists steps to configure Microsoft Dynamics CRM 4.0 e-mail in different deployment scenarios.


Microsoft Dynamics CRM 4.0 for Outlook Readme (On-Premise and Hosted Editions)

This document provides important late-breaking information.


Microsoft Dynamics CRM 4.0 Implementation Guide

This guide contains comprehensive information about how to plan, install, and maintain Microsoft Dynamics CRM 4.0.


Microsoft Dynamics CRM 4.0 Server Readme

This document provides important late-breaking information for Microsoft Dynamics CRM 4.0 Server.


Microsoft Dynamics CRM 4.0 E-mail Router Readme (On Premise)

This document provides important late-breaking information for the Microsoft Dynamics CRM 4.0 E-mail Router (on-premise and hosted editions).


Microsoft Dynamics CRM 4.0 Data Migration Manager Readme

This document provides important late-breaking information.


Microsoft Dynamics CRM 4.0 Internet Facing Deployment Scenarios

This document covers how to set up the Microsoft Dynamics CRM 4.0 Web site to make it available from the Internet.