{"id":86441,"date":"2020-02-21T18:09:24","date_gmt":"2020-02-21T18:09:24","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=86441"},"modified":"2024-08-30T14:12:43","modified_gmt":"2024-08-30T14:12:43","slug":"sql-server-machine-learning-2019-working-with-security-changes","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/business-intelligence\/data-science\/sql-server-machine-learning-2019-working-with-security-changes\/","title":{"rendered":"SQL Server Machine Learning 2019: Working with Security Changes"},"content":{"rendered":"<p>I have a confession to make. Why, in my last article about <a href=\"https:\/\/www.red-gate.com\/simple-talk\/sql\/sql-development\/sql-server-2019-graph-database-and-shortest_path\/\"><strong>shortest_path<\/strong><\/a> in <em>SQL Server 2019<\/em>, have I used <em>Gephi<\/em> in order to illustrate the relationships, instead of using a script in <em>R <\/em>for the same purpose and demonstrate Machine Learning Services as well?<\/p>\n<p>The initial plan was to use an R script; however, the R script which works perfectly in <em>SQL Server 2017<\/em> doesn&#8217;t work in <em>SQL Server 2019<\/em>.<\/p>\n<p>This is the original R script I had planned to use:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">EXEC\u00a0<strong>Sp_execute_external_script<\/strong>\u00a0\r\n\u00a0\u00a0@language\u00a0=\u00a0N'R',\u00a0\r\n\r\n\u00a0\u00a0@script\u00a0=\u00a0N' require(igraph) \r\ng\u00a0&lt;-\u00a0graph.data.frame(graphdf) \r\nV(g)$label.cex\u00a0&lt;-\u00a02\r\npng(filename\u00a0=\u00a0\"c:\\\\R\\\\plot1.png\",\u00a0height\u00a0=\u00a01200,\u00a0width\u00a0=\u00a01200,\u00a0res\u00a0=\u00a0100);\u00a0 \r\nplot(g,\u00a0vertex.label.family\u00a0=\u00a0\"sans\",\u00a0vertex.size\u00a0=\u00a040) \r\ndev.off()\u00a0',\u00a0\r\n\r\n\u00a0\u00a0@input_data_1\u00a0=\u00a0N'select\u00a0LikeMember.MemberName\u00a0as\u00a0LikeMember,\r\n LikedMember.MemberName\u00a0as\u00a0LikedMember \r\nfrom\u00a0dbo.ForumMembers\u00a0as\u00a0LikeMember,\u00a0\u00a0dbo.ForumMembers\u00a0as\u00a0LikedMember,\r\n\u00a0\u00a0Likes\r\n \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0where\u00a0Match(LikeMember-(Likes)-&gt;LikedMember)',\u00a0\r\n\r\n\u00a0\u00a0@input_data_1_name\u00a0=\u00a0N'graphdf'\u00a0\r\n\r\ngo\u00a0<\/pre>\n<p>The purpose of the script was only to create an image file with the relationships stored in graph tables, but in SQL Server 2019<strong>,<\/strong> it results in an access denied message.<\/p>\n<p>If you would like to reproduce this demonstration, you can use <a href=\"https:\/\/1drv.ms\/u\/s!ApgSbfuN8DmZnMZyL0ATb0ZnmOkvqg?e=VwBGjy\">this script file<\/a> to create the database in a SQL Server 2017. You must also install the R packages needed by the script. There are many ways to do it. My favourite is this:<\/p>\n<ol>\n<li>Open windows explorer<\/li>\n<li>Browse to the SQL Server <strong>R<\/strong> folder, usually is this one: <em>C:\\Program Files\\Microsoft SQL Server\\MSSQL15.MSSQLSERVER\\R_SERVICES\\bin<\/em><\/li>\n<li>Execute the <em>R.EXE<\/em> application<\/li>\n<li>Execute the instruction <em>install.packages(&#8220;igraph&#8221;)<\/em> , where \u201cigraph\u201d is the name of the package to be installed<\/li>\n<\/ol>\n<p>When you try and run the above <strong>R<\/strong> script. It will work well in <strong>SQL Server 2017<\/strong>, and fail in <strong>SQL Server 2019<\/strong> with the error message you can see in the image below:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1143\" height=\"268\" class=\"wp-image-86442\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-screenshot-of-a-cell-phone-description-automati-14.png\" alt=\"A screenshot of a cell phone\n\nDescription automatically generated\" \/><\/p>\n<p>During PASS Summit, I was able to ask questions to the Microsoft employees in the Data Clinic, and they explained the differences between the two versions. The explanation hasn\u2019t solved all the problems, but I was able to find a solution and create a workaround for this script.<\/p>\n<p>While researching the workaround, I saw many examples of R scripts manipulating files and all these examples may fail in SQL Server 2019<strong>. <\/strong>Highlighting this problem, solution, and workaround in this article will be important to everyone using <em>SQL Server Machine Learning<\/em> and planning to migrate to <em>SQL Server 2019.<\/em><\/p>\n<p>First, I\u2019ll review how Machine Learning in SQL Server works: it involves the execution of an external script, in R or Python, but has a flexible structure, you could create your own external language. The execution itself is started by a service called <em>SQL Server Launchpad<\/em>, isolating the execution from SQL Server itself. The image below (from <a href=\"https:\/\/www.sqlservercentral.com\/steps\/what-is-the-machine-learning-server-level-2-of-the-stairway-to-ml-services\">What is the Machine Learning Server<\/a>) illustrates how the execution architecture works, although this article will not go so deep into the architecture.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86443\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-screenshot-of-a-cell-phone-description-automati-15.png\" alt=\"SQL Server Machine Learning Architecture\" width=\"624\" height=\"301\" \/><\/p>\n<h2>Launchpad in SQL Server 2017<\/h2>\n<p>\nIn SQL Server 2017 the Launchpad starts processes for each execution. The processes need an identity, so SQL Server dynamically creates users inside the group <em>SQLRUserGroup<\/em>. In the following image, you can see two groups, <em>SQLRUserGroup<\/em>, for SQL Server 2019 and <em>SQLRUserGroupSQL2017<\/em> for SQL Server 2017, since I have both in the same machine.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86444\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-close-up-of-a-logo-description-automatically-ge-2.png\" alt=\"Groups for SQL Server Machine Learning\" width=\"633\" height=\"64\" \/><\/p>\n<p>The difference in the name is due to the instance name; SQL Server 2019 is the default instance in my machine. Due to that, its user group name has only the core name, while SQL Server 2017 is in an instance called <em>SQL2017<\/em>, so the instance name is attached to the core group name.<\/p>\n<p>The processes have access to the machine resources, limited by the permissions given to the <em>SQLRUserGroup<\/em>. Since SQL Server 2017 creates many users, you shouldn\u2019t grant permissions directly to the users, only to the group.<\/p>\n<p>In order to be able to manipulate files in the file system, you only need to give the correct permissions to the group <em>SQLRUserGroup<\/em>. The image below shows the group and users for SQL Server 2017<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86445\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-screenshot-of-a-cell-phone-description-automati-16.png\" alt=\"SQLRUserGroup Members\" width=\"502\" height=\"573\" \/><\/p>\n<h2>Launchpad in SQL Server 2019<\/h2>\n<p>In SQL Server 2019, the Launchpad was improved. Instead of using processes for each execution, it uses app containers. The app containers need only a single identity, so they use the same identity as the Launchpad service.<\/p>\n<p>While the 2017 version, the SQLRUserGroup, had many users; in the 2019 version, it has only one, the Launchpad service account. The image below shows the group for SQL Server 2019.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86446\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-screenshot-of-a-cell-phone-description-automati-17.png\" alt=\"SQL R User Group Properties in 2019\" width=\"500\" height=\"572\" \/><\/p>\n<p>Nowadays, when talking about containers immediately, we think about Docker, but not in this case. The app containers I\u2019m mentioning here are a sandbox set of APIs introduced in Windows 8. These allow any application to create app containers and start an execution inside an app container.<\/p>\n<p>The app containers work as isolated virtual machines (but much more lightweight, of course). This means that you can&#8217;t access the file system of the host (your SQL Server), neither for reading or to save files. You save any files inside the app container, and they are destroyed after the app container is released. In fact, the container is redirected to save files in a temporary folder inside the host hard drive. This increases the security, no doubt about that, but any script dealing with the file system may need changes to work in SQL Server 2019.<\/p>\n<p>These app containers are created by Launchpad using the windows <em>Sandbox API<\/em>. The Launchpad doesn\u2019t offer any way to customize the app containers creation, which could solve the problem.<\/p>\n<p>You can learn more about app containers with these two links:<\/p>\n<p><a href=\"https:\/\/techcommunity.microsoft.com\/t5\/Windows-Kernel-Internals\/Windows-Sandbox\/ba-p\/301849\">https:\/\/techcommunity.microsoft.com\/t5\/Windows-Kernel-Internals\/Windows-Sandbox\/ba-p\/301849<\/a> <br \/>\n<a href=\"https:\/\/www.malwaretech.com\/2015\/09\/advanced-desktop-application-sandboxing.html\">https:\/\/www.malwaretech.com\/2015\/09\/advanced-desktop-application-sandboxing.html<\/a><\/p>\n<h2>Proving this Concept<\/h2>\n<p>In order to determine this concept, you can use a script in R to access the disk and list the files. You can execute the script below in both SQL Server versions, 2017 and 2019, and compare the results. In SQL Server 2017, the script will have access to the host disk. In SQL Server 2019, the script will have access only to a fake disk, which is, in fact, a temporary folder in the host disk and will be deleted after its use by the app container.<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">EXEC\u00a0<strong>Sp_execute_external_script<\/strong>\u00a0\r\n\u00a0\u00a0@language\u00a0=\u00a0N'R',\u00a0\r\n\u00a0\u00a0@script\u00a0=\u00a0N'data\u00a0&lt;-\u00a0list.files() \r\n            data2\u00a0&lt;-\u00a0data.frame(data)',\u00a0\r\n\u00a0\u00a0@output_data_1_name\u00a0=\u00a0N'data2'\u00a0\r\n\r\nEXEC\u00a0<strong>Sp_execute_external_script<\/strong>\u00a0\r\n\u00a0\u00a0@language\u00a0=\u00a0N'R',\u00a0\r\n\u00a0\u00a0@script\u00a0=\u00a0N'data\u00a0&lt;-\u00a0list.dirs(\"..\\\\\") \r\n             data2\u00a0&lt;-\u00a0data.frame(data)',\u00a0\r\n\u00a0\u00a0@output_data_1_name\u00a0=\u00a0N'data2'\u00a0\r\n\r\nEXEC\u00a0<strong>Sp_execute_external_script<\/strong>\u00a0\r\n\u00a0\u00a0@language\u00a0=\u00a0N'R',\u00a0\r\n\u00a0\u00a0@script\u00a0=\u00a0N'data\u00a0&lt;-\u00a0list.dirs(\"..\\\\..\") \r\n            data2\u00a0&lt;-\u00a0data.frame(data)',\u00a0\r\n\u00a0\u00a0@output_data_1_name\u00a0=\u00a0N'data2'\u00a0<\/pre>\n<p>The image below the script shows the result in SQL Server 2019.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86447\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-screenshot-of-a-social-media-post-description-a-8.png\" alt=\"Script results in 2019\" width=\"444\" height=\"397\" \/><\/p>\n<h2>Solutions<\/h2>\n<p>The app containers are created during the SQL Server Machine Learning services setup. They are objects inside the Windows local directory, and such as all the objects in the local directory, they have a unique <em>SID<\/em> to identify the app container.<\/p>\n<p>You can define file system permissions directly for the SIDs. The problem is: How to identify the SID\u2019s of the app containers since they are not listed as user or groups.<\/p>\n<p>Analyse two possible solutions:<\/p>\n<ol>\n<li>Easy and tempting: You can give permission to the object called <em>All Application Containers<\/em>. The R scripts will have the file system permission; however, any other app container eventually running on the same machine will have the file system permission as well.<\/li>\n<li>Secure: You can identify the SIDs of the app containers installed by SQL Server and give permissions directly to them. In this way, only the app containers used by the SQL Server Machine Learning Services will receive these permissions<\/li>\n<\/ol>\n<p>You can test both solutions using a straightforward script. First, create a folder called C:\\<em>testFolder<\/em> and copy some files, any files, to the folder. Try to list the files in this folder using the script below.<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">EXEC Sp_execute_external_script \r\n  @language = N'R', \r\n  @script = N'data &lt;- list.files(\"c:\\\\testFolder\") \r\n            data2 &lt;- data.frame(data)', \r\n  @output_data_1_name = N'data2'<\/pre>\n<h3>All Application Containers<\/h3>\n<p>The All Application Container object has a fixed SID in the local directory, which is S-1-15-2-1 . You can use the application <em>icacls <\/em>to grant permission<\/p>\n<ol>\n<li>Execute the R script above against the 2019 instance using SSMS. The script will return no result<\/li>\n<li>Press <em>Win+R<\/em> and type <em>CMD<\/em> to open a command prompt as administrator<\/li>\n<li>Type the following instruction and press <em>ENTER<\/em><\/li>\n<pre class=\"lang:ps theme:powershell-ise\">icacls c:\\testFolder \/grant *S-1-15-2-1:(OI)(CI)F \/t<\/pre>\n<li>Using Windows Explorer, right-click the <em>testFolder<\/em> and select <em>Properties<\/em> in the context menu<\/li>\n<li>In the <em>Properties<\/em> window, click the <em>Security<\/em> tab. You will be able to confirm the permission, like the image below.<\/li>\n<li style=\"list-style-type: none;\">\u00a0<\/li>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86448\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-screenshot-of-a-social-media-post-description-a-9.png\" alt=\"TestFolder security properties\" width=\"450\" height=\"603\" \/><\/p>\n<li>Open <em>SQL Server Configuration Manager<\/em><\/li>\n<li>On the left side of <em>SQL Server Configuration Manager<\/em>, select <em>SQL Server Services<\/em><\/li>\n<li>In the right-side of <em>SQL Server Configuration Manager<\/em>, right-click the <em>Launchpad<\/em> service and select <em>Restart<\/em> item in the context menu<\/li>\n<li>Execute the same R script again in SSMS. This time it will work and list the files in the folder.<\/li>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86449\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-screenshot-of-a-cell-phone-description-automati-18.png\" alt=\"Script results\" width=\"387\" height=\"184\" \/><\/p>\n<li>Delete the <em>testFolder<\/em> folder so that you can continue with the other solution below<\/li>\n<\/ol>\n<h3>Using the App Containers SID<\/h3>\n<p>The challenge you will face when giving permissions to the App Containers SID is to discover which are the App Containers SID. There is not a direct solution for that, although, after knowing where to look, it becomes easy. SQL Server installs firewall rules in Windows Firewall in order to forbid app containers from making external contact to the network. These firewall rules are created precisely to block the app containers SID from using the network, so you can identify these SIDs by analysing these firewall rules.<\/p>\n<p>If you look at the firewall rules, you can see that the app containers are blocked.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86450\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/word-image-81.png\" alt=\"Firewall\" width=\"356\" height=\"286\" \/><\/p>\n<p>The firewall UI doesn\u2019t provide the SIDs, however. You need to dig deeper using PowerShell to retrieve the SIDs from the firewall rules.<\/p>\n<p>The PowerShell command is a combination of the cmdlet <code>Get-NetFirewallRule<\/code>, to retrieve the firewall rules and the cmdlet <code>Get-NetFirewallApplicationFilter<\/code> in order to retrieve, from each rule, the app container filter information.<\/p>\n<p>The complete PowerShell command line is this:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">(Get-NetFirewallRule | Where-Object { ($_.Direction -eq \"Outbound\") -and ($_.DisplayName -like \"*appcontainer*\")}) | %{ ($_ | Get-NetFirewallApplicationFilter) | %{Write-Output $_.Package } } <\/pre>\n<p>Analyse this command line in more detail:<\/p>\n<ol>\n<li><code>Get-NetFirewallRule<\/code> will retrieve all the firewall rules in Windows Firewall<\/li>\n<li><code>Where-Object<\/code> filters the firewall rules, retrieving only the outbound rules which contains <em>\u2018appcontainer\u2019<\/em> in the name<\/li>\n<li>The expression <code>%{ }<\/code> is a shortcut to <code>foreach-object<\/code> so that the instructions will be executed for each firewall rule<\/li>\n<li><code>Get-NetFirewallApplicationFilter<\/code> is executed for each firewall rule, having the firewall rule <code>($_ )<\/code> as a parameter<\/li>\n<li>The expression <code>% { }<\/code> is used again to run one line for each application filter found<\/li>\n<li>The <code>Write-Output<\/code> shows the value of the <code>Package<\/code> property on each application filter<\/li>\n<\/ol>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86451\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-black-and-blue-text-description-automatically-g-1.png\" alt=\"SIDs\" width=\"1138\" height=\"455\" \/><\/p>\n<p>Having discovered the SIDs, you need to set the permission to the folder. The instruction will look like the one below for each SID.<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">ICACLS \"C:\\testFolder\" \/grant \"*S-1-15-2-1853514363-3573294594-1452771951-225645021-3819702349-824866239-3302992986\":(OI)(CI)F \/t<\/pre>\n<p>You could copy and paste all the sids returned to create a batch file to set the permissions, but PowerShell can do all of this you as well. The following script prompts you for a file path, the one where you want Machine Learning to have access, and sets the permissions:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">Param (\r\n    [Parameter(\r\n        Mandatory)]\r\n        [string]\r\n        $FolderName\r\n    )\r\nGet-NetFirewallRule |\r\n    Where-Object {\r\n        $_.Direction -eq \"Outbound\" -and\r\n        $_.DisplayName -like \"*appcontainer*\"\r\n        } |\r\n    ForEach-Object {\r\n        $_ |\r\n        Get-NetFirewallApplicationFilter | \r\n        ForEach-Object {\r\n            $Acl = Get-Acl $FolderName\r\n            $Sec= New-Object System.Security.Principal.SecurityIdentifier($_.Package)\r\n            $AccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule(\r\n                $Sec,\r\n                \"FullControl\",\r\n                (1 -bor 2),\r\n                0,\r\n                \"Allow\"\r\n                )\r\n            $Acl.SetAccessRule($AccessRule)\r\n            $Acl |\r\n                Set-Acl $FolderName\r\n            } # end 2nd FE-O\r\n        } # end 1st FE-O <\/pre>\n<p>The script is using COM objects <code>System.Security.Principal.SecurityIdentifier<\/code> and <code>System.Security.AccessControl.FileSystemAccessRule<\/code> in order to set the access rule on the folder.<\/p>\n<p>After running the script, take a look at the security properties of the folder, and the R script should now work from SQL Server 2019.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86452\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/word-image-82.png\" alt=\"TestFolder properties\" width=\"453\" height=\"603\" \/><\/p>\n<h2>Workaround with a FileTable<\/h2>\n<p>Although I have identified two solutions, it\u2019s interesting to notice Microsoft is moving SQL Server Machine Learning towards a more secure environment, avoiding network and file system access. Considering this, you may like to use an alternative solution.<\/p>\n<p>There are some options, all based on returning the image as an output parameter to SQL Server. Here is one of the options.<\/p>\n<p>A filetable can map a disk folder as a table in SQL Server. This a very good solution when you need to deal with files. The script in R will see the table as a regular table. While reading or inserting into the table, the script will be reading and inserting from\/to the disk.<\/p>\n<p>It&#8217;s a very good solution for a permanent environment, however, it has some problems:<\/p>\n<ul>\n<li>You can&#8217;t define the final location of each file using SQL Server configuration. The filetable will be exposed from the server as a sharing folder, not a disk folder, so there is a slight difference in relation to the initial planning.<\/li>\n<li>Filetable is not supported in Azure PaaS cloud solutions, so you will be tied to IaaS solutions, which means creating and maintaining a virtual machine in Azure, or an on-premise environment.<\/li>\n<\/ul>\n<p>The Filetable technology was created in SQL Server 2016, based on the filestream technology which was built on the previous version.<\/p>\n<h3>Enabling Filetable in SQL Server<\/h3>\n<p>A filetable solution needs some administrative care; for this reason, this solution can only be used after enabling it on a server configuration level. Follow these steps to enable this solution:<\/p>\n<ol>\n<li>Open <em>SQL Server Configuration Manager<\/em><\/li>\n<li>On the left side of <em>SQL Server Configuration Manager<\/em>, select <em>SQL Server Services<\/em><\/li>\n<li>On the right side, double click the <em>SQL Server<\/em> service you are using<\/li>\n<li>On the <em>SQL Server<\/em> properties window, select the <em>FileStream<\/em> tab<\/li>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86453\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-screenshot-of-a-cell-phone-description-automati-19.png\" alt=\"Server properties\" width=\"1458\" height=\"896\" \/><\/p>\n<li>In the filestream tab, check the box <em>Enable filestream for Transact-SQL Access<\/em><\/li>\n<li>In the filestream tab, check the box <em>Enable filestream for I\/O Access<\/em><\/li>\n<li>The textbox <em>Windows Share Name<\/em> will be filled with the default value <em>MSSQLSERVER<\/em>. You can change it, but leave this default for now<\/li>\n<li>In the filestream tab, check the box <em>Allow remote clients to access filestream data.<\/em><\/li>\n<li>Click the <em>Ok<\/em> button in the <em>SQL Server Properties<\/em> window<\/li>\n<li>In the right-side of <em>SQL Server Configuration Manager<\/em>, right-click the SQL Server service and select <em>Restart<\/em> item in the context menu<\/li>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86454\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-screenshot-of-a-cell-phone-description-automati-20.png\" alt=\"Restart SQL Server\" width=\"513\" height=\"290\" \/><\/p>\n<li>In SSMS, change the FileStream access level configuration using the following code:<\/li>\n<\/ol>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">EXEC\u00a0sp_configure\u00a0'filestream\u00a0access\u00a0level',\u00a0\u00a02\u00a0\r\nRECONFIGURE<\/pre>\n<p>Value 1 enables filestream only for T-SQL access, which is not enough for filetable. Value 2 enables filestream for T-SQL and I\/O access.<\/p>\n<h3>Preparing the Database to Support Filetable<\/h3>\n<p>In order to support filetable, you need to make some changes to the database. You need to create a filegroup to contain the filestream data, configure a special file, which will point to a folder and change some database settings.<\/p>\n<p>Here is the T-SQL to execute for the database. Be sure to delete the c:\\R folder before running this code:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">ALTER\u00a0DATABASE\u00a0graphdemo\u00a0ADD\u00a0filegroup\u00a0filestreamgroup\u00a0\r\nCONTAINS\u00a0filestream\u00a0\r\n\r\nALTER\u00a0DATABASE\u00a0graphdemo\u00a0ADD\u00a0FILE\u00a0(NAME=fs,\u00a0filename='c:\\R')\u00a0\r\nTO\u00a0filegroup\u00a0filestreamgroup\u00a0<\/pre>\n<ol>\n<li>In <em>SSMS, Object Explorer<\/em>, right-click the <em>GraphDemo<\/em> database and select <em>Properties<\/em> in the context menu<\/li>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86455\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-screenshot-of-a-social-media-post-description-a-10.png\" alt=\"FileStream database properties\" width=\"864\" height=\"784\" \/><\/p>\n<li>In the database properties window, set the property <em>Filestream Directory Name<\/em> to <em>RFiles<\/em>. During further steps, you will better understand what this means.<\/li>\n<li>In the database properties window, set the property <em>Filestream Non-Transaction access<\/em> to <em>Full<\/em><\/li>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86456\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-screenshot-of-a-social-media-post-description-a-11.png\" alt=\"FileStream properties\" width=\"626\" height=\"65\" \/><\/p>\n<li>In the database properties window, click the <em>Ok<\/em> button<\/li>\n<li>Confirm the close of database connections clicking the <em>Ok<\/em> button in the dialog box that will appear<\/li>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86457\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-screenshot-of-a-cell-phone-description-automati-21.png\" alt=\"Confirm change\" width=\"761\" height=\"168\" \/><\/p>\n<li>Create the filetable using the following T-SQL:<\/li>\n<\/ol>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">CREATE\u00a0TABLE\u00a0images\u00a0AS\u00a0filetable\u00a0WITH\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0(\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0filetable_directory\u00a0=\u00a0'Images'\u00a0\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0);\u00a0\r\n\r\ngo<\/pre>\n<p>The filetable you created, <em>Images<\/em>, is linked to a folder. Which folder? If you check the path <em>c:\\R<\/em>, which was used to configure the database file, you will find some files and folders, but it&#8217;s not easy to understand them. So, what\u2019s missing?<\/p>\n<p>Follow these steps to see:<\/p>\n<ol>\n<li>Click <em>win+R<\/em><\/li>\n<li>Type <em>\\\\localhost<\/em> and click <em>Ok<\/em> You will find the share <em>MSSQLSERVER<\/em>, that&#8217;s the name you configured in SQL Server Configuration Manager. Now that you know how the name is used, you can choose a better name if needed.<\/li>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86458\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-close-up-of-a-logo-description-automatically-ge-3.png\" alt=\"MSSQLServer share\" width=\"226\" height=\"109\" \/><\/p>\n<li>Open the share <em>MSSQLSERVER<\/em> You will find a folder called <em>RFiles<\/em>. That&#8217;s the configuration made for the database. Each database configured to use filestream will have a share inside <a href=\"file:\/\/\/C:\\MSSQLSERVER\"><em>\\\\Localhost\\MSSQLSERVER<\/em><\/a><\/li>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86459\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-screenshot-of-a-cell-phone-description-automati-22.png\" alt=\"RFiles folder\" width=\"151\" height=\"92\" \/><\/p>\n<li>Open the folder <em>Rfiles<\/em> You will find another folder called Images, the name used for the filetable. Each filetable in the database <em>GraphDemo<\/em> will have its own folder inside <a href=\"file:\/\/\/C:\\MSSQLSERVER\\RFILES\"><em>\\\\localhost\\MSSQLSERVER\\RFILES<\/em><\/a><\/li>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86460\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-screenshot-of-a-cell-phone-description-automati-23.png\" alt=\"Images folder\" width=\"125\" height=\"96\" \/><\/p>\n<li>Open the folder <em>Images<\/em>. The folder is empty.<\/li>\n<li>In SSMS, execute the following query:\n<pre>select * from Images<\/pre>\n<p>The filetable is empty.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86461\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-screenshot-of-a-social-media-post-description-a-12.png\" alt=\"Filetable is empty\" width=\"1110\" height=\"110\" \/><\/p>\n<li>Copy a file, any file, to the Images folder, <a href=\"file:\/\/\/C:\\MSSQLServer\\RFILES\\Images\"><em>\\\\localhost\\MSSQLServer\\RFILES\\Images<\/em><\/a><\/li>\n<li>Execute the select again in SSMS:<\/li>\n<\/ol>\n<pre>select\u00a0*\u00a0from\u00a0Images <\/pre>\n<p>This time the table has one row, exactly the row for the new file you copied. The filetable is linked with the folder. So, if you insert a record inside the filetable, the record will be a file available in the folder, in this way you can save to disk the image generated by the <strong>R<\/strong> script<\/p>\n<h3>New R Script for Filetable<\/h3>\n<p>After preparing the filetable environment, the new script will be this one:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">DECLARE\u00a0@img\u00a0<em>VARBINARY<\/em>(max)\u00a0\r\n\r\nEXEC\u00a0<strong>Sp_execute_external_script<\/strong>\u00a0\r\n\u00a0\u00a0@language\u00a0=\u00a0N'R',\u00a0\r\n\u00a0\u00a0@script\u00a0=\u00a0N' require(igraph)\r\n               require(hexView) \r\n               g\u00a0&lt;-\u00a0graph.data.frame(graphdf)\r\n               V(g)$label.cex\u00a0&lt;-\u00a02 png(filename\u00a0=\u00a0\"plot1.png\",\u00a0height\u00a0=\u00a01200,\u00a0width\u00a0=\u00a01200,\u00a0res\u00a0=\u00a0100);\r\n\u00a0       plot(g,\u00a0vertex.label.family\u00a0=\u00a0\"sans\",\u00a0vertex.size\u00a0=\u00a040)\r\n              dev.off()\r\n\u00a0             img\u00a0&lt;-\u00a0readRaw(\"plot1.png\")\r\n              imageContent\u00a0&lt;-\u00a0img$fileRaw',\r\n\u00a0\r\n\u00a0\u00a0@input_data_1\u00a0=\u00a0N'select\u00a0LikeMember.MemberName\u00a0as\u00a0LikeMember, \r\n                    LikedMember.MemberName\u00a0as\u00a0LikedMember\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\r\n     from\u00a0dbo.ForumMembers\u00a0as\u00a0LikeMember,\u00a0\u00a0dbo.ForumMembers\u00a0as\u00a0LikedMember,\r\n\u00a0\u00a0         Likes \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\r\n     where\u00a0Match(LikeMember-(Likes)-&gt;LikedMember)',\u00a0\r\n\r\n\u00a0\u00a0@input_data_1_name\u00a0=\u00a0N'graphdf',\r\n\u00a0\r\n\u00a0\u00a0@params\u00a0=\u00a0N'@imageContent\u00a0varbinary(max)\u00a0OUTPUT'\r\n,\u00a0\r\n\u00a0\u00a0@imageContent\u00a0=\u00a0@img\u00a0output;\u00a0\r\n\r\nINSERT\u00a0INTO\u00a0images\u00a0(NAME,\u00a0file_stream)\u00a0\r\nVALUES\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0('test.png',\u00a0@img)\u00a0\r\n\r\ngo\u00a0<\/pre>\n<p>The script is very similar to the initial one; however, this time, it results in an output variable with the image.<\/p>\n<p>Here are the differences between this script and the original:<\/p>\n<ul>\n<li>A <code>varbinary(max)<\/code> variable which will be used as an output<\/li>\n<li>A new R library, <code>hexView<\/code>, must be installed in order to do additional manipulations in the file (yes, you will still use a file)<\/li>\n<li>After saving the plot to disk, it\u2019s read back, reading the raw file content<\/li>\n<li>The variable where you stored the raw image (<code>imageContent)<\/code> is defined as a parameter using the <code>@params<\/code> argument of the <code>sp_execute_external_script<\/code><\/li>\n<li>You need to link this script variable with a variable outside the script to get the content<\/li>\n<li>Finally, you insert the variable into the filetable<\/li>\n<\/ul>\n<p>The result of the execution of the script above will be a new file in the folder, you can check by accessing <a href=\"file:\/\/\/\\\\localhost\\MSSQLSERVER\\RFILES\\Images\"><em>\\\\localhost\\MSSQLSERVER\\RFILES\\Images<\/em><\/a><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-86463\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/02\/a-screenshot-of-a-cell-phone-description-automati-25.png\" alt=\"FileStream results\" width=\"488\" height=\"119\" \/><\/p>\n<h2>Conclusion<\/h2>\n<p>During my research on the web, I saw many examples of R scripts dealing with the file system. These scripts which are running nowadays in SQL Server 2017 will break in SQL Server 2019 unless one of the changes explained in this article is applied.<\/p>\n<p>You can choose to fix the file system permission or start to avoid direct file system access from the R script. I believe Microsoft will provide a fix for this problem soon, what probably will be an easier way to give permissions to the app container SIDs<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>SQL Server Machine Learning allows you to run R and Python scripts from SQL Server. When SQL Server 2019 was released, Microsoft enhanced the security for this functionality, but it caused some existing code writing to the file system to to break. In this article, Dennes Torres explains the security enhancement and describes three ways to work with it.  &hellip;<\/p>\n","protected":false},"author":50808,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[47,53],"tags":[5134],"coauthors":[6810],"class_list":["post-86441","post","type-post","status-publish","format-standard","hentry","category-data-science","category-featured","tag-sql-prompt"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/86441","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\/50808"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=86441"}],"version-history":[{"count":21,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/86441\/revisions"}],"predecessor-version":[{"id":86465,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/86441\/revisions\/86465"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=86441"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=86441"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=86441"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=86441"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}