簡體   English   中英

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

[英]Extracting a .NET Assembly from SQL Server 2005

我正在嘗試幫助一個有SQL CLR相關問題的私人朋友(現在也是客戶)。 他有一個SQL Server,其數據庫中嵌入了3個.NET程序集。 他讓我幫他從數據庫中提取程序集並將它們保存為磁盤上的.dll文件。 這甚至可能嗎?

是的,這是可能的。 程序集的實際二進制表示形式位於服務器的SQL目錄中。 也就是說,如果在sys.assembly_files和sys.assemblies之間運行連接,則可以獲得所需的所有信息。 程序集二進制文件位於sys.assembly_files視圖的content列中。

但是為了從SQL Server中提取二進制表示並將其提取到磁盤上的文件中,您必須編寫一些需要在您引用的程序集所在的同一數據庫上運行的.NET代碼。 在Visual 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();
                }
            }
        }
    }
}

然后構建項目並將其部署到您的數據庫。 確保在SQL Server上啟用了CLR Enabled配置選項。 這可能已經啟用,因為您有程序集。 如果未啟用clr執行,您可以在SSMS上運行以下代碼以啟用它:

sp_configure 'clr enabled', 1
go

reconfigure
go

您需要注意的另一件事是默認情況下SQL服務器可能不允許您從.NET代碼寫入磁盤。 如果在通過調用SSMS中的存儲過程運行上述代碼時出現FileIO安全性錯誤,則需要為程序集配置適當的權限集。 您可以通過SSMS執行此操作:右鍵單擊新程序集並查看“屬性”對話框中的“權限集”。 將其設置為外部訪問。 現在,您應該可以通過在SSMS中運行以下代碼來導出程序集:

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

希望這對你有用......

是。

select * from sys.assembly_files做一個select * from sys.assembly_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

Jonas的方法也適用於Console應用程序或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");
            }
        }
    }
}

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

我找到了一個更簡單的解決方案,這是必要的,因為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;

使用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

沒有任何編碼的替代解決方案是VisualStudio數據庫項目。 創建一個新的數據庫項目並使用程序集導入數據庫。 您將獲得DDL腳本和程序集。

注意,您需要為Visual Studio安裝SSDT

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM