Sonoma Partners Microsoft CRM and Salesforce Blog

CRM 2011 Legacy Feature Check Walkthrough

As we all know, CRM 2013 is coming and if you’re looking to upgrade there are a few legacy features that are being removed.  The removal of these features could potentially affect your deployment if you are currently using any of them.  Microsoft released this article which points out all the legacy features that are being removed and even references this handy tool called Legacy Feature Check.  The Legacy Feature Check tool can be used to detect if you are using any of those legacy features that won’t be available in CRM 2013.  In this post I will be walking through the steps to take to run the tool

Please note that it is recommended to run this tool in off-hours.  There are no known performance issues with running the tool but it is better to be safe!

First extract the zip file into an easy to access folder, preferably on the CRM Web Server where we will run the tool.

Note that you don’t necessarily need to be on the CRM Web Server to run this tool as you can specify the server name but running it from the CRM Server is recommended.  When I tried running it from my computer, I received an error that my SQL database doesn’t allow remote connections.

Then open the Command Prompt from the CRM Web Server and navigate to that folder you extracted to.  You can then use the following to view all the acceptable parameters for this tool

Microsoft.Crm.LegacyFeatureCheck.exe ?

This should spit out the following parameters shown below:
image

Since we are on the CRM Web Server we can skip the /s parameter to specify the server name.  You can use the /org parameter if you would like to run only against one organization or just leave that parameter out to run it against all organizations.

For this walkthrough, I will enable verbose logging with /Verbose and only search my org ScarlavaiLocal by passing in /org:ScarlavaiLocal as well as setting /out:output.xml to create an xml file of the output in the same folder that the Legacy Feature Check tool is in.

Microsoft.Crm.LegacyFeatureCheck.exe /org:ScarlavaiLocal /Verbose /out:output.xml 

My output from the xml file is shown below.

<Result>
    <OrganizationName>ScarlavaiLocal</OrganizationName>
    <OrganizationUniqueName>ScarlavaiLocal</OrganizationUniqueName>
    <OrganizationId>9bc8a9a0-deb0-dd11-83d6-0003ff800901</OrganizationId>
    <Issues>1</Issues>
    <Results>
      <VerificationResult>
        <Severity>Info</Severity>
        <Component>General</Component>
        <ComponentId>00000000-0000-0000-0000-000000000000</ComponentId>
        <Message>Checking for Microsoft Dynamics CRM 4.0 plug-ins</Message>
      </VerificationResult>
      <VerificationResult>
        <Severity>Info</Severity>
        <Component>PluginAssembly</Component>
        <ComponentId>b7bed020-bed1-e111-83d9-0019b9f8f548</ComponentId>
        <Message>Name: Scarlavai.Crm.Workflow, Version: 1.0.0.0, Length: 6318080, 
                 Location: Database, Is V4: True.
        </Message>
      </VerificationResult>
      <VerificationResult>
        <Severity>Warning</Severity>
        <Component>PluginAssembly</Component>
        <ComponentId>b7bed020-bed1-e111-83d9-0019b9f8f548</ComponentId>
        <Message>Assembly "Scarlavai.Crm.Workflow" with version '1.0.0.0'
                 references the CRM 4.0 SDK.
        </Message>
      </VerificationResult>
      <VerificationResult>
        <Severity>Info</Severity>
        <Component>PluginAssembly</Component>
        <ComponentId>b19050e0-4096-4271-96a5-66876164b59a</ComponentId>
        <Message>Name: Scarlavai.Crm.Plugins, Version: 1.0.0.0, Length: 1200812, 
                 Location: Database, Is V4: False.
        </Message>
      </VerificationResult>
      <VerificationResult>
        <Severity>Info</Severity>
        <Component>PluginAssembly</Component>
        <ComponentId>04e6093c-be23-4c32-a08d-bd5db4c63b09</ComponentId>
        <Message>Name: Scarlavai.Crm.WorkflowActivities, Version: 1.0.0.0, 
                 Length: 812376, Location: Database, Is V4: False.
        </Message>
      </VerificationResult>
      <VerificationResult>
        <Severity>Info</Severity>
        <Component>General</Component>
        <ComponentId>00000000-0000-0000-0000-000000000000</ComponentId>
        <Message>Checking for web resources accessing the 
                 2007 web service endpoint
        </Message>
      </VerificationResult>
      <VerificationResult>
        <Severity>Info</Severity>
        <Component>WebResource</Component>
        <ComponentId>00000000-0000-0000-0000-000000000000</ComponentId>
        <Message>Checking 160 web resources</Message>
      </VerificationResult>
      <VerificationResult>
        <Severity>Info</Severity>
        <Component>WebResource</Component>
        <ComponentId>00000000-0000-0000-0000-000000000000</ComponentId>
        <Message>Did not find any web resources that contain calls to the 
                 2007 web service endpoint
        </Message>
      </VerificationResult>
    </Results>
  </Result>

 

The tool will write out 1 <Result> node per organization that the tool was ran against.  Each <Result> node will have a child <Issues> node that contains the total count of issues found.  As you can see from above, my org has 1 issue detected.  Then you can sort through each child VerificationResult and look at the Severity to see if it is a Warning or an Info.  My output above has 1 Warning which is telling me that my PluginAssembly called “Scarlavai.Crm.Workflow” references the CRM 4.0 SDK.  Since the CRM 4.0 SDK will not be supported in CRM 2013, I will need to update that assembly to work with the latest SDK.  The last three VerificationResults, in my example, show that the tool checked all 160 web resources in my organization and did not find any that contain calls to the 2007 web service endpoint which puts me in the clear on web resources!

Hopefully this walkthrough of the Legacy Feature Check tool proves helpful.  Leave a comment for any questions or let us know if there is another walkthrough you would like to see!

Topics: Microsoft Dynamics CRM Microsoft Dynamics CRM 2011

CRM 2011 Timeouts and Limits Wiki

A little over a year ago I submitted the CRM 2011 Timeouts and Limits Wiki page to the CRM 2011 Wiki and it has grown quite a bit since then thanks to the CRM community.  I highly recommend bookmarking the page as it can be a huge help when troubleshooting any sort of CRM timeout or limit. 

A lot of the timeouts and limits listed in the Wiki page are configurable but usually requires an update through the API to configure the values.  Luckily, we have a free and easy to use utility to help configure these values for any type of organization and for multiple organizations at the same time.  Head here for the free Universal Settings download and here for more information on how to use the utility.

If you run into any timeouts or limits that are not listed on the wiki page, feel free to add them and we’ll be sure to keep the list update for CRM 2013 as well!

WinRT XAML – Capturing a Bing Maps Image

The Bing Maps control for Windows Store Apps is extremely powerful and has almost all the capabilities of Bing Maps with directions, searching, traffic, etc.  One feature that is missing though is a way to get a static image of what your map looks like if you need to generate a document, email or just have an image of the map control for reference.

What Bing does have though is an Imagery API that provides a way to get a static map using a variety of options such as the map size, zoom level, pushpins and any waypoints for directions.  The best part is that most Bing APIs (including Imagery) are free when used inside a Windows Store app! 

That being said…there is one issue with using the Imagery API in conjunction with the Bing Maps control.  If you allow the user to plot a route using waypoints in the Bing Maps control and then you pass the same waypoints into the Imagery API, there is a potential for the routes to be different as shown in my example below.  The Bing Maps control is shown on the left and the Imagery API output shown on the right.  This is using the same two waypoints with the same route settings and as you can see, a different route is displayed.

image         image

In order to solve this, we added some code to render an image that has the same route path that the Bing Maps control is using.  Shout out to Geoff Innis at Microsoft for pointing us in the right direction and Ricky’s Bing Maps Blog for code that we ported to WinRT.

Before we dig into the code, you’ll need the Writeable Bitmap Extensions from CodePlex which can be found here.  These extensions allow us to easily draw lines on an image since WinRT currently doesn’t have native support for it.  If you plan on saving the image, you’ll need to utilize the WriteableBitmapSaveExtensions from the WinRT XAML Toolkit as well, found here.

Now for the good part.  First we need to use the Bing Maps control’s Directions Manager to calculate directions based on an array of waypoints.  If AutoSetActiveRoute and AutoUpdateMapView are set to true, then the map control will automatically display the route connecting each waypoint.

DirectionsManager directionsManager = this.Map.DirectionsManager;
directionsManager.ClearActiveRoute();
directionsManager.RenderOptions.AutoSetActiveRoute = true;
directionsManager.RenderOptions.AutoUpdateMapView = true;
directionsManager.Waypoints = waypoints;

var response = await directionsManager.CalculateDirectionsAsync();

Then we have a method to build the static image of the map.  We will pass in the two waypoints, the center of the map, the map size, and the map zoom level to help mimic what the map control looks like.  This method will then return a WriteableBitmap which we can then draw the route on top of.

private async Task<WriteableBitmap> GetStaticMapBitmap(Route route)
{
    var imagerySet = "";
    switch (Map.MapType)
    {
        case MapType.Aerial:
        case MapType.Birdseye:
            imagerySet = MapType.Aerial.ToString();
            break;
        default:
            imagerySet = MapType.Road.ToString();
            break;
     }

     string url = String.Format("http://dev.virtualearth.net/REST/v1/Imagery/Map/{0}/{1},{2}/{3}"+
                                   "?mapSize={4},{5}&key={6}",
                                imagerySet,
                                this.Map.Center.Latitude,
                                this.Map.Center.Longitude,
                                Math.Floor(this.Map.ZoomLevel),
                                this.Map.Width,
                                this.Map.Height,
                                apiKey);
     for (var i = 0; i < route.RouteLegs.Count; i++)
     {
         var routeLeg = route.RouteLegs[i];
         url = String.Format("{0}&pushpin={1},{2};1;{3}", url, 
                          routeLeg.StartLocation.Location.Latitude, 
                          routeLeg.StartLocation.Location.Longitude, Number2Alpha(i + 1));
         if (i == _currentRoute.RouteLegs.Count - 1)
         {
             url = String.Format("{0}&pushpin={1},{2};1;{3}", url, 
                          routeLeg.EndLocation.Location.Latitude, 
                          routeLeg.EndLocation.Location.Longitude, Number2Alpha(i + 2));
         }
    }
    
    var backgroundDownloader = new BackgroundDownloader();
    var file = await ApplicationData.Current.TemporaryFolder.CreateFileAsync("tempmapimage.jpg",
        CreationCollisionOption.ReplaceExisting);
    var downloadOperation = backgroundDownloader.CreateDownload(new Uri(url), file);

    await downloadOperation.StartAsync();
    return await BitmapFactory.New(1, 1).FromStream(await file.OpenStreamForReadAsync(),
        Windows.Graphics.Imaging.BitmapPixelFormat.Unknown);
}
 

Now we need to draw the route on top of the image. These methods will first get the static image from Bing as a starting point and then loop through all the path points in the route to draw a line to each point and create our route.

private async Task DrawMapImage()
{
    var route = this.Map.DirectionsManager.ActiveRoute;
    WriteableBitmap writeableBmp = await GetStaticMapBitmap(route);
    List<Location> routes = new List<Location>();
    for (int i = 0; i < route.RoutePath.PathPoints.Count; i++)
    {
        routes.Add(route.RoutePath.PathPoints[i]);
    }

    AddPolyline(writeableBmp, routes.ToArray(), this.Map.Center, this.Map.Height, 
        this.Map.Width); 
}
 
private void AddPolyline(WriteableBitmap writeableBmp, Location[] polyline, Location center, 
    int mapHeight, int mapWidth)
{
    Point[] points = new Point[polyline.Length];

    //calculate pixel points
    for (int i = 0; i < polyline.Length; i++)
    {
         points[i] = LatLongToPixel(polyline[i], center, mapHeight, mapWidth);       
    }

    for (var i = 0; i < points.Length; i++)
    {
         if (i + 1 >= points.Length)
             return;

         WriteableBitmapExtensions.DrawLine(writeableBmp, (int)points[i].X, (int)points[i].Y, 
              (int)points[i + 1].X, (int)points[i + 1].Y, Colors.Blue);
    }
}

private Point LatLongToPixel(Location latlong, Location center, int mapHeight, int mapWidth)
{
    //Formulas based on following article:
    //http://msdn.microsoft.com/en-us/library/bb259689.aspx

    var zoom = Math.Floor(this.Map.ZoomLevel);

    //calcuate pixel coordinates of center point of map
    double sinLatitudeCenter = Math.Sin(center.Latitude * Math.PI / 180);
    double pixelXCenter = ((center.Longitude + 180) / 360) * 256 * Math.Pow(2, zoom);
    double pixelYCenter = (0.5 - Math.Log((1 + sinLatitudeCenter) / (1 - sinLatitudeCenter))
                          / (4 * Math.PI)) * 256 * Math.Pow(2, zoom);

    //calculate pixel coordinate of location
    double sinLatitude = Math.Sin(latlong.Latitude * Math.PI / 180);
    double pixelX = ((latlong.Longitude + 180) / 360) * 256 * Math.Pow(2, zoom);
    double pixelY = (0.5 - Math.Log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI))
                    * 256 * Math.Pow(2, zoom);

    //calculate top left corner pixel coordiates of map image
    double topLeftPixelX = pixelXCenter - (mapWidth / 2);
    double topLeftPixelY = pixelYCenter - (mapHeight / 2);

    //calculate relative pixel cooridnates of location
    double x = pixelX - topLeftPixelX;
    double y = pixelY - topLeftPixelY;

    return new Point((int)Math.Floor(x), (int)Math.Floor(y));
}
 
 

At this point you can use the WriteableBitmapSaveExtensions from the WinRT Xaml Toolkit to either save the Writeable Bitmap as an image file or convert it to a base64 string as shown below so we can store the image as a Note attachment in Dynamics CRM.

private async Task<string> ConvertBitmapToByteArray(WriteableBitmap bitmap)
{
    var imageFile = await bitmap.SaveToFile(ApplicationData.Current.TemporaryFolder, "MapImage.bmp", 
        CreationCollisionOption.ReplaceExisting);
    var stream = await imageFile.OpenReadAsync();

    using (var dataReader = new DataReader(stream))
    {
        var bytes = new byte[stream.Size];
        await dataReader.LoadAsync((uint)stream.Size);
        dataReader.ReadBytes(bytes);

        return Convert.ToBase64String(bytes);
    }
} 
 

And the final result:

image

It is important to note that the performance for this method is definitely not the best as the Windows 8 device will be processing each path point and drawing a line for each one.  It is only ideal for routes that are shorter in distance so that there are less path points to draw.

Topics: Microsoft Dynamics CRM Microsoft Dynamics CRM 2011

JavaScript Date Formatting in CRM 2011 Using User Settings

Today’s guest blogger is Erik Volkening, a Developer at Sonoma Partners.

Recently I had to add some custom date formatting on a custom HTML5 dialog for one of our Dynamics CRM clients. The date field on the dialog was always displayed in the US format of M/D/YYYY, and the client had many international users who, rightfully so, wanted to see the date formatted using their local conventions.

Sounds easy right? It should be a matter of grabbing the value from the dateformatstring field in the usersettings entity for that user’s record and using that value to format the date using JavaScript and SOAP.

Except it looks like CRM is caching the datetimestring.

To test my changes I found a user who had his user settings set to Spanish (Spain) and the date on the custom dialog was displayed to the user in the proper Spanish format (DD/MM/YYYY). So far, so good.


image


This showed up in the dialog as 20/08/2013. Great!

Next, I found a setting where the date format was DD-MM-YYYY. When I tested the dialog, the date was still in the DD/MM/YYYY format. Maybe I forgot to hit save so I tried again. Maybe I’ll try a few different countries with the DD-MM-YYYY, or even a few DD.MM.YYYY date formats. Still, every time I opened up that custom dialog, the date was always in the DD/MM/YYYY format.


image


This would still show up in the dialog as 20/08/2013. It looked like the value was being cached. This is not what we’re looking for.

Fiddler traces and logging to the browser’s developer console were both showing the same thing even though I had changed the user settings about 10 times today. Since this is a setting that most users will never change, and probably will change it a couple times per lifetime max if they do, I figured CRM was caching the value.

I spoke with several colleagues to see if that was the case. They were seeing the same things I was and it certainly looked like caching. However, they were pretty sure nothing in our code or CRM would cache values like that. We were pretty stumped but thought to try one more thing - let’s find a setting where the format is radically different. So we found the English (South Africa) setting where the date format was YYYY/MM/DD.


image

 

For this format, the date showed in the YYYY/MM/DD format on the dialog. So this is obviously not a caching issue. However, a colleague pointed out that there was also a dateseparator field in the usersettings entity. I updated the code to not only use dateformatstring, but to also use dateseparator, and everything started working the way we wanted!

Here are 2 things I learned from this experience:

  • When dealing with CRM, if it looks like a field’s value is cached, it most likely isn’t (Although keep in mind that Entity Metadata sometimes is)

  • In order to get the “date format” from the usersettings entity, you need to grab both the dateformatstring and dateseparator field.  The dateformatstring contains order of the month, day and year, but ALWAYS separates them by a slash.  The dateseparator field contains the separator character that you should use to replace that slash with.


    Using the XrmSvcToolkit, we came up with the following method:

function getusersDateTimeFormat(userID) {
    var result;
    var fetchXml = [
           '<fetch>',
               '<entity name="usersettings">',
                          '<attribute name="dateformatstring" />',
                    '<attribute name="dateseparator" />',
                          '<filter>',
                        '<condition attribute="systemuserid" operator="eq" value="', userID, '" />',
                          '</filter>',
               '</entity>',
           '</fetch>'].join('');
    XrmSvcToolkit.fetch({
        fetchXml: fetchXml,
        async: false,
        successCallback: function (result) {
            result = result.entities[0];
        },
       errorCallback: function (error) {
            throw error;
        }
    });

    var seperator = result.dateseparator;
    return result.dateformatstring.replace("/", seperator).replace("/", seperator);
}

Hopefully the time we spent on this will save you from having to go through the same debugging steps that we did.  Enjoy!

Topics: Microsoft Dynamics CRM Microsoft Dynamics CRM 2011

How Much Will I be Paying For CRM?

Today’s guest blogger is Jacob Cynamon-Murphy, a Sales Engineer at Sonoma Partners.

You may have heard that Microsoft announced updated pricing for Microsoft Dynamics CRM Online licenses, slated to roll out this fall with the CRM 2013 release.  What you are about to read below may save you thousands of dollars over the life of your subscription.

Historically, the list price for a Dynamics CRM Online user license was US$44/mo.  If your firm had a special purchase agreement, typically related to volume purchasing, that rate could go down.  For the sake of this evaluation, I'll assume that you are paying face value for your licenses.

The new rates are as follows

  • Professional (US$65) - For the core CRM users, who need the full capabilities of Microsoft Dynamics CRM including sales force automation as well as marketing and customer care.  We believe most users will find this license best fits their needs.
  • Basic (US$30) - For sales, service and marketing users who need to manage accounts, contacts, leads, cases and access custom applications as well as for business analysts who require reporting capabilities.
  • Essential (US$15) - For light-weight users who need to access custom applications developed in house or by our vast network of partners.

The included descriptions have been extracted from Microsoft's recent article about the pricing change.  At this time, the descriptions are not yet explicit about what is included in the lower tiers, so my goal is to help you think more abstractly about the pricing change and be prepared when more detail is available.

Professional will offer everything for which you are paying $44/user/mo.  Rather than look at that as a pricing increase, ask yourself this - how many of your users need full access?  Now is the ideal time to start thinking about your users differently.

  • Which users are power-users, with extended privileges to customize or administer the system?  These users should be licensed as Professional.
  • Who are your active, non-power users?  These are likely your CRM workhorses, actively using the system but not pushing CRM to its limits.  These users should be licensed as Basic.
  • Who is an occasional user of CRM?  Are they only using custom entities?  Perhaps they use CRM for a function other than sales, marketing or service?  These users should be licensed as Essential.

But what if you have no idea how your users are using the system?  When I began writing this post, I didn't have any idea how I was going to conclude it, but half-way through, it became clear - you need a way to measure your user activity and classify who might belong in each of the license tranches.  Turns out, Sonoma Partners has a custom solution that we've made available to customers of ours - Control Tower.  It's good for more than aiding in user adoption; with a little help from us, you can be collecting the necessary data to know which license type each of your users should have.  Contact us at Sonoma Partners and we'll help you make the right licensing decisions.

Topics: Microsoft Dynamics CRM Microsoft Dynamics CRM 2013