Pages

Showing posts with label Reporting. Show all posts
Showing posts with label Reporting. Show all posts

Tuesday, November 18, 2014

CRM_URL Manipulation

There is a well known technique for opening an entity form from an SRS report. For example, you can create a hyperlink to the underlying CRM contact record using the following syntax:

=IIF(IsNothing(Parameters!CRM_URL.Value),Nothing,Parameters!CRM_URL.Value & "?ID={"&Fields!contactid.Value.ToString()&"}&LogicalName=contact")


However if you want to create a link to a CRM record without an underlying reference ID to pass then things can become a little more tricky. This is because while you require the server reference provided by the CRM_URL variable (shown in green below), you need to get rid of the "CRMReports/viewer/drillopen.aspx" section as that throws it off (shown in yellow).


This can be achieved by using the following function in the URL action:
=Mid(Parameters!CRM_URL.Value, 1, InStr(Parameters!CRM_URL.Value, "/CRMReports"))

Now you can rebuild the URL anyway you want. For example, you can construct a URL as follows:


And this will open up a new CRM record (with passed defaults etc.).


Tuesday, March 18, 2014

Run Report button missing on Forms

We noticed that certain forms in a CRM 2013 upgraded installation were missing the embedded "Run Report" option in the command bar. It appears in some forms but not in others... with no apparent rhyme or reason. For example, below is a screenshot of the upgraded opportunity form:



Anyone see the Run Report option? Neither do I... and the reason cannot be attributed to there not being any context sensitive reports for opportunities. There indeed are!


Yet... it does appear in the command menu for the grid view. Go figure.

I found this post which pointed me in the direction of the resolution. It references two options for resolution. One by directly modifying the export XML for the entity. And the second by using the Ribbon Workbench tool.

The first solution is the way to go. Quicker and easier and does the job. That is, you just need to add the following command definition to the RibbonDiffXml of the entity.

<CommandDefinition Id="Mscrm.ReportMenu.Form">
    <EnableRules>
      <EnableRule Id="Mscrm.FormStateNotNew" />
    </EnableRules>
    <DisplayRules>
      <DisplayRule Id="Mscrm.ReadReport" />
    </DisplayRules>
    <Actions />
</CommandDefinition>

After importing and publishing, the reports have returned to their rightful place (ok I know the report icon is still missing but that is a battle for another day).



Thursday, December 19, 2013

Send email with PDF attachment

Frequently there is a need to send out an email from CRM containing a generated PDF attachment. the following approach can be used to allow an end user to manually initiate the process using the click of a button via the user interface.

But what if we wanted this to happen automatically rather than relying on the user to manually execute the action? From my experience this is by far a more common scenario. A good example might be auto-generating an invoice and then emailing it to a customer once an invoice has been approved. Another one might be to generate a quote and have it sent off once the quote has been approved. The natural solution that we can look to in such a scenario would be to leverage the CRM workflow capability.

The first step would be to design some kind of report that would be used as the template for generating the PDF document. Once this is in place we can leverage the technique outlined in my previous post for publishing the report externally and making it context sensitive which is a key requirement.

This allows us to design a workflow where we can construct a URL that will produce a report for the record in question. For example, we can design a workflow as follows:


  1. Generate GUID for the record against which the workflow is running (using the workflow productivity tools)
  2. Create the URL passing in the GUID generated above as a dynamic parameter i.e. http://crm2011db01/ReportServer/Pages/ReportViewer.aspx?%2fCRMEZE2011TEST_MSCRM%2fUser+Summary&CRM_FilteredSystemUser=select  [systemuser0].*  from  FilteredSystemUser as systemuser0  where  (systemuser0.systemuserid = N'%7B{0}%7D')&rs:Command=Render    [%7B and %7D are decodes for the { and }. And {0} is the parameter to be replaced by Param 1]
  3. Send email using the link generated above




Finally, you can also pass in the same URL and use the technique mentioned in this post to convert the report to a PDF and send as an attachment via email.

Note: Make sure that the service account under which the the asyncronous service is running has access to the SSRS report folder containing the externally published CRM report.

Wednesday, December 18, 2013

Publish External Reports - Dynamic Content

Time to complete what I started... So in my last post we walked through the steps to make an externally published report accessible to non-CRM users which would seem to be the objective of the whole publishing externally exercise as documented in the introduction to this topic.

However by following those steps we are limited to a fairly static report based on the report parameters of the originally published report. This is going to be of limited benefit. For example, consider the scenario where I want to include a link via an email to a non-CRM user that would allow them to view data from within CRM - for instance - we might want to send them a report on an opportunity whereby clicking on the link they are able to view the opportunity details. Well, unless we're able to pass in parameters to this published report we will be prevented from doing so i.e. it will lack the necessary context sensitivity.

In order to overcome this limitation we'll be manipulating the CRMFilteredEntity report parameter mentioned in my previous post. We'll continue using the example of the User Summary report for the sake of illustration.

The first thing to do is to navigate to the report using the Report Server navigation i.e.:


Now click on your externally published report (in our case "User Summary") and retrieve the execution URL i.e. http://crm2011/ReportServer/Pages/ReportViewer.aspx?%2fCRM_MSCRM%2fUser+Summary&rs:Command=Render and you should be presented with something that looks like this:


Now take the URL and carefully graft on the value from the CRMFilteredEntity parameter (provided in my previous post). That is, your URL should now include the CRM_FilteredSystemUser parameter and look something like the following:

http://crm2011/ReportServer/Pages/ReportViewer.aspx?%2fCRM_MSCRM%2fUser+Summary&CRM_FilteredSystemUser=select  [systemuser0].*  from  FilteredSystemUser as "systemuser0"  where  ("systemuser0".systemuserid = N'{43AF6573-C97C-E011-9556-00155DA5304E}')&rs:Command=Render

And voila - your report will now render the User Summary report only returning the corresponding user specified by the report parameter.


Also test with a non-CRM user to ensure that this is working outside of CRM authentication.

This exercise proves that we can now manipulate the URL of this report to pass in parameters to the report. Essentially all you need to do is to update the query specified in the CRM_FilteredEntity parameter and this will filter the results of the report. Of course the same approach can be applied to any other report in the system. For example, if we were working with an account report we'd manipulate the query for the CRM_FilteredAccount parameter; if we were working an opportunity report we'd manipulate the query for the CRM_FilteredOpportunity parameter. And so on and so forth.

Now of course this result doesn't lend itself very well to specifying report parameters manually - your users would lynch you if you tried proposing this to them!  But it does lend itself quite well to automated mechanisms such as via workflow whereby you can construct a URL using dynamic workflow fields resulting in a report URL in an email that a user just has to click on in order to see the relevant, context-sensitive, dynamic report content.

In the next post we'll take a look at performing those steps as well as seeing how this approach can also be leveraged to generate PDF documents that can be sent to external users or customers.

Tuesday, December 17, 2013

CRM 2013 Report Error: "An error has occurred. Try this action again..."

We were getting an odd error when running a CRM report in an environment that had been upgraded from CRM 2011 Online to CRM 2013 Online.

In short, we were getting the following rather unhelpful generic error message:


This error only occurred with a few of the records but ran fine for other records. In order to troubleshoot, I removed all the front end components of the report so that essentially the report would just have a blank rendering. And even after doing this, the report still gave up the error above.

So this seemed to point to the fact that there was some issue with the data set...

As it happened this data set had a link-entity join clause and I discovered that when you added in the linked record such that the join would succeed the report would run fine. And in cases where the join did not bring back results the report error would occur - this being our particular case.

So this explained why it occurred only for certain records whereas not for other records in the system. However it does not explain why this error occurs in the first place. At the most I would expect the report to not render results when a join does not bring back results. After all in many cases it is perfectly valid to have a join that does not succeed. Even if there was an error caused by the lack of the join, I would expect to see this error manifesting itself in the returned report as is the case for example when running a report where the sub-report it is calling does not exist.

All in all, while I'm glad I discovered the issue, this does seem very much like a bug in the platform to me.

Wednesday, September 18, 2013

Publish External Reports - Credentials

My previous post introduced the topic of publishing reports for external usage and the conclusion drawn was that - as far as I can tell - the only reason why this action would be performed would be to grant non-CRM users a view into CRM data. But unless we make some changes to the report credentials, external users will be prevented from running the report. This post describes how to go about performing this step.

We'll use the default "User Summary" report for the sake of this exercise. In order to demonstrate how this works end to end, first modify the Default Filter for this report and arbritarily add some filter criteria. We'll add a filter by user name.


Subsequently edit the report and select the "Publish Report for External Use" option. We'll publish the default "User Summary" report for the sake of this exercise.


The report will now appear in the CRM report server home page (http://CRM/Reports/Pages/Folder.aspx?ItemPath=%2fCRM_MSCRM&ViewMode=Detail).In its current state it will only be accessible to CRM users. In order to make this accessible to other users we need to have it impersonate a user who has the necessary credentials to run the report. By doing so, you will actually be giving permission to anyone who has the URL to run the report.

To do so, hover over the report name and click Manage from the menu that appears.


Let's first take a look at the report parameters. What's interesting to note is that anything that was specified as part of the default filter will be stuffed into the single parameter called CRM_FilteredEntity (in our case CRM_FilteredSystemUser).




So you'll see something like the following appear for this parameter based on the default filter that we used:
select  [systemuser0].*  from  FilteredSystemUser as "systemuser0"  where  ("systemuser0".systemuserid = N'{43AF6573-C97C-E011-9556-00155DA5304E}')

The "systemuserid" is the identity (guid) of Adam Barr's user record (see above). If we had instead specified a default filter of "last name = Smith" then this parameter would like like this:

select  [systemuser0].*  from  FilteredSystemUser as "systemuser0"  where  ("systemuser0".lastname = N'Smith')

You get the picture. Understanding how this parameter is constructed will feature prominently in the next related post. Now let's move onto updating the report credentials.

Click on "Data Sources" and create a custom data source and specify a user that has the necessary permissions as shown.



Alternatively, create a custom data source using the same input as shown above and reference it using the "a shared data source" option shown above. This approach is the recommended approach because if you have more than one external report, you can just link to the same shared data source for each rather than specifying the same credentials in each case (which also helps with maintenance as passwords need to change and such like). And finally bear in mind that if your report has sub-reports then you will already have more than one report that needs to have the credentials updated (yes, all generated reports need to have this applied).

Now that you've performed this action, test the report by running it with a non-CRM user and it should return a report that looks something like what we have below. Note how the result is being limited to the default filter defined for this report in CRM.


Next: We will build on this walk through by manipulating the parameters of the report in order to have the output be dynamic and context sensitive versus the static report output that we've just presented. Being able to do so provides all kinds of interesting options.

Tuesday, September 17, 2013

Publishing reports externally

It's been a while since I last posted. Things got a little busy over the summer. Anyway, during that period I collected quite a few tidbits here and there and hope to be posting them over the course of the next few weeks. We'll see how it goes.

As anyone who has been using CRM for a while knows, CRM reports can be published externally as shown in the screenshot below. Note: this option is only available to CRM On Premise since there is no report server accessible via online.


After the report has been published it is copied to the Org_MSCRM home page in the report server i.e. http://reportserver/Reports/Pages/Folder.aspx?ItemPath=%2fOrg_MSCRM&ViewMode=Detail. Navigating to this location you will see the result of your publish action - the report along with any sub-reports that it referenced will be published as shown. In this case, the published report contained 3 sub-reports resulting in 4 reports being published.



But the question has to be asked - seems like a nifty little piece of functionality - but what is the practical application of it? To compound it further - if you try and run this report from the reporting server location - it will only work for a CRM user that has access to the CRM database. And therefore why would you not just execute the same report from within CRM? The latter option provides you with the CRM pre-filter option and a generally much better overall user experience - so there is no reason that I can think for a CRM user to execute the report from anywhere other than from within CRM.

In light of all of the above, the only reason that I can think of for using this feature is to provide a report that can be accessed by non-CRM users. There are a number of scenarios in increasing degrees of complexity:

  1. Just provide a URL for external users to be able to access the report
  2. Use workflow to send out a report link to non-CRM users based on a given business scenario
  3. Use workflow to email a PDF document based on an externally published report

As will be demonstrated over the next few posts, each of these builds on top of one another. And an issue that we'll have to contend in both cases 2 and 3 is one of context sensitivity i.e. typically if you're automating something you're going to want it to generate a report that is specific to the quote, order, invoice etc. that it was generated from.

The first thing we need to deal with is giving access to an externally published report. Because, if you've been paying attention, there is an inherent contradiction - externally published reports cannot be accessed by non-CRM users... By default.

In the next post, I will walk through the steps for allowing external users to access the CRM published report.

Wednesday, October 10, 2012

Reports: Excel Export Formatting

When exporting an SSRS report to Excel all kinds of interesting things can happen from a formatting point of view. The report which might otherwise render quite nicely in SSRS may appear to have been attacked by a bunch of gremlins in its exported Excel format. Several things might contribute to this haphazard effect:


  1. The cells of data on the spreadsheet might be scattered all over the place
  2. Some elements of data may occupy several excel cells having been auto-merged in the export
  3. The report might export into multiple different sheets within the spreadsheet with no apparent rhyme or reason

The report below illustrates this showing cells of data being spread way out and multiple sheets being created from a single export file.


The main reason for this is that Excel is a highly structured document and it needs to fit the results into neat rows and columns. If the cells or rows are not aligned then Excel makes automatic decisions in terms or rendering the export. For example, merge cells may bring 2 sets of non-aligned data elements into alignment; expanding a column width or height may similarly bring a non-aligned element into alignment with other report elements.

Therefore it goes without saying that one of the keys to ensuring that a report renders well when exporting to Excel is to ensure alignment. This might seem obvious and is fairly easy when it comes to grid controls which are fairly structured elements to begin with. However if you look at the report above, you will notice that many of the data elements are text fields and you will need to ensure that these all align quite nicely. It might be difficult to discern with the eye - for example, a location of a cell might be 2.51322 vs. 2.5 and that small discrepancy can throw things off in the export to Excel. The following guidelines should therefore be used when creating SSRS reports (in general in terms of overall quality but obviously with extra emphasis when you know the report will be exported to Excel):

  • Ensure alignment - you should use the text box properties to ensure that cells or aligned exactly
  • Use rectangles extensively for grouping. It is quite common for different parts of a report to have different formatting structures. Using rectangles for grouping helps excel cope with changes to the format.
  • If you are using sub-reports in your main report - ensure the page size of your sub-report adheres to the size of the parent report and once again - I'd recommend using a rectangle to enclose the sub-report
  • Be careful with page breaks before or after sections. When excel encounters a page break it will create a new sheet.

One other important feature to be aware of is that you can also control the name that Excel assigns when creating a new sheet. As illustrated in the screenshot above - the export created default Sheet1, Sheet2 etc. sheet names. You can refer to the following post for a detailed write up of this feature (only available in RS 2008 R2). In my case, I just entered the page details as follows:


The end result is much cleaner looking export:




Thursday, July 26, 2012

Cannot delete reports

On occasion I have encountered an error while trying to delete reports from the CRM user interface. The specific error message being: "Error occurred while deleting an item from the report server":


The error details are as follows:

Unhandled Exception: System.ServiceModel.FaultException`1[[Microsoft.Xrm.Sdk.OrganizationServiceFault, Microsoft.Xrm.Sdk, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]: System.Web.HttpUnhandledException: Microsoft Dynamics CRM has experienced an error. Reference number for administrators or support: #9B008CA6Detail:
<OrganizationServiceFault xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/xrm/2011/Contracts">
  <ErrorCode>-2147220970</ErrorCode>
  <ErrorDetails xmlns:d2p1="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
  <Message>System.Web.HttpUnhandledException: Microsoft Dynamics CRM has experienced an error. Reference number for administrators or support: #9B008CA6</Message>
  <Timestamp>2012-07-26T08:26:48.1031182Z</Timestamp>
  <InnerFault>
    <ErrorCode>-2147187945</ErrorCode>
    <ErrorDetails xmlns:d3p1="http://schemas.datacontract.org/2004/07/System.Collections.Generic" />
    <Message>Error occurred while deleting an item from the report server.</Message>
    <Timestamp>2012-07-26T08:26:48.1031182Z</Timestamp>
    <InnerFault i:nil="true" />
    <TraceText i:nil="true" />
  </InnerFault>
  <TraceText i:nil="true" />
</OrganizationServiceFault>

The error seems to be caused by the fact that the report has lost its reference back to the reporting services report repository (i.e. CRM org folder under http://<server>/reports).

The easiest way to deal with this issue is to simply upload a working report definition into the errant report. And subsequently you should be able to perform the delete operation.

Tuesday, June 19, 2012

Advanced SSRS Reporting Part 3

Continuing with the advanced reporting series let's take a closer look at context sensitivity. In the previous example the report was made context sensitive to just a single CRM entity, namely the marketing list. What happens if you we wanted to make this context sensitive to multiple different entities? For example, let's say we have the following 2 parameters defined:




If the main query can be constructed to join against the various tables such that the context sensitivity will restrict the query sufficiently no matter which entity the report is launched from then that should work. However that is probably not going to be the case in most cases. And if you don't take the both parameters into account (and what happens when one is not passed), then you'll likely find that the query will run very inefficiently when run from the entity which was not taken into proper consideration. That's the best case. The worst and more likely case is either that the report will time out or it will return incorrect data due to not restricting the query sufficiently.

This is because, when a report is not run from within the context sensitive form or grid, the "default filter" is used. So for example, if the default filter is set as follows:



And the report is run from the "contact" context then the variables will be populated as follows:

CRM_FilteredList:

select  [list0].* from FilteredList as "list0" 
where ( list0.modifiedonutc >= '20120520 04:00:00' and list0.modifiedonutc <= '20120619 15:39:16' )

CRM_FilteredContact:

select  [list0].*  from  FilteredContact as "list0" 
where  ("list0".contactid = N'{92F129FB-4099-E111-9EFF-006073502237}')

That is, the marketing list query will be quite unrestricted (there would be no restrictions at all were there no default filters acting on it at all) whereas the contact query is restricted to the contact that is currently being viewed. This is of course expected behaviour (and the situation would be reversed were the report being run from the marketing list context).

So other than creating 2 separate reports for each of the queries - how can the procedure accomodate context sensitivity from both contexts?

The answer lies in interrogating the above variables from within the procedure. As can be seen, when the report is being run from the contact context the CRM_FilteredContact variable will contain "contactid" as part of the string. Similarly when running from the marketing list context the CRM_FilteredList variable will contain "listid" as part of the string. When not in context, the "id" field will not be part of the string.

Armed with this knowledge we can make a simple modification to the report processing logic as follows:

if (@CRM_FilteredList like '%listid%')
 set @SQL = 'insert into #report(ord, contactid, fullname, processed) ' +
    'select (ROW_NUMBER() OVER(ORDER BY c.Fullname))*10, c.contactid, c.fullname, 0 ' +
    'from  (' + @CRM_FilteredList + ') AS list ' + 
    'inner join FilteredListMember m on m.listid = list.listid and EntityType = 2 ' +
    'inner join FilteredContact c on c.contactid = m.EntityId '
else 
 set @SQL = 'insert into #report(ord, contactid, fullname, processed) ' +
    'select 0, c.contactid, c.fullname, 0 ' +
    'from  (' + @CRM_FilteredContact + ') AS c ' 


And the report will execute different queries depending from which context sensitivity it is run.

And once again the sky is the limit.

Monday, June 4, 2012

Embedding Reports in a Navigation Link

Now that we've explored how context sensivity can be added into a form Navigation Link, we can devise yet another approach for embedding reports into a CRM form and this time - you guessed it - via the form Navigation Links.


The first thing you'll need to do is of course create your report. As we're going to integrate this with the CRM form, you may want to have the report have a similar look and feel to CRM's native look and feel. Once you're happy with your report obtain the report GUID.



The next thing you'll need to is create an html web resource to contain the report.
 










Unlike the web resource that is used for embedding reports within an I-Frame, we need to update this report and supply the GUID for the report that you wish to integrate (copied from above) i.e. you'll need to modify the following:
var reportId = "ReportGuid";
Subsequently, once you have created the web resource, add it to the form and publish:
The result should be something that looks like the following:

Wednesday, May 23, 2012

Report not showing under "Run on Current Record"


This has happened to me on a number of occassions. I modify an existing report making sure it adheres to all the CRM SDK reporting standards and in the report definition I make sure to specify that he appropriate entity/entities are entered in the "Related Record Types" and that the "Display In" is set to "Forms for related record types" and/or "Lists for related record types".

Yet when I open up the entity form (in the example above this would be a Marketing List record), the report appears under the "Run on All Records" section rather than under the "Run on Current Record". While this would be ok had the report not been linked using the context-sensitive "CRMAF" nomenclature - in this case, the report has been written such that it should be context-sensitive. So why does it show in the wrong section?



The behavior that I've observed is that if the report is created initially such that it does not adhere to context-sensitivity, it will rightly appear under the "Run on All Records" section. Later on, if you modify the report definition to include context-sensitivity it will still remain in this section.

If that is the case simply deleting and recreating the report will resolve this issue. At least that's been my experience.

Wednesday, May 16, 2012

Embed Charts in a CRM Form

While still on the topic of reporting in general and more specifically with regards to reports embedded on the CRM form we should also briefly mention the very powerful out-of-the-box charting option enabled in  CRM 2011.

My previous post comes in handy when you have a reporting or charting requirement that cannot be modelled using the CRM charting overlay on views.

If however your visualization can be achieved through the CRM charts then you can embed this into a form without having to create a custom report as illustrated by the following screenshot:




Presuming that you have already defined the chart view in CRM, the way to achieve the above is very simple. Simply select the "Insert Subgrid" option in the form designer and toggle the Chart Options as shown.


Monday, May 14, 2012

Embedding Reports in a CRM Form

In CRM 4.0 we were able to embed CRM reports into directly into an entity form via the use of an IFrame. This old post walks through the configuration of that option. This is useful in several instances, such as:

  • Creating charts, tables, etc. to be able to easily visualize the data (e.g. a chart showing the total purchases by product line for the account being viewed right in the body of the form)
  • Pulling information from other applications into a form - a kind of "virtual" integration

Fortunately this can also be accomplished in CRM 2011 using html web resources.

We will start by referencing a Microsoft team post that walks through the process of embedding reports on a CRM 2011 dashboard (very useful in and of itself). This post references an html web resource that can be downloaded and used to configure the dashboard option.

The only difference between embedding a report into the dashboard versus is embedding into a form is "context sensitivity". That is, by it's very definition a dashboard is a high level view of data to enable the data to be visualized, analyzed and drilled down into. While the same kind of analysis could apply at the form level (e.g. viewing a summary of orders, invoices etc. for a particular customer), the report at the form level should (in most cases) be showing data that is linked/relevant to the entity that you are viewing.

So with that in mind, all we need to do with the html web resource referenced above is context sensitivity and then we can leverage that for to embed CRM reports into a form. And this is precisely what we have done - you can download the solution containing the html web resource for displaying embedded reports from here.

Once you have done so, all you need to do display the embedded report is to insert the web resource into the form as shown and to pass in the report ID in the Custom Parameters (see Microsoft's post on how to obtain the report ID).



 After publishing, open your form and you should see something that looks like this:



It would be nice too if we could hide the report header information (i.e. everything shown in blue in the screenshot above) however that appears to be somewhat challenging. If you're using a reporting services URL then you can tack on parameters such as the one below to suppress this (see the following post for a complete list of reporting services URL parameters):

&rs:Command=Render&rs:ClearSession=true&rc:ToolBar=false


However if you're trying to render the report using the "/crmreports/viewer/viewer.aspx" approach (which is how all reports natively get called from CRM and therefore is preferred and is also used that way in the web resource), then the parameters above will not work. If anyone has figured this one out I'd appreciate if you could enlighten me.

Friday, May 11, 2012

Advanced SSRS Reporting Part 2

The following post walks through what can be viewed as a kind of "template" for advanced reporting in MSCRM as introduced in my previous post. This example leverages the reporting scenario that I described there by way of example. However, the point of this post is to describe the relevant parts of the template rather than describe the processing logic relevant to that specific example - the processing logic will of course vary greatly from report to report so I do not want to focus on this at all as that is besides the point.


I have segmented the relevant parts of the template into Sections A-G - the entire declarative statement which is input as a Data Set into the report appears at the bottom of this article - you should reference that in terms of the rest of this article. The following screenshot shows how this statement ties into the SSRS report.




  • Section A - The majority of this report effort is probably going to be in terms of retrieving and formatting the source data that you want to output in the report. So you need to have a way of testing it outside of the report to ensure that it's retrieving the correct data inside SSMS (SQL Server Management Studio). As the parameter that this report relies on is "@CRM_FilteredList" we define this variable here which should pretty much mirror how the variable is defined in SSRS (see below - this format also adheres to the CRM SDK reporting standard). This section is commented out as it is only used for testing in SSMS. It should be uncommented when testing the declarative statement but should be commented out when inputting in SSMS (please do not forget to comment out this section when inputting into the report!)


  • Section B - Create a temp table for processing the report data. This is not required in all cases of course but comes in very handy when performing complex processing.

  • Section C - Populate the temp table with a query that will be used as the basis for subsequent processing logic.

  • Section D - This command executes the query in Section C

  • Section E - Run a cursor to process through the records that were populated into the temp table

  • Section F - Perform the processing logic specific to your report including lookups, updates, deletes etc. and update your temp table as necessary.

  • Section G - Once your temp table has been updated via the above processing logic output the results joining to other CRM views to bring in whatever additional attributes are required. Remember to join using the CRM filtered views.

And that pretty much is that. Once you have this data you can then input as a Data Source into your report and use whatever fancy reporting techniques you want for rendering your report. As long as you adhere to using filtered views and respect the naming conventions of the CRM parameters, you should be able to add this report in a context sensitive ways to forms and views with the standard Advanced Find pre-filtering.


/*
SECTION A: THIS IS FOR TROUBLESHOOTING THE REPORT IN SQL:

drop table #report
declare @CRM_FilteredList nvarchar(100)
set @CRM_FilteredList = 'select * from FilteredList where listid = ''92F129FB-4099-E111-9EFF-006073502237'''

*/
declare @SQL nvarchar(max)
BEGIN
 /* SECTION B: CREATE TEMP TABLE */
 create table #report
 (
  contactid   nvarchar(36),
  fullname  nvarchar(100),
  processed       bit,
  ord             int,
  inlist          bit
 )

 
 /* SECTION C: FIRST OBTAIN BASIC REPORT DATA */
 set @SQL = 'insert into #report(ord, contactid, fullname, processed, inlist) ' +
    'select (ROW_NUMBER() OVER(ORDER BY c.Fullname))*10, c.contactid, c.fullname, 0, 1 ' +
    'from  (' + @CRM_FilteredList + ') AS list ' + 
    'inner join FilteredListMember m on m.listid = list.listid and EntityType = 2 ' +
    'inner join FilteredContact c on c.contactid = m.EntityId '

 /* SECTION D: EXECUTE ABOVE STATEMENT WHICH POPULATES THE #report TEMP TABLE */
 exec(@SQL)

 /* SECTION E: RUN THROUGH REPORT TEMP TABLE RECORDS */
    declare @cid     nvarchar(36)
 declare @sid       nvarchar(36)
 declare @fullname  nvarchar(100)
 declare @ord       int
 declare @inlist    int
    
 declare ProcessTemp cursor for
 select r.contactid, con.record2id, con.record2idname, r.ord
   from #report r
 inner join FilteredConnection con on con.record1id = r.contactid and con.record1objecttypecode = 2
 inner join FilteredContact c on c.contactid = con.record1id
 where con.record2roleid in ('2C110BC6-168D-E111-8DD6-006073502237','EE375944-5415-437D-9336-7698CF665B26')  /* Partner/Spouse */
 order by r.ord
 
 open ProcessTemp
    fetch ProcessTemp into @cid, @sid, @fullname, @ord

 while @@fetch_status = 0
 begin
  /* SECTION F: PERFORM WHATEVER PROCESSING LOGIC IS NECESSARY - INSERTS/UPDATES/DELETES.
             THE PROCESSING BELOW IS JUST SAMPLE PROCESSING - THIS COULD OF COURSE BE ANY LOGIC
  
  */
  update #report
  set processed = 1
  where contactid = @cid
  and processed = 0
  
  if (@@ROWCOUNT != 0) -- if update wasn't successful must be because contact was already there
  begin
  
   -- delete if it appears in the marketing list since it will be added above the partner record 
   delete from #report
   where contactid = @sid
   and processed = 0
   
   if (@@ROWCOUNT = 0) -- No record deleted ==> was not part of original marketing list 
    set @inlist = 0
   else
    set @inlist = 1
       
   -- insert/reinsert above partner record 
   insert into #report (ord, contactid, fullname, processed, inlist)
   values (@ord, @sid, @fullname, 1, @inlist)
  end

  fetch ProcessTemp into @cid, @sid, @fullname, @ord

 end --while
 close ProcessTemp
 deallocate ProcessTemp


 /* SECTION G: OUTPUT RESULTS BY JOINING #report TEMP TABLE TO OTHER CRM RECORDS - THIS IS WHAT WILL BE OUTPUT TO SSRS */
 select 
     r.inlist
    ,c.contactid
    ,c.firstname
    ,c.lastname
    ,c.address1_line1
    ,c.address1_line2
 from #report r
 inner join filteredcontact as c on c.ContactId = r.ContactId
 order by r.ord

END

Thursday, May 10, 2012

Advanced SSRS Reporting Part 1

In my previous post, I made the following comment with regards to SSRS Reporting:
"However the sky is pretty much the limit when it comes to SSRS Reporting as you can leverage the full capability of both SSRS and SQL in order to manipulate the data in exactly the way you want."

In this post, I'm going to expand on this concept a little. Let's use a sample requirement to illustrate this:
  • Report to be run against the contacts of a marketing list
  • Report to be ordered by contact name (fullname)
  • Contacts are related to one another by means of connections and we want spouses to break the ordering logic specified above. That is, whenever a contact has a spouse, the spouse record should appear immediately after the first spouse record (bear in mind, a spouse may not always have the same last name as their partner).
  • We even want spousal contacts to appear in the report as long as a single spouse appears in the marketing list. That is, the report is against all contacts in the marketing list + spouses who may or may not be members of said marketing list.
  • Include some tags to indicate whether the spouse was a member of the marketing list (or just brought in as a result of his/her connections!)

The problem with a requirement like the above is that it cannot be met by any simple query means. You simply cannot write a SQL query that will be able to retrieve the data and manipulate the data as specified.

So can something like the above be achieved using SSRS reporting? Absolutely!

Well of course you might say that this is fairly obvious as you can write a stored procedure to perform all the various querying, re-querying and data manipulation and finally output the results in a select statement that SSRS can subsequently manipulate. And if you said that, you would be right.

But such a report would not adhere to the CRM SDK which would not enable you to link the report into CRM forms, views and Advanced Find queries in a context sensitive manner. Nor would it be technically supported as saving stored procedures in the CRM database is... verboten.

So the follow up question is - can something like the above be achieved using SSRS reporting while accomodating the CRM SDK requirements? And the answer once again is... Absolutely!

The key point to recognize is that just as SSRS can consume a stored procedure as part of a data source it can similarly consume declarative statements as part of the data source (as opposed to queries) and those declarative statements can have all the complex processing power of Transact SQL. Once we realize this, we can leverage the full power of T-SQL to run multiple queries, inserts and updates into temp tables in order to get the data into the format that is desired before sending to SSRS for rendering.

As an aside - I personally find that manipulating the data prior to sending to SSRS and using SSRS mostly as a rendering rather than data manipulation tool is a much easier way of going about it. Especially when it comes to the more complex report requirements. And the above technique can come in most handy in this regard.

In my next post, I'll attempt to provide a template that can be used for constructing and testing the declarative statement for the report data source.

Wednesday, May 9, 2012

CRM Reporting Options

There are many and varied ways of reporting out of CRM. Most report requirements can be achieved using the CRM front end without having to resort to writing custom reports using SSRS and the like.Very briefly the following outlines the reporting options in order of complexity:

  • CRM Views and Advanced Find - Being able to defined and save Advanced Find and system queries is by far the most common type of "report" in CRM. As long as your requirements are just to return records from a single entity and the criteria for the report can be supported by the Advanced Find query options, this is the report to be using. This report has been enhanced significantly in CRM 2011 by including visualization and drill down options using the built in charting capability

  • Excel Reporting - Excel reporting is an extension of the CRM Views allowing for the data to be dynamically exported and then once you are in the Excel environment, you can use the full capability of Excel visualization to render pivot tables, charts etc. These can be saved as actual reports in the Reports area of the application as they can be connected to live data like all of the other reporting options.


  • Charting/Dashboards - With the introduction of CRM 2011, this method of reporting/visualization has to be next in terms of level of complexity (i.e. not very). Anyone who is familiar with creating charts using the likes of Excel (as illustrated above) should be able to use the CRM front end tools to create charts and, by extension, dashboards (which are just a bunch of charts, views etc. grouped together in a single presentation to present key metrics in a visualized format). Charts are of course also then used to integrate and extend the CRM views as already illustrated above.

  • Report Wizard - This walks you through the process of building a report. This is not too dissimilar from building a pivot table report. This approach is often useful as a starting point to build reports as you can then export the generated "rdl" and use SSRS to tweak the report further and add features not supported by the wizard.

  • SSRS Reporting - Typically you would use SSRS to create a custom report because the query, format, grouping, layout etc. that is required cannot be supported by any of the above. Ensuring your report adheres to the SDK guidelines will allow the report to integrate in a context sensitive manner into your CRM forms and views. Most times a SSRS report is also fairly straight forward that involves creating a query joining several tables together and then rendering the results into a table. However the sky is pretty much the limit when it comes to SSRS Reporting as you can leverage the full capability of both SSRS and SQL in order to manipulate the data in exactly the way you want.

  • Data Warehousing/Data Mining/Cubes etc. - This gets us into the most sophisticated area of reporting which typically involves extracting the operational data into a separate data warehouse and building cubes against that data to be able to create Key Performance Indicator dashboards, view trending etc. Customers typically do not get to this level of reporting until they have been using the CRM system for some time and have accrued lots of data that can be analyzed to identify interesting patterns, trends etc.There are a number of 3rd party ISVs that directly integrate with MSCRM and generally greatly simplify this activity.  

Thursday, February 23, 2012

Convert Email Body to Plain Text

If you wish to create a report on the email body within CRM, the problem is that if you just retrieve the data from the email body it will appear with all the html tags which is not too pretty to say the least.


But luckily there's an easy way to handle this. To do so, follow these steps:


  • Highlight the report expression containing the html text and select "Placeholder Properties..."


  •   Select the Markup Type option of "HTML - Interpret HTML tags as styles"


And that's pretty much it. Now the html text renders as, well... html text (not technically "plain text" as indicated by the title of this post).




Friday, December 9, 2011

CRM 2011 Upgraded Report not working

I am not sure if this is something specific in the environment that I am working but I had some custom reports that were not working post upgrade. To troubleshoot I opened up in SSRS and when I tried to run the preview, I received an error message indicating that one of the fields on the report was not in the dataset scope.


This was a "form" report (rather than grid output) and it seems the form elements were no longer associated with the parent "list" in which they were embedded. It could be that this was because this report was developed in SSRS 2005 and it was now being edited in SRS 2008 - I'm not sure.

To resolve I needed to add the "list" back into the report. The following are the steps taken to do so:


  • Highlight all report elements (ctrl+A)


  • Insert new List (tablix) into the report and drag to the size of the report body



  • Click into the list/tablix (ensure outline of tablix is showing) and paste and your report should now look something like this:

  • Update the dataset for the tablix that you just inserted

  • Try the preview again and if the report runs or you are presented with something similar to the screenshot below you should be all set