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
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 ...
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
génial. Simple et propre. Je vous remercie. –
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
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");
}
}
}
}
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
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;
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