{"id":191,"date":"2006-12-06T00:00:00","date_gmt":"2006-12-04T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/building-my-first-sql-server-2005-clr\/"},"modified":"2021-08-16T15:02:25","modified_gmt":"2021-08-16T15:02:25","slug":"building-my-first-sql-server-2005-clr","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/learn\/building-my-first-sql-server-2005-clr\/","title":{"rendered":"Building my First SQL Server 2005 CLR"},"content":{"rendered":"<p>Although Microsoft has always advised us not to use them, if you are like me you still might have found reason to use one or more of the SQL Server 2000 undocumented features. In particular, I built a number of processes that used an undocumented, extended stored procedure (SP) called <b>xp_getfiledetails<\/b>, which allowed me to obtain various bits of operating system information for a physical disk file. <\/p>\n<p>Well, in SQL Server 2005, the &#8220;bad&#8221; news is that Microsoft has decided to remove some of those undocumented features, including the <b>xp_getfiledetails<\/b> stored procedure. On the plus side, SQL 2005 does bring with it a lot of new features, one of which is the Common Language Runtime (CLR). The CLR allows you to extend SQL Server functionality by building your own routines using programming languages other than T-SQL.<\/p>\n<p>So although there is no direct replacement for the extended SP <b>xp_getfiledetails<\/b>, it&#8217;s possible to build a common language runtime (CLR) object that will replicate its functionality. This article describes the first CLR-based stored procedure that I have written and implemented into our SQL Server 2005 environment, and what I learned from the process.<\/p>\n<h2>What did xp_getfiledetails do? <\/h2>\n<p>As noted, the <b>xp_getfiledetails<\/b> extended SP allowed you to obtain some operating system information for a physical disk file. To execute <b>xp_getfiledetails<\/b> you passed in a file name, and the extended SP returned the following information:<\/p>\n<ul>\n<li>short name  <\/li>\n<li>size  <\/li>\n<li>create date and create time  <\/li>\n<li>last written date and time  <\/li>\n<li>last access date and time  <\/li>\n<li>file attribute byte of the file<\/li>\n<\/ul>\n<p>I used this extended SP in a number of my T-SQL scripts and SPs to determine whether or not a file existed and to get the size of the file.<\/p>\n<h2>Building my first CLR <\/h2>\n<p>Since I had a number of processes that called <b>xp_getfiledetails<\/b> I needed a way to replace this functionality and I wanted to avoid having to modify all my existing processes. <\/p>\n<p>My solution was to build a CLR stored procedure that could transparently replace <b>xp_getfiledetails<\/b>. Microsoft has recommend that CLR&#160;code&#160;only be used in situations where they out-perform T-SQL, or where they provide functionality that is simply not available using T-SQL. It seemed to me that building a CLRbased stored procedure to replace the deprecated <b>xp_getfiledetails<\/b> would fit into the Microsoft recommendation.<\/p>\n<p>Following are the steps that I followed to get the CLR stored procedure built and installed:<\/p>\n<ol>\n<li>Write the .NET (C#) code that would perform the same functionality as <b>xp_getfiledetails<\/b>  <\/li>\n<li>Compile the .NET code  <\/li>\n<li>Enable CLR environment&#160;in the database  <\/li>\n<li>Create an assembly in the database based on the compiled .NET code  <\/li>\n<li>Create a CLR stored procedure based on the assembly<\/li>\n<\/ol>\n<p>Let&#8217;s take a look at each one in more detail. I will focus mainly on the various hurdles I had to jump to get my C# code into SQL Server 2005 environment, and execute it.<\/p>\n<h3>Writing the C# code <\/h3>\n<p>I decided to replicate the <b>xp_getfiledetails<\/b> behavior in C#. In the interests of openness I should confess that this was not only was my first CLR stored procedure, but also my first C# program!<\/p>\n<p>I&#8217;m primarily a rookie VB programmer, but I decided to write my CLR assembly using C# because I found out that C# allowed me an easy way to return an integer value completion code for my CLR sproc (as it turns out, I later I found an example of how Visual Basic could also return an integer value).<\/p>\n<p>I&#8217;m not going to review my C# code in detail here, but the full source code is available for download from the &#8220;Code Download&#8221; link in the box to the right of the article title.<\/p>\n<h3>Compiling the C# Code <\/h3>\n<p>Download the C# source code and place it in a directory of your choice. In my examples, just for testing purposes, I&#8217;ve used <b>c:\\temp<\/b>. <\/p>\n<p>Before you can use the C# code in a CLR you have to compile it. I did this using the C# compiler, &#8220;csc.exe&#8221;, found in the .NET framework library, and the following DOS command:<\/p>\n<pre><\/pre>\n<p class=\"MsoNormal\">c:\\WINDOWS\\Microsoft.NET\\Framework\\v2.0.50727\\csc \/target:library \/<br \/>   out:c:\\temp\\xp_getfiledetails.dll c:\\temp\\xp_getfiledetails.cs<\/p>\n<p>This compiler exe command used my C# code in the file &#8220;c:\\temp\\xp_getfiledetails.cs&#8221; and created a compiled DLL named &#8220;c:\\temp\\xp_getfiledetails.dll&#8221;. <\/p>\n<p>With my <b>xp_getfiledetails<\/b> DLL created, I could step into the database and, using T-SQL, complete the rest of the steps needed to define my CLR stored procedure.<\/p>\n<h3>Enabling the CLR <\/h3>\n<p>By default the CLR functionality is turned off. To enable it, you can either use the SQL Server 2005 Surface Area Configuration tool or T-SQL. Following is the T-SQL code that I used to enable the CLR on my SQL Server 2005 machine:<\/p>\n<pre><\/pre>\n<p class=\"MsoNormal\"><code>sp_configure&#160;<\/code><code>'clr&#160;enabled'<\/code><code>,&#160;<\/code><code>1 <\/code><br \/><code>GO <\/code><br \/><code>RECONFIGURE <\/code><br \/><code>GO<\/code><\/p>\n<p><b>Note:<br \/><\/b><i>It isn&#8217;t actually necessary to enable the CLR in order to create your assembly, but only to execute it.<\/i><\/p>\n<h3>Creating the assembly <\/h3>\n<p>My next step was to create an assembly in the database, based on my compiled DLL. On the face of it, this is pretty straight-forward:<\/p>\n<pre><\/pre>\n<p class=\"MsoNormal\">CREATE ASSEMBLY xp_getfiledetails<br \/>from &#8216;c:\\temp\\xp_getfiledetails.dll&#8217;<br \/>WITH PERMISSION_SET = UNSAFE<\/p>\n<p>However, I gave quite a bit of thought to the following <b>PERMISSION_SET<\/b> options, which identify whether the assembly can access external resources and\/or unmanaged code:<\/p>\n<ul>\n<li><b>SAFE<\/b> &#8211; allows assemblies to do only local computations and have access only to local files  <\/li>\n<li><b>EXTERNAL_ACCESS<\/b> &#8211; similar to SAFE permissions except the assembly can access network resources and the registry  <\/li>\n<li><b>UNSAFE<\/b> &#8211; allows unrestricted access to resources, and even allows execution of unmanaged code blocks<\/li>\n<\/ul>\n<p>Using <b>UNSAFE<\/b> was not my first choice but was the option I ended up using. I tried both the <b>SAFE<\/b> and <b>EXTERNAL_ACCESS<\/b> options first, but my C# code contained a call to a <b>pinvokeimpl<\/b> method, which could only be assembled using the <b>UNSAFE<\/b> permission set.<\/p>\n<p>PInvoke (platform invoke) is the means by which you can call Win32 API functions that are not directly available to the .NET framework. My C# code contained the <b>GetShortPathName<\/b> method, which is in the kernel32 DLL:<\/p>\n<pre><\/pre>\n<p class=\"MsoNormal\">&#160;&#160; \/\/ include GetShortPathName API to get short name of file<br \/>&#160;&#160; [DllImport(&#8220;kernel32.dll&#8221;)]<br \/>&#160;&#160; static extern int GetShortPathName(string lpszLongPath, <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; StringBuilder lpszShortPath, <br \/>&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#160;&#160;&#160;&#160;&#160;&#160;&#160;int cchBuffer);<\/p>\n<p>Since this is unmanaged code, the assembly that calls it requires the <b>UNSAFE<\/b> permission set. If you are concerned about <strong>UNSAFE<\/strong> assemblies and don&#8217;t need to return the short file name then you simply can remove the <b>GetShortPathName<\/b> code and use the <b>EXTERNAL_ACCESS<\/b> permission set.<\/p>\n<p>Resigned to building an <strong>UNSAFE<\/strong> assembly, when I first attempted to create it, I received the following error:<\/p>\n<pre><\/pre>\n<p class=\"MsoNormal\">Msg 10327, Level 14, State 1, Line 1<br \/>CREATE ASSEMBLY for assembly &#8216;xp_getfiledetails&#8217; failed because<br \/>assembly &#8216;xp_getfiledetails&#8217; is not authorized for PERMISSION_SET = UNSAFE.<br \/>The assembly is authorized when either of the following is true:<br \/>the database owner (DBO) has UNSAFE ASSEMBLY permission and the database<br \/>has the TRUSTWORTHY database property on; or the assembly is signed with<br \/>a certificate or an asymmetric key that has a corresponding login with<br \/>UNSAFE ASSEMBLY permission.<\/p>\n<p>I was already the DBO of the database (master) in which I was trying to assemble this code. To get around the above error in my testing environment, I set the <b>TRUSTWORHTY<\/b> property on the master database by using the following command:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">ALTER&#160;DATABASE&#160;master&#160;SET&#160;trustworthy&#160;ON<\/pre>\n<p>However, setting <b>TRUSTWORTHY<\/b> on potentially opens the floodgates for dangerous code to run in an uninhibited manner, so on the live system this is unadvisable. Instead you will want to <strong>sign the assembly using a certificate<\/strong>.&#160;<\/p>\n<h3>Creating the CLR stored procedure<\/h3>\n<p>Once I had successfully assembled my C# code, all that was left to create a T-SQL reference to the <strong>xp_getfiledetails <\/strong>method in my assembly, which would produce&#160;my stored procedure. To accomplish this I used the following command:<\/p>\n<pre><\/pre>\n<p class=\"MsoNormal\"><code>CREATE&#160;PROCEDURE&#160;<\/code><code>xp_getfiledetails&#160; <\/code><br \/><code>&#160;&#160;&#160;<\/code><code>@FileName&#160;<\/code><code>NVARCHAR<\/code><code>(<\/code><code>128<\/code><code>) <\/code><br \/><code>AS <\/code><br \/><code>&#160;&#160;&#160;<\/code><code>EXTERNAL&#160;NAME&#160;<\/code><code>xp_getfiledetails<\/code><code>.SQLCLR.<\/code><code>xp_getfiledetails<\/code> <\/p>\n<p>I was now ready to execute my <b>xp_getfiledetails<\/b> CLR stored procedure&#160;for the first time. All I needed to do was issue the <b>EXECUTE<\/b> command just as I would have done to execute the deprecated extended SP in SQL Server 2000. Here is an example of my <b>EXECUTE<\/b> command that displays the details of a file on my c drive:<\/p>\n<pre><\/pre>\n<p class=\"MsoNormal\">declare @return_code int<br \/>EXECUTE @return_code = xp_getfiledetails &#8216;c:\\temp\\xp_getfiledetails.cs&#8217;<br \/>print @return_code<\/p>\n<p>So there you have it. There&#8217;s nothing too difficult here, but yet this CLR stored procedure allowed me to add some functionality to SQL Server 2005 I would not have had otherwise. Also I was able to simulate the deprecated <b>xp_getfiledetails <\/b>extended SP without having to modify any of the existing code that referenced it.<\/p>\n<h2>Observations and advice <\/h2>\n<p>Now that I had a CLR object in my database, I realized that, for the first time, I had objects defined in my database for which the source code was stored externally. The upshot of this is that if you are going to allow CLRs then you had better develop a source code management process to make sure that you do not lose the source code associated with your CLRs. In addition to this you will need to consider where the DLLs for your CLRs should be placed. For the sake of this article, I compiled my DLL into the &#8220;c:\\temp&#8221; directory. Obviously it would be wise to establish a different location for storing your CLR DLLs. <\/p>\n<p>There seems to be a lot of discussion about what kinds of code\/processes are appropriate for CLRs. Just because it can be done with a few lines of VB or C# code, it doesn&#8217;t necessarily make it the correct design decision. At our shop we plan to move cautiously forward with CLRs. We plan to develop standards and guidelines around what is an appropriate use for CLRs and what is not. It is hoped that our standards\/guidelines and testing methodology will ensure we minimize the impact that CLRs might have on our SQL Server environment.<\/p>\n<p>Lastly, the DBAs I speak to are generally concerned about potential security issues associated with creating an <b>UNSAFE<\/b> assembly. My research on the web did not indicate that my assembly would open the door for any security breach, but then not all <strong>UNSAFE<\/strong> code is as benign as that called by my C# program. As a DBA, you will want to perform a thorough review of any CLR code that a programmer says needs to be assembled using the <b>UNSAFE<\/b> permission set.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Greg Larsen provides a step-by-step tour of the hurdles he had to jump in building and deploying his first serious CLR stored procedure.&hellip;<\/p>\n","protected":false},"author":78478,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143525],"tags":[4349,4348,4532,4149,4533,4150,4151,4171],"coauthors":[],"class_list":["post-191","post","type-post","status-publish","format-standard","hentry","category-learn","tag-assembly","tag-clr","tag-clr-stored-procedure","tag-learn-sql-server","tag-permission_set","tag-sql","tag-sql-server","tag-sql-server-2005"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/191","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/78478"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=191"}],"version-history":[{"count":4,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/191\/revisions"}],"predecessor-version":[{"id":39761,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/191\/revisions\/39761"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=191"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=191"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=191"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=191"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}