Following things are important to notice:
- The VS Project should be set to Build | Optimize code
- The VS Project must be set to Signning | Sign the assembly with Strong Name Key File ….snk
- The VS Project must NOT set Signing | Delay sign only
(this would lead to Msg 10314 Strong Name validation error) - Windows SDK with the Strong Name Components must be installed
- The VS Project must be set to Build events | Post-build event:
“C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64\signtool.exe” sign /f “$(ProjectDir)YourPrivateCertificate.pfx” /p “YourPfxPassword” $(TargetDir)$(TargetFileName)
Change the Version number according to your installed SDK^^.
-- Steps: -- 1. Replace Placeholders in this Script -- 2. Create Certificate/Private Key/PFX File -- 3. Adapt your C#/VB .NET Project with the SQL CLR Assembly to use the Certificate (PFX file). -- 4. Make changes to Server Instance to enable Common Language Runtime. Server-Instanz so einstellen, dass die strikte Security deaktiviert wird für CLR. -- 5. Create CLR Assembly in Sql Server. -- 6. (optionally) Drop old CLR Functions. -- 7. Create new CLR Functions. /* This Article is based on input from: https://github.com/MicrosoftDocs/sql-docs/issues/2052 (Comment of Solomon Rutzky) https://nielsberglund.com/2017/07/01/sqlclr-and-certificates/ (Niels Berglund) https://nielsberglund.com/2017/07/23/sql-server-2017-sqlclr---whitelisting-assemblies/ (Niels Berglund) Regards to all of you - thank you so much for your publishment! */ -- 1... /* Replace each of the following Placeholders using Ctrl+H with your own values: DWH -> Database name where the Assembly should be deployed to (excluding []) Win10Sdk64bitPath -> Path to the installation of Windows 10 SDK 64bit: C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64 CertFolderPath -> Path to the folder where the Certificate, Private Key and PFX files will be saved (excluding ""). e.g.: C:\DWH\Certificates AssemblyFullPath -> Full Path to the DLL file to create the CRL Assembly from (excluding ""). e.g.: C:\DWH\SqlServerClrFunctionsLib.dll SqlClrAssemblyName -> Name of Assembly under which it should be registered in your Database. e.g.: ClrFunctions CertAuthority -> Canonical Name of the Certificate's Authority (excluding "") e.g.: CompanyName Authority CertName -> Name of the Certificate file (excluding .cer, folders and excluding "") e.g.: Assemblies.CompanyName.Public CertPvkName -> Name of the PVK file (excluding .pvk, folders and excluding "") e.g.: Assemblies.CompanyName.PrivateKey CertPfxName -> Name of the PFX file (excluding .pfx, folders and excluding "") e.g.: Assemblies.CompanyName.PrivateKey CertPvkPassword -> Your strong Private Key Password (excluding "") CertPfxPassword -> Another (less-strong) Password to protect the PFX file (excluding "") */ -- 2.... -- Use Windows 10 SDK makecert.exe and pvk2pfx.exe to create a (public) Certificate, Private Key and export a PFX file: -- https://developer.microsoft.com/de-de/windows/downloads/windows-10-sdk/ -- 1. "Win10Sdk64bitPath\makecert.exe" -r -pe -n "CN=CertAuthority" -a sha256 -sky signature -cy authority -sv "CertFolderPath\CertPvkName.pvk" -len 2048 -m 144 "CertFolderPath\CertName.cer" -- you will be prompted for a Private Key Password - enter: CertPvkName -- -- 2. "Win10Sdk64bitPath\pvk2pfx.exe" -pvk "CertFolderPath\CertPvkName.pvk" -spc "CertFolderPath\CertName.cer" -pfx "CertFolderPath\CertPfxName.pfx" -pi "CertPvkName" -po "CertPfxPassword" -- 3.... -- 1. Copy previously created PFX file to the root of your C# .Net Project so that $(ProjectDir) matches the directory of the PFX file. -- 2. Sign DLL in Visual Studio using the created PFX file in step 2 by adding the following command to ---->>>> Post Build Event: <<<<<---- -- "Win10Sdk64bitPath\signtool.exe" sign /f "$(ProjectDir)CertPfxName.pfx" /p "CertPfxPassword" $(TargetDir)$(TargetFileName) -- 3. Build Project -- -- 4. Copy your signed DLL to "AssemblyFullPath" of the SQL Server. -- 4.... USE MASTER GO EXEC sp_configure 'show advanced options', 1; RECONFIGURE; GO EXEC sp_configure 'clr_enabled', 1 RECONFIGURE GO EXEC sp_configure 'clr strict security', 0; RECONFIGURE; GO -- Re-visit current server configuration (displays a list of all server settings): EXEC sp_configure GO /* Optional: Sometimes it might be necessary to change the Owner of the Database to the 'SA' (or 'dbo' or another Server Admin). -- This might be the case if your Database has been imported/restored from another server (or by another user). USE [DWH]; --SELECT owner_sid FROM sys.databases WHERE database_id = DB_ID('DWH') SELECT name FROM sys.database_principals WHERE sid = (SELECT TOP 1 owner_sid FROM sys.databases WHERE database_id = DB_ID('DWH')); EXEC sp_changedbowner @loginame = 'sa'; */ -- 5.... -- a.) Ensure, the SQL Server Service User (see services.msc and look out for your SQL Server Instance) -- has Read & Execute Permissions to the Assembly File USE [DWH]; -- Important to set context to the Database where you want to register your DLL! GO CREATE ASSEMBLY SqlClrAssemblyName from 'AssemblyFullPath' WITH PERMISSION_SET = UNSAFE -- use UNSAFE to be aligned with the Permission granted to the Certificate Login previsouly created GO -- In case of SQL Server Error Message 10134: It might be necessary to add your Assembly to the trusted_assemblies if your Functions won't execute... /* SELECT * FROM sys.assemblies a WHERE a.name LIKE '%SqlClrAssemblyName%'; -- Grab the BINARY of the Assembly file: SELECT content FROM sys.assembly_files WHERE name LIKE '%SqlClrAssemblyName%' DECLARE @hash varbinary(64), @clrName nvarchar(4000) = (SELECT name FROM sys.assemblies a WHERE a.name LIKE '%SqlClrAssemblyName%'); SELECT @hash = HASHBYTES('SHA2_512', (SELECT content FROM sys.assembly_files WHERE name LIKE '%SqlClrAssemblyName%')); SELECT @hash as Hash, @clrName as Name; EXEC sp_add_trusted_assembly @hash = @hash, @description = @clrName; SELECT * FROM sys.trusted_assemblies */ -- 6..... USE [DWH]; GO BEGIN TRAN DROP FUNCTION IF EXISTS common.ToBit; -- tbd... DROP FUNCTION IF EXISTS ... -- in case, you run into trouble and need to re-create the Assembly, execute this after Dropping previously created functions... -- DROP ASSEMBLY SqlClrAssemblyName; -- 7..... GO CREATE FUNCTION common.ToBit(@value sql_variant, @valueIfNull bit, @valueIfConvertError bit) RETURNS bit EXTERNAL NAME SqlClrAssemblyName.Scalar.ToBit; GO COMMIT TRAN /* After modifying the DLL it might be necessary to apply the following: ALTER ASSEMBLY SqlClrAssemblyName from 'AssemblyFullPath' WITH PERMISSION_SET = UNSAFE , UNCHECKED DATA; -- !! After changing the Certificate of the DLL, you might be required to update the HASH in the trusted_assemblies by re-invoking the Step 5. !! -- tbd... Create or Drop & re-create Functions... */
Following example C# Scalar.cs file within in Class Library Project and .NET 4.5.2 Target Framework
using System; using System.Text; using System.Data.SqlTypes; using System.Collections; using System.Globalization; using System.Collections.Generic; using System.Text.RegularExpressions; using Common; using SqlServerClrFunctionsLib; using Microsoft.SqlServer.Server; // TODO: For Unit Testing: Set the following options in this Project's Properties: // - Project | Build | Optimize Code => 0 // // TODO: For Deployment to SQL Server: Set the following options in this Project's Properties: // - Project | Build | Optimize Code => 1 /// <summary> /// Constraints for CLR Functions: /// https://docs.microsoft.com/en-us/sql/relational-databases/user-defined-functions/create-user-defined-functions-database-engine?view=sql-server-2017 /// /// Data Type Mapping: /// https://docs.microsoft.com/en-us/sql/relational-databases/clr-integration-database-objects-types-net-framework/mapping-clr-parameter-data?view=sql-server-2017 /// Extract: (SQL Server -> System.Data.SqlTypes.* -> .NET CLR Data Type /// bigint -> SqlInt64 -> Int64, Nullable{Int64} /// bit -> SqlBoolean -> Boolean, Nullable{Boolean} /// date -> SqlDateTime -> DateTime, Nullable{DateTime} /// decimal -> SqlDecimal -> Decimal, Nullable{Decimal} /// float -> SqlDouble -> Double, Nullable{Double} /// nvarchar -> SqlChars | SqlString -> String | Char[] // SQLChars is a better match for data transfer and access, and SQLString is a better match for performing String operations. /// nvarchar(1) -> SqlChars | SqlString -> Char | Nullable{char} /// sql_variant -> None -> Object /// </summary> public class Scalar { [SqlFunction(DataAccess = DataAccessKind.None, IsDeterministic = true)] public static string LTrim(string value, string charsToTrim) { if (value == null || value == "") return value; else { return value.TrimStart(charsToTrim.ToCharArray()); } } /// <summary> /// Description: Konvertiert den übergebenen Wert @value in den entsprechenden Ja/Nein (Bit)-Wert (1 Bit). /// Es werden auch Strings mit Ja/Nein, J/N, Yes/No, Y/N, Si/No, True/False erkannt, sowie X/- erkannt. /// /// Wenn @value NULL ist, wird @valueIfNull zurückgegeben. /// Wenn ein Konvertierungsfehler auftritt, wird @valueIfConvertError zurückgegeben. /// </summary> /// <param name="value"></param> /// <param name="valueIfNull"></param> /// <param name="valueIfConvertError"></param> /// <returns></returns> [SqlFunction(DataAccess = DataAccessKind.None, IsDeterministic = true)] public static bool? ToBit(Object value, bool? valueIfNull, bool? valueIfConvertError) { convertStringAgain: try { long lngValue; if (value == DBNull.Value) return valueIfNull; else if (value.GetType() == typeof(SqlBoolean)) return (bool?)((SqlBoolean)value).Value; else if (value is bool) return (bool)value; else if (value.GetType() == typeof(SqlString)) { string s = ((SqlString)value).Value; if (s.StartsWithAny(StringComparison.CurrentCultureIgnoreCase, "J", "Y", "S", "T", "W", "1", "X")) return true; else if (s.Length == 0 || s.StartsWithAny(StringComparison.CurrentCultureIgnoreCase, "N", "F", "0", "-", " ")) return false; } else if (value.GetType() == typeof(SqlChars)) { char[] val = ((SqlChars)value).Value; char c = val.Length > 0 ? val[0] : ' '; if (c.In('J', 'Y', 'S', 'T', 'W', '1', 'j', 'y', 's', 't', 'w', 'X', 'x')) return true; else if (c.In('N', 'F', '0', 'n', 'f', '-', ' ')) return false; } else if (value.GetType() == typeof(string)) { value = new SqlString((string)value); goto convertStringAgain; } else if (long.TryParse(Convert.ToString(value), out lngValue)) return lngValue != 0; return valueIfConvertError; } catch (Exception ex) { throw new InvalidOperationException(string.Format("Exception when converting the value '{0}' of type '{1}' to Bool! Message: {2}", value, value.GetType().FullName, ex.Message)); } } }