从 SQL Server 2005 中提取 .NET 程序集

发布于 2024-09-30 16:50:35 字数 127 浏览 1 评论 0原文

我正在尝试帮助一位私人朋友(现在也是客户)使用 SQL CLR 相关问题。他有一个 SQL Server,其中的数据库有 3 个 .NET 程序集 嵌入其中。他让我帮他从里面提取组件 数据库并将它们保存为磁盘上的 .dll 文件。这可能吗?

I am trying to help a personal friend (who now also is a client) with a SQL CLR
related problem. He has a SQL Server with a database that has 3 .NET assemblies
embeded in it. He asked me to help him extract the assemblies from within the
database and save them as .dll files on the disk. Is this even possible?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

桃扇骨 2024-10-07 16:50:35

是的,这是可能的。程序集的实际二进制表示形式
在您的服务器的 SQL 目录中。也就是说,如果您在
sys.assemblies_files 和 sys.assemblies 你可以得到你想要的所有信息
需要。程序集二进制文件位于 sys. assembly_files 的内容列中
看法。

但是为了从 SQL Server 中提取二进制表示形式并将其转换为
磁盘上的文件,您将必须编写一些需要在磁盘上运行的.NET代码
您引用的程序集现在所在的数据库相同。视觉上
Studio 启动一个 SQL CLR 项目并使用以下代码向其中添加一个类:

using System;
using System.IO;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Security.Permissions;

namespace ExtractSqlAssembly {
    [PermissionSet(SecurityAction.Demand, Unrestricted = true, Name = "FullTrust")]
    public partial class SaveSqlAssembly {

        [SqlProcedure]
        public static void SaveAssembly(string assemblyName, string path) {
            string sql = @"SELECT AF.content FROM sys.assembly_files AF JOIN sys.assemblies A ON AF.assembly_id = A.assembly_id where AF.file_id = 1 AND A.name = @assemblyname";
            using (SqlConnection conn = new SqlConnection("context connection=true")) {
                using (SqlCommand cmd = new SqlCommand(sql, conn)) {
                    SqlParameter param = new SqlParameter("@assemblyname", SqlDbType.VarChar);
                    param.Value = assemblyName;
                    cmd.Parameters.Add(param);

                    cmd.Connection.Open();  // Read in the assembly byte stream
                    SqlDataReader reader = cmd.ExecuteReader();
                    reader.Read();
                    SqlBytes bytes = reader.GetSqlBytes(0);

                    // write the byte stream out to disk
                    FileStream bytestream = new FileStream(path, FileMode.CreateNew);
                    bytestream.Write(bytes.Value, 0, (int)bytes.Length);
                    bytestream.Close();
                }
            }
        }
    }
}

然后构建该项目并将其部署到数据库。确保 CLR
在 SQL Server 上启用启用配置选项。这大概是
已经启用,因为上面有程序集。如果 clr 执行不
启用后,您可以在 SSMS 上运行以下代码来启用它:

sp_configure 'clr enabled', 1
go

reconfigure
go

您还需要注意的另一件事是,默认情况下 SQL Server 可能会
不允许您从 .NET 代码写入磁盘。如果你得到一个 FileIO
通过调用以下存储过程来运行上述代码时出现安全错误
SSMS,您需要为
集会。您可以通过 SSMS 执行此操作:右键单击新程序集并查看
属性对话框中的权限集。将其设置为外部访问。现在轮到你
应该能够通过在 SSMS 中运行以下代码来导出程序集:

exec SaveAssembly 'AssemblyName', 'f:\path\to\assemblyname.dll'

希望这对您有用...

Yes, this is possible. The actual binary representation of the assemblies live
in the SQL catalog for your server. Namely, if you run a join between
sys.assembly_files and sys.assemblies you can get to all the information you
need. The assemblies binary is in the content column of the sys.assembly_files
view.

But in order to extract the binary representation from SQL Server and into a
file on disk you will have to write some .NET code that needs to run on the
same database where the assemblies you refer to are located now. In Visual
Studio start a SQL CLR project and add a class to it with the following code:

using System;
using System.IO;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Security.Permissions;

namespace ExtractSqlAssembly {
    [PermissionSet(SecurityAction.Demand, Unrestricted = true, Name = "FullTrust")]
    public partial class SaveSqlAssembly {

        [SqlProcedure]
        public static void SaveAssembly(string assemblyName, string path) {
            string sql = @"SELECT AF.content FROM sys.assembly_files AF JOIN sys.assemblies A ON AF.assembly_id = A.assembly_id where AF.file_id = 1 AND A.name = @assemblyname";
            using (SqlConnection conn = new SqlConnection("context connection=true")) {
                using (SqlCommand cmd = new SqlCommand(sql, conn)) {
                    SqlParameter param = new SqlParameter("@assemblyname", SqlDbType.VarChar);
                    param.Value = assemblyName;
                    cmd.Parameters.Add(param);

                    cmd.Connection.Open();  // Read in the assembly byte stream
                    SqlDataReader reader = cmd.ExecuteReader();
                    reader.Read();
                    SqlBytes bytes = reader.GetSqlBytes(0);

                    // write the byte stream out to disk
                    FileStream bytestream = new FileStream(path, FileMode.CreateNew);
                    bytestream.Write(bytes.Value, 0, (int)bytes.Length);
                    bytestream.Close();
                }
            }
        }
    }
}

Then build the project and deploy it to your database. Make sure that the CLR
Enabled configuration option is enabled on the SQL Server. This is probably
already enabled, since you have assemblies on it. In case clr execution is not
enabled you can run the following code on SSMS to enable it:

sp_configure 'clr enabled', 1
go

reconfigure
go

One more thing that you need to be aware of is the by default SQL server may
not allow you to write to the disk from the .NET code. If you get a FileIO
security error when you run the code above by calling the stored procedure in
SSMS, you will need to configure the proper permission set for the
assembly. You can do this via SSMS: right-click the new assembly and look at
the Permission Set in the Properties dialog. Set it to External Access. Now you
should be able to export your assemblies by running the following code in SSMS:

exec SaveAssembly 'AssemblyName', 'f:\path\to\assemblyname.dll'

Hope this works for you...

一个人的旅程 2024-10-07 16:50:35

是的。

执行 select * from sys.assemble_files 来查找所需程序集的 id

DECLARE @IMG_PATH VARBINARY(MAX)
DECLARE @ObjectToken INT

SELECT @IMG_PATH = content FROM sys.assembly_files WHERE assembly_id = 65536

EXEC sp_OACreate 'ADODB.Stream', @ObjectToken OUTPUT
        EXEC sp_OASetProperty @ObjectToken, 'Type', 1
        EXEC sp_OAMethod @ObjectToken, 'Open'
        EXEC sp_OAMethod @ObjectToken, 'Write', NULL, @IMG_PATH
        EXEC sp_OAMethod @ObjectToken, 'SaveToFile', NULL, 'c:\temp\myassembly.dll', 2
        EXEC sp_OAMethod @ObjectToken, 'Close'
        EXEC sp_OADestroy @ObjectToken

Yes.

do a select * from sys.assembly_files to find the id of the assembly you want

DECLARE @IMG_PATH VARBINARY(MAX)
DECLARE @ObjectToken INT

SELECT @IMG_PATH = content FROM sys.assembly_files WHERE assembly_id = 65536

EXEC sp_OACreate 'ADODB.Stream', @ObjectToken OUTPUT
        EXEC sp_OASetProperty @ObjectToken, 'Type', 1
        EXEC sp_OAMethod @ObjectToken, 'Open'
        EXEC sp_OAMethod @ObjectToken, 'Write', NULL, @IMG_PATH
        EXEC sp_OAMethod @ObjectToken, 'SaveToFile', NULL, 'c:\temp\myassembly.dll', 2
        EXEC sp_OAMethod @ObjectToken, 'Close'
        EXEC sp_OADestroy @ObjectToken
一曲琵琶半遮面シ 2024-10-07 16:50:35

Jonas 的方法也可以很好地用作控制台应用程序或 Linqpad 脚本 - 正如他所暗示的那样,代码不需要在 SQL 进程中本地执行。例如,从数据库中提取 tSQLt 程序集(测试工具):

void Main()
{
    var assemblyName = "tSQLtCLR";
    var serverName = "localhost";
    var databaseName = "MyDb";
    var targetDir = Environment.ExpandEnvironmentVariables("%TEMP%");
    var targetFile = Path.Combine(targetDir, assemblyName) + ".dll";

    var sql = @"SELECT AF.content FROM sys.assembly_files AF JOIN sys.assemblies A ON AF.assembly_id = A.assembly_id where AF.file_id = 1 AND A.name = @assemblyName";

    var connectionString = string.Format("Data Source={0};Initial Catalog={1};Integrated Security=true", serverName, databaseName);
    using(var connection = new System.Data.SqlClient.SqlConnection(connectionString)){
        connection.Open();

        var command = connection.CreateCommand();
        command.CommandText = sql;
        command.Parameters.Add("@assemblyName", assemblyName);

        using(var reader = command.ExecuteReader()){
            if(reader.Read()){
                var bytes = reader.GetSqlBytes(0);

                File.WriteAllBytes(targetFile, bytes.Value);
                Console.WriteLine(targetFile);
            }else{
                throw new Exception("No rows returned");
            }
        }
    }
}

Jonas' approach works fine as a Console app or Linqpad script also - there's no need for the code to be executed locally within the SQL process, as he implies. eg extracting the tSQLt assembly (a testing tool) from a database:

void Main()
{
    var assemblyName = "tSQLtCLR";
    var serverName = "localhost";
    var databaseName = "MyDb";
    var targetDir = Environment.ExpandEnvironmentVariables("%TEMP%");
    var targetFile = Path.Combine(targetDir, assemblyName) + ".dll";

    var sql = @"SELECT AF.content FROM sys.assembly_files AF JOIN sys.assemblies A ON AF.assembly_id = A.assembly_id where AF.file_id = 1 AND A.name = @assemblyName";

    var connectionString = string.Format("Data Source={0};Initial Catalog={1};Integrated Security=true", serverName, databaseName);
    using(var connection = new System.Data.SqlClient.SqlConnection(connectionString)){
        connection.Open();

        var command = connection.CreateCommand();
        command.CommandText = sql;
        command.Parameters.Add("@assemblyName", assemblyName);

        using(var reader = command.ExecuteReader()){
            if(reader.Read()){
                var bytes = reader.GetSqlBytes(0);

                File.WriteAllBytes(targetFile, bytes.Value);
                Console.WriteLine(targetFile);
            }else{
                throw new Exception("No rows returned");
            }
        }
    }
}
泛滥成性 2024-10-07 16:50:35

Preet 的解决方案对我有用,但我必须配置 Ole Automation 才能在 SQL Server 2008 R2 上工作。另请注意,SaveToFile 不起作用,也不会给出错误消息,除非 SQL Server 具有该目录的权限。就我而言,我使用了 SQL Server 实例的数据文件夹,效果很好。

EXECUTE SP_CONFIGURE 'show advanced options', 1
RECONFIGURE WITH OVERRIDE
GO

EXEC sp_configure 'Ole Automation Procedures', 1;
RECONFIGURE WITH OVERRIDE
GO

DECLARE @IMG_PATH VARBINARY(MAX)
DECLARE @ObjectToken INT

SELECT @IMG_PATH = content FROM sys.assembly_files WHERE assembly_id = 65546

EXEC sp_OACreate 'ADODB.Stream', @ObjectToken OUTPUT
        EXEC sp_OASetProperty @ObjectToken, 'Type', 1
        EXEC sp_OAMethod @ObjectToken, 'Open'
        EXEC sp_OAMethod @ObjectToken, 'Write', NULL, @IMG_PATH
        EXEC sp_OAMethod @ObjectToken, 'SaveToFile', NULL, 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\myassembly.dll', 2
        EXEC sp_OAMethod @ObjectToken, 'Close'
        EXEC sp_OADestroy @ObjectToken  

EXEC sp_configure 'Ole Automation Procedures', 0;
RECONFIGURE WITH OVERRIDE
GO

EXECUTE SP_CONFIGURE 'show advanced options', 0
RECONFIGURE WITH OVERRIDE
GO

Preet's solution worked for me, but I had to configure Ole Automation to work on SQL Server 2008 R2. Note also that the SaveToFile doesn't work--neither does it give an error message--unless SQL Server has permissions to that directory. In my case I used the data folder of the SQL Server instance which worked fine.

EXECUTE SP_CONFIGURE 'show advanced options', 1
RECONFIGURE WITH OVERRIDE
GO

EXEC sp_configure 'Ole Automation Procedures', 1;
RECONFIGURE WITH OVERRIDE
GO

DECLARE @IMG_PATH VARBINARY(MAX)
DECLARE @ObjectToken INT

SELECT @IMG_PATH = content FROM sys.assembly_files WHERE assembly_id = 65546

EXEC sp_OACreate 'ADODB.Stream', @ObjectToken OUTPUT
        EXEC sp_OASetProperty @ObjectToken, 'Type', 1
        EXEC sp_OAMethod @ObjectToken, 'Open'
        EXEC sp_OAMethod @ObjectToken, 'Write', NULL, @IMG_PATH
        EXEC sp_OAMethod @ObjectToken, 'SaveToFile', NULL, 'C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\myassembly.dll', 2
        EXEC sp_OAMethod @ObjectToken, 'Close'
        EXEC sp_OADestroy @ObjectToken  

EXEC sp_configure 'Ole Automation Procedures', 0;
RECONFIGURE WITH OVERRIDE
GO

EXECUTE SP_CONFIGURE 'show advanced options', 0
RECONFIGURE WITH OVERRIDE
GO
孤星 2024-10-07 16:50:35

我找到了解决此问题的更简单的解决方案,这是必要的,因为 sp_OACreate 似乎不适用于 SQL Server 2017(至少不适用于 Linux 版本)。

您可以使用 BCP 实用程序将程序集写入磁盘上的文件,如下所示:

/opt/mssql-tools/bin/bcp "SELECT content FROM sys.assembly_files WHERE name = '${ASSEMBLY_NAME}'" \
    queryout /tmp/my_assembly.so -f bcp.fmt \
    -S localhost -U sa -P "${SA_PASSWORD}" -d master

并使用此格式文件 (bcp.fmt):

13.0
1
1       SQLBINARY           0       0       ""   1     content                          ""

生成的文件 (/tmp/my_ assembly.so) 可用于创建装配,像这样:

CREATE ASSEMBLY [MyAssembly] AUTHORIZATION [dbo]
FROM '/tmp/my_assembly.so' WITH PERMISSION_SET = SAFE;

I have found a simpler solution to this problem, which was necessary because sp_OACreate does not seem to be available for SQL Server 2017 (at least, not the Linux version).

You can just use the BCP utility to write the assembly to a file on disk, like so:

/opt/mssql-tools/bin/bcp "SELECT content FROM sys.assembly_files WHERE name = '${ASSEMBLY_NAME}'" \
    queryout /tmp/my_assembly.so -f bcp.fmt \
    -S localhost -U sa -P "${SA_PASSWORD}" -d master

And use this format file (bcp.fmt):

13.0
1
1       SQLBINARY           0       0       ""   1     content                          ""

The resulting file (/tmp/my_assembly.so) can be used in the creation of an assembly, like so:

CREATE ASSEMBLY [MyAssembly] AUTHORIZATION [dbo]
FROM '/tmp/my_assembly.so' WITH PERMISSION_SET = SAFE;
╰沐子 2024-10-07 16:50:35

采用 Preet 和 Nate 的解决方案并将其转换为一个脚本,该脚本将使用游标导出所有 clr 过程:

EXECUTE SP_CONFIGURE 'show advanced options', 1
RECONFIGURE WITH OVERRIDE
GO

EXEC sp_configure 'Ole Automation Procedures', 1;
RECONFIGURE WITH OVERRIDE
GO

RAISERROR ('Starting...', 0, 1) WITH NOWAIT

DECLARE @ObjectToken INT
DECLARE @AssemblyLocation VARCHAR(MAX)
DECLARE @Msg VARCHAR(MAX)
DECLARE @Content VARBINARY(MAX)
DECLARE @Count AS INT = (SELECT COUNT(name) FROM sys.assembly_files)

DECLARE AssemblyFiles CURSOR FAST_FORWARD
FOR
  SELECT 
    CAST(ROW_NUMBER() OVER (ORDER BY name) AS VARCHAR(10)) + ' of ' + CAST(@Count AS VARCHAR(10)) + ' - ' + name AS Msg,
    '[a location the server can write to]' + name + '.dll' AS AssemblyLocation,
    content    
  FROM
    sys.assembly_files
  ORDER BY 
    name

OPEN AssemblyFiles
FETCH NEXT FROM AssemblyFiles
INTO @Msg, @AssemblyLocation, @Content

WHILE @@FETCH_STATUS = 0
  BEGIN
    EXEC sp_OACreate 'ADODB.Stream', @ObjectToken OUTPUT
    EXEC sp_OASetProperty @ObjectToken, 'Type', 1
    EXEC sp_OAMethod @ObjectToken, 'Open'
    EXEC sp_OAMethod @ObjectToken, 'Write', NULL, @Content
    EXEC sp_OAMethod @ObjectToken, 'SaveToFile', NULL, @AssemblyLocation, 2
    EXEC sp_OAMethod @ObjectToken, 'Close'
    EXEC sp_OADestroy @ObjectToken  

    RAISERROR (@Msg, 0, 1) WITH NOWAIT

    FETCH NEXT FROM AssemblyFiles
    INTO @Msg, @AssemblyLocation, @Content
  END

CLOSE AssemblyFiles
DEALLOCATE AssemblyFiles

RAISERROR ('Done', 0, 1) WITH NOWAIT

EXEC sp_configure 'Ole Automation Procedures', 0;
RECONFIGURE WITH OVERRIDE
GO

EXECUTE SP_CONFIGURE 'show advanced options', 0
RECONFIGURE WITH OVERRIDE
GO

Taking Preet and Nate's solutions and turning them into a script that will export ALL clr procs using a cursor:

EXECUTE SP_CONFIGURE 'show advanced options', 1
RECONFIGURE WITH OVERRIDE
GO

EXEC sp_configure 'Ole Automation Procedures', 1;
RECONFIGURE WITH OVERRIDE
GO

RAISERROR ('Starting...', 0, 1) WITH NOWAIT

DECLARE @ObjectToken INT
DECLARE @AssemblyLocation VARCHAR(MAX)
DECLARE @Msg VARCHAR(MAX)
DECLARE @Content VARBINARY(MAX)
DECLARE @Count AS INT = (SELECT COUNT(name) FROM sys.assembly_files)

DECLARE AssemblyFiles CURSOR FAST_FORWARD
FOR
  SELECT 
    CAST(ROW_NUMBER() OVER (ORDER BY name) AS VARCHAR(10)) + ' of ' + CAST(@Count AS VARCHAR(10)) + ' - ' + name AS Msg,
    '[a location the server can write to]' + name + '.dll' AS AssemblyLocation,
    content    
  FROM
    sys.assembly_files
  ORDER BY 
    name

OPEN AssemblyFiles
FETCH NEXT FROM AssemblyFiles
INTO @Msg, @AssemblyLocation, @Content

WHILE @@FETCH_STATUS = 0
  BEGIN
    EXEC sp_OACreate 'ADODB.Stream', @ObjectToken OUTPUT
    EXEC sp_OASetProperty @ObjectToken, 'Type', 1
    EXEC sp_OAMethod @ObjectToken, 'Open'
    EXEC sp_OAMethod @ObjectToken, 'Write', NULL, @Content
    EXEC sp_OAMethod @ObjectToken, 'SaveToFile', NULL, @AssemblyLocation, 2
    EXEC sp_OAMethod @ObjectToken, 'Close'
    EXEC sp_OADestroy @ObjectToken  

    RAISERROR (@Msg, 0, 1) WITH NOWAIT

    FETCH NEXT FROM AssemblyFiles
    INTO @Msg, @AssemblyLocation, @Content
  END

CLOSE AssemblyFiles
DEALLOCATE AssemblyFiles

RAISERROR ('Done', 0, 1) WITH NOWAIT

EXEC sp_configure 'Ole Automation Procedures', 0;
RECONFIGURE WITH OVERRIDE
GO

EXECUTE SP_CONFIGURE 'show advanced options', 0
RECONFIGURE WITH OVERRIDE
GO
<逆流佳人身旁 2024-10-07 16:50:35

无需任何编码的替代解决方案是 VisualStudio 数据库项目。
创建新的数据库项目并导入您的数据库与程序集。您将获得 DDL 脚本和程序集。

请注意,您需要 安装适用于 Visual Studio 的 SSDT

Alternative solution without any coding is VisualStudio database project.
Create a new database project and import your database with an assembly. You will get DDL scripts together with assemblies.

Note, you will need to install SSDT for Visual Studio.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文