2010-11-05 11 views
15

J'essaie d'aider un ami personnel (qui est maintenant un client) avec un problème lié à SQL CLR . Il a un serveur SQL avec une base de données qui a 3 assemblages .NET incorporés dedans. Il m'a demandé de l'aider à extraire les assemblages de la base de données et de les enregistrer sous forme de fichiers .dll sur le disque. Est-ce seulement possible?Extraction d'un assemblage .NET à partir de SQL Server 2005

Répondre

12

Oui, c'est possible. La représentation binaire réelle des assemblages est dans le catalogue SQL de votre serveur. A savoir, si vous exécutez une jointure entre sys.assembly_files et sys.assemblies, vous pouvez obtenir toutes les informations dont vous avez besoin . Le fichier binaire des assemblys se trouve dans la colonne de contenu de la vue sys.assembly_files .

Mais afin d'en extraire la représentation binaire de SQL Server et dans un fichier sur le disque, vous devrez écrire un code .NET qui doit fonctionner sur la même base de données où les assemblées vous référer à sont situés maintenant. Dans Visual Studio de démarrer un projet CLR SQL et ajouter une classe à avec le code suivant:

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(); 
       } 
      } 
     } 
    } 
} 

ensuite construire le projet et le déployer à votre base de données. Assurez-vous que l'option de configuration CLR Enabled est activée sur SQL Server. C'est probablement déjà activé, puisque vous avez des assemblages dessus. Si l'exécution de clr n'est pas activé, vous pouvez exécuter le code suivant sur SSMS pour l'activer:

sp_configure 'clr enabled', 1 
go 

reconfigure 
go 

Une chose que vous devez être au courant est le par défaut du serveur SQL peut ne vous permet pas d'écrire sur le disque à partir du code .NET. Si vous obtenez une erreur de sécurité FileIO lorsque vous exécutez le code ci-dessus en appelant la procédure stockée dans SSMS, vous devez configurer le jeu d'autorisations approprié pour l'assembly . Vous pouvez le faire via SSMS: cliquez avec le bouton droit sur le nouvel assemblage et regardez le jeu d'autorisations dans la boîte de dialogue Propriétés. Réglez-le sur Accès externe. Maintenant, vous devriez être en mesure d'exporter vos assemblages en exécutant le code suivant dans SSMS:

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

espère que cela fonctionne pour vous ...

+0

Cette approche fonctionne bien lorsqu'elle est exécutée ernally aussi - vous n'avez pas besoin d'aller sur la route SQLCLR. Voir ma réponse ci-dessous – piers7

8

Oui.

faire un select * from sys.assembly_files pour trouver l'identifiant de l'assemblage que vous voulez

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 
+0

génial. Simple et propre. Je vous remercie. –

3

solution de Preet a fonctionné pour moi, mais je devais configurer Ole Automation pour travailler sur SQL Server 2008 R2. Notez également que SaveToFile ne fonctionne pas - il ne donne pas non plus de message d'erreur - à moins que SQL Server n'ait des permissions sur ce répertoire. Dans mon cas, j'ai utilisé le dossier de données de l'instance SQL Server qui a bien fonctionné.

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 
6

approche Jonas fonctionne très bien comme une application console ou d'un script LINQPad aussi - il n'y a pas besoin de code à exécuter localement dans le processus SQL, comme il l'indique.par exemple, l'extraction de l'assemblage tSQLt (un outil de test) à partir d'une base de données:

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"); 
      } 
     } 
    } 
} 
0

Prendre Preet et des solutions Nate et les transformer en un script qui sera exporté procs clr l'aide d'un curseur:

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 
0

I ont trouvé une solution plus simple à ce problème, qui était nécessaire car sp_OACreate ne semble pas être disponible pour SQL Server 2017 (au moins, pas la version Linux).

Vous pouvez simplement utiliser l'utilitaire BCP pour écrire l'ensemble à un fichier sur le disque, comme suit:

/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 

Et utiliser ce fichier de formatage (bcp.fmt):

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

Le le fichier résultant (/tmp/my_assembly.so) peut être utilisé dans la création d'un assembly, comme ceci:

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