Saturday, June 30, 2007

Deploying Embedded Code in Reporting Services

Yesterday I was setting up some reports in SQL Server 2005 at a client. I was to make a tabular report based on an MDX-statement. To get all the information intended I had to make lookups in the database to e.g. get the name of the customer and things like that.


The solution was to embed some code in the report, which made a query to the sql server database containing a Data Warehouse and then returning the result to the actual row.


You can embed code in your reports by viewing the properties of the report (right click the report surface and select properties). The resulting window contains a tab called Code.



If I then had to use my function DoCracyStuff(), I would do this by using an expession in the report looking like this: =Code.DoCracyStuff("param1")

In my scenario yesterday I had to use a sql connection and issuing a query towards the sql server. That was easy done using ADO.NET and System.Data.SqlClient.SqlConnection and SqlCommand. The only thing to do was adding a reference to System.Data at the References-tab.
Everything worked as expected. I got the details that I wanted. At least when using the preview function of the report designer.

I continued doing some more reports and when the day had come to its end and I was almost headed for the weekend, I thought it was time to deploy the reports to the reporting server. Drama! At every place in which I used my embedded code the resulting text was #ERROR. Too bad!
Googling a bit gave me more a less the answer.. Reporting Services is using the Code Access Security features of the .NET runtime, which retrospectively makes perfectly good sense. Reporting Services gives the user the opportunity to deploy his or her own reports, which gives everyone the opportunity to write code in the custom code tab. This could lead to a serious vulnerability. However by using Code Access Security Reporting Services managed to only allow the most simple operations.
Returning to my scenario, this is really a pain in the ass... I had to change the Code Access Security policy - the reports are located internally at the company and everyone has access to the database server, so giving them the opportunity to execute ADO.NET statements against the SQL Server won't cause any trouble.

So how do you change RS to allow queries to SQL Server?
You have to change the policy file of the reporting server, which you find here (by default installation):
%ProgramFiles%\Microsoft SQL Server\MSSQL.3\Reporting Services\ReportServer
the file to edit is: rssrvpolicy.config.

In this config you have to make a new Named Permission Set. So at the Named Permission Set Node insert the following:


<NamedPermissionSets>

....

<PermissionSet class="NamedPermissionSet" version="1" Name="RSDataAccess" >

<IPermission class="SecurityPermission" version="1" Flags="Execution" />

<IPermission class="SqlClientPermission" version="1" Unrestricted="true" />

</PermissionSet>

....

</NamedPermissionSets>

The new permission set includes the SqlClientPermission, which allow Reporting Services to run ADO.NET code.

The unrestricted="true" attribute is for allowing all calls, but you could refine this to lock down the user a bit more. Look at the MSDN docs here: http://msdn2.microsoft.com/en-us/library/system.data.sqlclient.sqlclientpermissionattribute_properties.aspx

So now you have a permission set. Next step is to apply this to the policy for embedded code.

This is the first code group below the xml statements above.

It should look like this:

<CodeGroup class="UnionCodeGroup" version="1" PermissionSetName="RSDataAccess" Name="Report_Expressions_Default_Permissions" Description="This code group grants default permissions for code in report expressions and Code element. ">

The task is to change the PermissionSetName from Execution to RSDataAccess.
After this your reports will be able to make calls to ado.net.

Someone will sure note that I could do the same by using custom assemblies, and only grant the necessary code access security rights for this assembly only, but I find this solution easier to manage right away, when the only permissions needed are those for SQL Server.

16 comments:

abhi_viking said...

Thanks a lot man for such a lovely post, This post really helped me, U Rock !!!

abhi_viking said...

hey man, I tried following the steps mentioned in this blog, but after applying the last step ie; "The task is to change the PermissionSetName from Execution to RSDataAccess." I am getting this error "Failed to load expression host assembly. Details: Could not load file or assembly 'expression_host_3667b8325d2142f18c4fa10691618a8f, Version=10.8.2047.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. Failed to grant permission to execute. (Exception from HRESULT: 0x80131418) (rsErrorLoadingExprHostAssembly) " whenever I try to run report on ReportServer.

Any Idea how to solve this.

Regards,
abhi_viking

Mogens Nielsen said...

Hi Abhi_viking,
Thank you for your comments.. I found an error in the code in the post..

A comma has found its way into this line at the Flags-attribute : <IPermission class="SecurityPermission" version="1" Flags="Execution," />

Just delete the comma after Execution and it should work.
Hope this helps...

I will update the post to reflect this change shortly.

abhi_viking said...

Hi Mogens Nielsen,

I made the change(removed comma) u mentioned. Report now works as expected. :)

Thanks a Lot!!!

Mogens Nielsen said...

I'm glad to be able to help you out.

Anonymous said...

Incredible post!!! I have been banging my head against this problem for a while now. Thanks!

Anonymous said...

Thank goodness I ran across this after a 5 minute google post!!!

Anonymous said...

Hello
Thanks for the Post. here is my scenario

I have references to System.data and System.XML. I have added the PermissionSet. But when I add codegroup it says report server is not available. Also which class should I refer to ? I don't think I should refer this class="SqlClientPermission". All your help will be appreciated.

Thanks

Mogens Nielsen said...

As others have managed to get the above instructions working, I suspect that your config-file is corrupted somehow. If you like you can email me the file (rssrvpolicy.config) in the state where it's not working for you. I may be able to help you then.
You can write me at mogens (dot) nielsen (at) gmail (dot) com

Mogens

Anonymous said...

This was a very helpful conversation. I will add to it that I experienced a similar set of symptoms (embedded code that worked in the IDE but not on the report server after deployment) after migrating a report from RS2000 to RS2005. Both versions have Code Access Security as I understand it, but things somehow worked differently in 2005.

The issue I discovered was that, although many examples of embedded code show the use of the Fields object as a parameter passed to embedded functions with the parameter's type as "Object", RS2005 would not tolerate this. Even after futzing with the rssrvpolicy.config file (going as far as setting the embedded CodeGroup to FullTrust) I still received #Error when the report was rendered.

I remembered from some time in the past that the Fields collection actually has a real, full class name: Microsoft.ReportingServices.
ReportProcessing.ReportObjectModel.
Fields

When I changed my function definition to receive the Fields collection as an object of this type (rather than as just a generic object), my problem was solved. It sort of makes sense in that I would expect this particular class to be trusted, as its part of the core system. But I still think maybe there was some casting that was cool in RS2000 that's NOT cool in RS2005. I usually prefer Option Explicit and Option Strict anyway, just to keep myself out of trouble. But I really wish the IDE and the Report Server were more consistent out of the box in what is allowed or not allowed -- an also wish there was an easier way to get some feedback out of the logs about what exception or failure was causing the #Error. I looked there first and found nothing. It was only my good fortune to stumble on this post that got me thinking about these issues.

Thanks very much.

Anonymous said...

hi M. N.
i use embedded code to run a LDAP query to check permisions for diferent items on the report. the code is in vbscript. i made the changes in the config file - still get the error - i asume it has to do with the sqlclient... any idea what and where to change? thanks

Anonymous said...

This is fantastic!!!... I love it... It works perfectly when I'm logged onto the server. However, when I open the report on the client side I get an error: "Login failed for user '(null)'. Reason: Not associated with a trusted SQL Server connection". I'm using Integrated Security in my connection string and I don't know how to impersonate the current user into embedded code.

Any idea?

Thank you.

Anonymous said...

Hi

Can you please tell me what I need to do for the following error:

'Request for the permission of type 'System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed.'


I get this error for embedded code in a report where I'm trying to access a file.

I tried using a variant of the code provided above but to no avail.

Thanks

Vasant said...

Hi,
Great post.
But i am getting the error like:
"Invalid Xml; can only parse elements of version one."

Any idea??

Anonymous said...

Thanks a lot,after two days of absolute misery, I came across your post, very clear instructions..

Cate said...

After Googling for hours and coming up with nothing but info on how to set up rssvrpolicy.config for a custom extension, I was thrilled to come across your blog entry.
Thanks Mogens!