Invoke Systems
About Invoke News & Events Blog Contact Us

Microsoft Dynamics CRM 3.0 Developer's Weblog

Blog

Microsoft CRM
Altiris
Business Management
Government IT
Partnership
Corporate Headquarters
5523 Research Park Drive
Suite 130
Baltimore, MD 21228

Toll Free: 800.482.5742
Tel: 410.455.9600
Fax: 410.455.9185

Bellevue, Washington Office
10940 NE 33rd Place
Suite 212
Bellevue, WA 98004

Tel: 425.822.6071
Fax: 425.822.0792

 

 

We've Moved!

I am happy to report that our Microsoft CRM blog has moved and is now available at Ascentium.com!  This move coincides with the release of our updated Ascentium web site.  The new web site is built entirely on Microsoft SharePoint 2007 and has been updated to include pertinent information about the new Ascentium National CRM Practice.

Please visit us at our new blog location:
http://www.ascentium.com/blog/crm/Default.aspx

Cheers,

Published Friday, December 21, 2007 4:17 AM by aaron.elder

The Geek Speaks - Microsoft CRM Development Q&A

On Wednesday I had the privilege of being a guest on the MSDN Channel9 Program, "geekSpeak".  The result was discussion around Microsoft CRM as a development platform and a great session of Q&A on how to approach certain problems and how to get started developing on Microsoft CRM.

http://channel9.msdn.com/ShowPost.aspx?PostID=348962

Enjoy,

Published Friday, October 19, 2007 7:39 AM by aaron.elder
Filed Under: ,

Creating a Web Portal with CRM 3 and LINQ

I have been spending a decent amount of time reading up and playing around with the new features that are coming with Visual Studio 2008.  One site that I regularly read is Scott Guthrie's blog.  Lately he has been posting a lot about LINQ and I really like the idea behind the technology.  Having a more object oriented way of querying opens up a new realm of possibilities as well as makes life easier when writing code.  Intellisense is a wonderful thing!

Today, I'm going to be writing about using LINQ in combination with CRM's filtered views to create a very basic web portal that allows you to search for Accounts and display the name and primary Contact in a GridView.  I'll demostrate how simple it is to create the LINQ objects and hook into CRM's database all while being 100% supported and secure.  For those not familiar with CRM filtered views, they are basically a Microsoft supported way to query the database directly and still maintain security on the data.  So let's get started.

I started out by firing up the latest build of VS 2008 (Beta 2) and created a new web project on my pre-built CRM VPC.  After creating the project, I right clicked on the App_Code folder and chose to add a new item.  In the New Item list, displayed below, I chose LINQ to SQL Classes file and named it CRM.dbml.

After adding the new file I then went into the Server Explorer and connected to my Sql Server's MSCRM database.  I then opened up my CRM.dbml file and dragged the FilteredAccount and FilteredContact views from the Server Explorer.

*Notice that after you drag the views into the main window, they appear as a package with properties on them.  You can select each property (field) individually and set properties on them.  To increase the efficiency of the querying itself, fields such as "Delay Loaded" will only load the data for that field if it's used.  For more details on specifying properties check out Scott Guthrie's post here.

After adding the 2 filtered views, I added a TextBox, Button, and GridView Asp.Net controls to my Default.aspx.

*Another very cool feature coming with VS2008 is the split view display.  This display allows you to view the output page as you write the Html.  I find it handy for CSS updates and manipulating the page to get that exact look and feel.

I then opened the code behind and started writing my code on my "Find" button's onclick event.  By default the namespace System.Linq is added in my header and the intellisense immediately picked up.

protected void queryButton_Click(object sender, EventArgs e)

{

    if (search.Text != string.Empty && search.Text.Length > 0)

    {

            CRMDataContext db = new CRMDataContext();

            accountGrid.DataSource = from a in db.FilteredAccounts

                  join con in db.FilteredContacts

                        on a.primarycontactid equals con.contactid

                  where a.name.StartsWith(search.Text)

                  select new

                  {

                        Name = a.name,

                        PrimaryContact = con.fullname

                  };

           

            accountGrid.DataBind();

    }

}

The first line I added is my data context.  To reference my CRM filtered views, I simply type CRMDataContext.  I then set my DataGrid's (Id = accountGrid) DataSource to the LINQ query I am going to run against the object.  Notice how similar the query looks like Sql.  When creating the query, I joined my FilteredAccount to the FilteredContact to return the Primary Contact's fullname.  While typing the query the intellisense made it very easy to use.  I didn't have to go back to my view and see what the fields name were, they were right there.  One less Alt+Tab is a big plus in my book!  You can see it in the screenshot below.

 

After finishing my code, I hit save and ran my project.  Below are the results.

 

Here is the CRM view of the data. 

So there you have it.  CRM data displayed in a web portal, all supported and secure, using LINQ with VS 2008 and CRM's filtered views.

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

Update:  Michael Friis emailed me to let me know of a project he is currently working on relating to LINQ to CRM.  I suggest checking it out here

Published Monday, August 20, 2007 11:49 PM by ross.lotharius
Filed Under: ,

Importing a non-CRM Workflow

On a recent project, one of our senior CRM development team members had the challenge of importing Opportunities that were in the middle of a workflow.  Here is a post Chris Scott worked up on the subject:

Recently I worked on a data migration project from a legacy system to Microsoft CRM. As part of this migration, opportunities were migrated including a field that was essentially equivalent to a workflow stage. The customer wanted to use the CRM workflow and update each opportunities workflow stage to the legacy system’s equivalent.  The customer’s preference was to set the stage of the workflow without any of the previous workflow tasks being created. If this was not supported, then their next preference was to set each of the tasks as cancelled until the appropriate stage in the workflow was reached.

Workflow details
The workflow process had 9 stages with 3 to 5 tasks in each stage. Once a specific set of tasks (one or more) were marked as completed, the next stage would open up with its associate tasks. The workflow would fire upon creation of a customer record creating a sales process task list and setting the CloseProbability in the Opportunity entity to a predetermined value.

Design overview
Our investigations revealed that the customer’s first preference of skipping ahead to the appropriate stage level without creating the children tasks was not supported. We found that the method CurrentStepId within the WFProcessInstance class would set the stage to a specific level, but when we attempted to set the CurrentStepID to a future stage an error was generated. We found that this error was caused by the supporting workflow database records associated with each tasks are not created until the previous stage is completed / cancelled / skipped. What this meant is we would have to go and, using the imported data, programmatically set each task within already completed task to “cancelled”. To accomplish this, we used the SetStateTaskRequest to set each TaskState and TaskStatus until the appropriate stage was reached. As each task status was changed, CRM would create the associated database records and open the next stage.

Code overview
We created a C# console application that retrieves records from a custom stored procedure. The stored procedure queries the FilteredActivityPointer table where the TypeCodeName is a task and the opportunity is active.

Note: Custom stored procedures are not supported within CRM.  For this purpose however, the stored procedure was only used during the initial data migration and dropped after the migration was completed.

The stored procedure only retrieve records where the current workflow CloseProbability (stored in FilteredOpportunity) is less than the legacy CloseProbability value (code not shown).

The console app created a loop calling the stored procedure until the no records were returned.  For each procedure call, the console looped through the records setting the TaskState and TaskStatus to “cancelled” for a specific ActivityID for an EntityId:

 

CrmService crmService = CrmUtility.GetCrmService();

 

  //Set the values to canceled for the state & status

SetStateTaskRequest stateTask = new SetStateTaskRequest();

stateTask.TaskState = TaskState.Canceled;

stateTask.TaskStatus = 6;

 

  //Open the database connection

oSQLConn.Open();

 

SqlDataReader dr = cmd.ExecuteReader();

 

while(dr.Read())

{

//Set the EntityId to the ActivityId

stateTask.EntityId = dr.GetGuid(0

 

//Set the state of each task to cancelled

crmService.Execute(stateTask);

 

}

oSQLConn.Close();

 

Lessons learned
During the import of opportunities records (prior to updating the workflow tasks), we found that with the workflow enabled, the Opportunity CloseProbability value was overridden resetting each value to 0. Although this was unexpected, it is the "by design" of the workflow process.  However, this meant in order to maintain legacy information, we needed to store the legacy CloseProbability value into a custom column.

We also found that once a workflow is enabled and associated with an opportunity, an instance of the workflow is created for that opportunity. This instance is independent of the “main” workflow and will run its course until completed or cancelled even if the “main” workflow is altered or deactivated. Each instance and its detailed can be viewed in the workflow monitor (under the Microsoft CRM start menu group).  This tool, often overlooked, can be your biggest ally when trying to understand what is happening within a workflow.

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

Published Friday, August 10, 2007 9:18 PM by aaron.elder
Filed Under: ,

Visio Shapes & PowerTools Download Locations

It turns out that Microsoft has taken down the CRM Sandbox and while they transition to a new home, has stood up a temporary download location for the content that was there.  I did not actually realize how popular these tools were, but since the sandbox went down, I have gotten several emails and phone calls asking for updated links!  I have now updated my previous posts and provided this new post with the updated URLs.

The new URLs are:

Invoke PowerTools for CRM 3.0:
http://www.gotdotnet.com/Community/UserSamples/Details.aspx?SampleGuid=4D5F4E45-0853-4475-925C-36373A8AFA66

Visio Shapes:
http://www.gotdotnet.com/Community/UserSamples/Details.aspx?SampleGuid=9E56FAE2-8DF1-48C2-ACF6-7EE44B626594

Enjoy...

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

Published Thursday, August 02, 2007 8:18 PM by aaron.elder
Filed Under:

Rolling with Rollup Update #2

The Microsoft CRM 3.0 Rollup Update #2 has been out for a few weeks now and I am happy to report that early last week, we updated our production CRM system to this release.  The update for us was very minor, as we had been running the beta of this update for a few months now.  I am of course happy to report that the update was very easy and that everything has been running strong since the installation, with no problems to report!

In other news, as many of you know, we make heavy use of Virtual PC's to demonstrate and develop for CRM.  Our VPC typically has both the CRM Server and the CRM Client installed on it, which of course is an "unsupported situation".  Of the development / demonstration scenarios almost require it, so we do it anyway in non-production situations.

Now that rollup Update #2 is available, you will probably want to update both the Client and Server on a single VPC.  Unfortunately if you try to install the Server Rollup, you will get the following (and useless error):

There are two fixes to this that I have found:

Option #1 is to uninstall the client, install the server update and then re-install the client.  This of course is a pain.

Option #2 is to find this registry key:
HKEY_CURRENT_USER\Software\Microsoft\Installer\Products\223F2CCFC88E
FB14D92F37114E967322


And temporarily rename it to:
HKEY_CURRENT_USER\Software\Microsoft\Installer\Products\223F2CCFC88E
FB14D92F37114E967322.bak

Now, install the Server Update and then rename it back. 

Unlike Rollup Update #1, the build numbers between Update #2 Client and Update #2 Server appear to be the same.  For this reason, I assume you can install them in either order.  As a best practice though, I would install the client update last.

Cheers,

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

Published Thursday, July 12, 2007 3:57 PM by aaron.elder
Filed Under: ,

Invoke Systems - PowerTools for Microsoft CRM 3.0

In celebration of our merger with Ascentium, we are releasing our internal "PowerTools" for Microsoft CRM 3.0.  This set of tools is of course completely "unsupported", so use them at your own risk.

This release is provided "AS IS" and contains no warranty and confers no rights.  Copyright 2007 Invoke Systems – All rights reserved.

 

Form Editor

  • View Entity XML
  • View Form XML
  • View Properties XML
  • Access advanced form properties (See Below)
    • Customize system-level onload / onsave events
      (Event the Microsoft team doesn’t have this, they hack the XML!)
    • Hide/Show the “Related Information” pane on a per entity basis
      (Event the Microsoft team doesn’t have this, they hack the XML!)
    • Write form events using the new event editor (See Below)
    • Access advanced field properties
      • Customize system-level onclick / onchange events
        (Event the Microsoft team doesn’t have this, they hack the XML!)
      • Write events using the new event editor(See Below)
    • New Event Editor (See Below)
    •  Add / Remove Hidden Fields to the form
      (Event the Microsoft team doesn’t have this, they hack the XML!)
    • Kill Field (Remove any field for any reason, even locked fields)

View Editor

  • View Entity XML
  • View Properties XML
  • View Grid XML
  • View Fetch XML

Preview Editor

  • View Entity XML
  • View Properties XML
  • View Preview (Form) XML

General

  • Right-click is enabled throughout the UI 

New Event Editor

 

 

 

 

The new event editor is SUPER fancy and is designed to make writing form events easy and fast.  New features include:

  • Tabbing (Don’t ask why it doesn’t support code tabbing/formatting out of the box)
  • A field selector (Makes it VERY easy to access any field on the form, without having to go back and forth 50 times to figure out the schema names)
  • Quick Buttons for injecting commonly used commands
  • Quick Buttons for injecting commonly used (but unsupported commands) – Like making a field required on-the-fly 

New Advanced Form Properties

  

 

The “Other Details” section is new and you will notice that you can now edit the system-level events via the UI!  You can also use the “Hidden Data Fields” tab to manage (add/remove) hidden fields with ease!

 

Download the tools here (Updated):

http://www.gotdotnet.com/Community/UserSamples/Details.aspx?SampleGuid=4D5F4E45-0853-4475-925C-36373A8AFA66

 

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

 

Published Sunday, July 08, 2007 11:04 PM by aaron.elder
Filed Under: , ,

Build Numbers? <Redux>

A ltitle while back I posted about the history of CRM build numbers.  With the recent release of Rollup Update #2 mixed with the fact I am stuck in an airport for the next 5 hours, I thought I would take a moment to update the list. :-)

Build Number

Released

Details

3.0.5300.0

October 2005

This is the RTM release

3.0.5300.1185

-
3.0.5300.1189

July 2006

Rollup Update #1 Fork

This actually started in July of 2006 and was released in November 2006

3.0.5300.1200

Summer 2006 (?)

Appears to be a fork used for hot fixes that were not going to make it into Rollup Update #1

3.0.5300.1255

August 2006

Spotted in a hot fix

3.0.5300.1283

September 2006

Spotted in a hot fix

3.0.5300.1344

November 2006

Web Application Performance Hot Fix

3.0.5300.1361

December 2006

Outlook Client Compatibility Re-Release (v3c) Final

3.0.5300.1500

December 2006 (?)

Seems to be the current hot fix tree for all fixes post the v3c release.

3.0.5300.1525

January 2007

Spotted in recent hot fixes

3.0.5300.1528

February 2007

Spotted in recent hot fixes

3.0.5300.1558

3.0.5300.1561

June 2007

Rollup Update #2

 

Published Friday, June 29, 2007 12:58 AM by aaron.elder
Filed Under:

Impersonation and using CRM Filtered Views from callouts

A common problem that we often face when writing CRM callouts is how to call Filtered Views from within the callout.  We might want to do this, for a variety of reasons such as performing T-SQL operations, JOINs and the like.  The problem of course is that the CRM Filtered Views “filter” / provide row-level security based on the AD user that is making the request.  This is why you normally cannot use SQL Authentication to access CRM Filtered Views and must use Integrated Authentication.  Since callouts typically run in the context of NT AUTHORITY\NETWORK SERVICE, calls made to a Filtered View from within a callout will always return zero results.  This happens because NETWORK SERVICE is not a CRM user and cannot own or have shared access rights to any records.

What is the fix?  Well previously, we had been using .NET impersonation to impersonate a CRM user prior to making the call to SQL.  While this got around the issue, it required use to store the username and password of the user to be impersonated on the server.  While we would store the password in an encrypted fashion, this is still not very security and it made management of the account difficult.    We have recently discovered a much cleaner way that does not require storing the AD credentials on the machine and also enable the use of SQL Authentication.

The trick is to use SQL context switching to impersonate the AD user prior to calling the Filtered View SELECT statement.  The two methods for doing this are SETUSER and EXECUTE ASSETUSER is legacy and works in SQL 2000 as well as SQL 2005 (although it may be deprecated in the future) and EXECUTE AS is new in SQL 2005.  If you are using SQL 2005, I definitely recommend EXECUTE AS, as it requires fewer privileges from the calling user and as such, I would consider it more secure.  You can read about the differences between these two methods here:

http://msdn2.microsoft.com/en-us/library/ms188315.aspx

 

Here is a sample callout that shows the two methods:

 

public override PreCalloutReturnValue PreCreate(CalloutUserContext

userContext, CalloutEntityContext entityContext, ref string entityXml,

ref string errorMessage)

{

// Store the result

bool rows = false;

     

// Callout is running as NETWORK SERVICE,connect using Integrated Auth

 

using (SqlConnection conn = new SqlConnection("Data Source=localhost; Initial Catalog=Invoke_Systems_Dev_MSCRM;Integrated Security=SSPI"))

{

      SqlCommand cmd = new SqlCommand();

      cmd.Connection = conn;

      cmd.CommandType = CommandType.Text;

      conn.Open();

 

      // SQL 2000 - Impersonate the CRM Admin   

      // cmd.CommandText = @"SETUSER 'invokecrm\administrator'

      //                     SELECT * FROM dbo.FilteredAccount";

     

      // SQL 2005 - Impersonate the CRM Admin             

      cmd.CommandText = @"EXECUTE AS USER = 'invokecrm\administrator'

                          SELECT * FROM dbo.FilteredAccount

                          REVERT";

                                       

      SqlDataReader reader = cmd.ExecuteReader();

           

      // This will always be false, unless we use
      // SQL impersonation to impersonate a CRM user

      rows = reader.HasRows;

}

 

      return PreCalloutReturnValue.Continue;

}

 

This post can also be found here.

 

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

Published Monday, May 21, 2007 6:28 PM by aaron.elder
Filed Under: ,

You have my "permission" to use Bulk Import

Bulk Import in Microsoft CRM, allows end users to import Accounts, Contacts, Leads and Campaign Responses into CRM from a text file.  Users can access this from the “Tools | Import…” menu at the homepage level of the application.

Recently, we had a problem where the menu option was not showing up for all users.  We of course knew it was a privilege issue, but the particular privileges required to get this item to show up are not exactly intuitive.  Here are the privileges required for a user to see and use Bulk Import.

One or more of the following:

Create Account OR Create Contact OR Create Lead OR Create Activity

And all of the following:

Read Activity

AND

Create Activity

AND

Write Activity

AND

Delete Activity

AND

Append Activity

AND

AppendTo Activity

AND

Read Note

AND

Create Note

AND

Write Note

AND

Delete Note

AND

Append Note

AND

AppendTo Note

AND

Read Bulk Import

AND

Create Bulk Import

AND

Write Bulk Import

AND

Delete Bulk Import