How to encrypt all existing stored procedures of a database - sql-server

Is there any possibility to encrypt all existing stored procedures of a SQL Server 2008 database AFTER they have been created via an SQLCMD script?
The reason I want to do this is the following:
I'd like to develop the stored procedures without encryption so I can easily click on "Modify" in SQL Server Management Studio to check their contents.
However, for the deployment I'd like to encrypt them so I thought that maybe I could write a script which encrypts them only after they're created. For dev systems I simply wouldn't run the script while on end-user systems the script would be run.

You might want to check Encrypting all the Stored Procedures of a Database :
If you ever decide that you need to protect your SQL Stored
Procedures, and thought encrypting was a good idea, BE VERY CAREFUL!!!
Encrypting Database stored procedures SHOULD NOT be done without
having backup files or some sort of Source Control for the stored
procedures. The reason I say this is because, once they are encrypted,
there is no turning around. (Yes, there are third party tools that
will decrypt your code, but Why go through that trouble.)
This trick is something I developed because my company needed to host the application on a different server, and we were concerned
about our code being compromised. So, to deliver the database, we
decided to encrypt all out stored procedures. Having over a hundred
procedures written, I didn't want to open each procedure and paste
'WITH ENCRYPTION' in each and every stored procedure. (For those of
you who do not know how to encrypt, refer How Do I Protect My Stored
Procedure Code[^]). So I decided to make my own little C# application
that did the same.
This application is a console application made
using Visual Studio 2005 and SQL server 2005. The input parameters are
database name, Server address, database username and password. Once
you are able to provide these details, you are ready to have all your
stored procedures encrypted.
I have put the code of my application
here as is. For this code to work, you will need to add an
"Microsft.SQlserver.SMO" refrence to the application, so that the
classes such as "Database" and "StoredProcedure" are accessible.
BEFORE YOU DO THIS, TAKE A BACKUP!!!!!!!
//Connect to the local, default instance of SQL Server.
string DB = "";
ServerConnection objServerCOnnection = new ServerConnection();
objServerCOnnection.LoginSecure = false;
Console.WriteLine("Enter name or IP Address of the Database Server.");
objServerCOnnection.ServerInstance = Console.ReadLine();
Console.WriteLine("Enter name of the Database");
DB = Console.ReadLine();
Console.WriteLine("Enter user id");
objServerCOnnection.Login = Console.ReadLine();
Console.WriteLine("Enter Password");
objServerCOnnection.Password = Console.ReadLine();
Console.WriteLine(" ");
Server srv = new Server();
try // Check to see if server connection details are ok.
{
srv = new Server(objServerCOnnection);
if (srv == null)
{
Console.WriteLine("Server details entered are wrong,"
+ " Please restart the application");
Console.ReadLine();
System.Environment.Exit(System.Environment.ExitCode);
}
}
catch
{
Console.WriteLine("Server details entered are wrong,"
+ " Please restart the application");
Console.ReadLine();
System.Environment.Exit(System.Environment.ExitCode);
}
Database db = new Database();
try // Check to see if database exists.
{
db = srv.Databases[DB];
if (db == null)
{
Console.WriteLine("Database does not exist on the current server,"
+ " Please restart the application");
Console.ReadLine();
System.Environment.Exit(System.Environment.ExitCode);
}
}
catch
{
Console.WriteLine("Database does not exist on the current server,"
+ " Please restart the application");
Console.ReadLine();
System.Environment.Exit(System.Environment.ExitCode);
}
string allSP = "";
for (int i = 0; i < db.StoredProcedures.Count; i++)
{
//Define a StoredProcedure object variable by supplying the parent database
//and name arguments in the constructor.
StoredProcedure sp;
sp = new StoredProcedure();
sp = db.StoredProcedures[i];
if (!sp.IsSystemObject)// Exclude System stored procedures
{
if (!sp.IsEncrypted) // Exclude already encrypted stored procedures
{
string text = "";// = sp.TextBody;
sp.TextMode = false;
sp.IsEncrypted = true;
sp.TextMode = true;
sp.Alter();
Console.WriteLine(sp.Name); // display name of the encrypted SP.
sp = null;
text = null;
}
}
}

I have the same problem.
My solution is to put "-- WITH ENCRYPTION" in all of my stored procedures. This version is used by developers and stored in source control.
I then use a tool (like sed) in my build to replace "-- WITH ENCRYPTION" with "WITH ENCRYPTION" on the files before I send them to be installed.
For a pure SQL solution you could use REPLACE.

WITH ENCRYPTION means that the code behind the proc is not stored in the SysComments table.
You could write a script that does a exec sp_helptext 'MyProcName' and gets the contents into a VarChar (MAX) so it can hold multiline / large procedures easily and then modifiy the procedure from it's original state
CREATE MyProcName AS
SELECT SecretColumns From TopSecretTable
change CREATE to ALTER and AS surrounded by space or tab or newline (good place to use Regular Expressions) to WITH ENCRYPTION AS
ALTER MyProcName WITH ENCRYPTION AS
SELECT SecretColumns From TopSecretTable
This will hide all code for the stored proc on the production server.
You can put this in a LOOP or a CURSOR (not really a set based operation IMHO) for all objects of a specific type and/or naming convention that you want to encrypt, and run it every time you deploy.

I would recommend creating the sproc in a multi-line string variable and then inserting or altering it using sp_executesql. The only annoying downside to this approach is doubling of single quotes for strings.
DECLARE #action varchar(max);
SET #action = 'CREATE'; /* or "ALTER" */
DECLARE #withEncryption varchar(max);
SET #withEncryption = ''; /* or "WITH ENCRYPTION" */
DECLARE #sql varchar(max);
SET #sql = #action + ' PROCEDURE dbo.Something'
(
....
) ' + #withEncryption +
' AS
BEGIN
DECLARE #bob varchar(10);
SET #bob = ''Bob'';
....
END;
';
EXEC sp_executesql #statement = #sql;
[Note the whitespace around the variables.]
All of my scripts use this method, which works well once you get used to the quote doubling thing.
I also use a batch file to call the script, and SQLCMD-mode command line variables to select various behaviours, which makes it repeatable and easy to test.

Use This Query which Encrypt All Procedures in database
CREATE TABLE #backup
(
id BIGINT IDENTITY(1, 1),
sptext NVARCHAR(MAX) NOT NULL,
spname NVARCHAR(100) NOT NULL,
encrypttext NVARCHAR(MAX) NULL,
encryptstatus BIT NOT NULL
DEFAULT ( 0 )
)
DECLARE #sptexttable TABLE
(
id BIGINT IDENTITY(1, 1),
sptext NVARCHAR(MAX),
spname NVARCHAR(100)
)
INSERT INTO #sptexttable ( sptext, spname )
SELECT [text],
[name]
FROM syscomments
JOIN sysobjects ON syscomments.id = sysobjects.id
AND sysobjects.xtype = 'p'
DECLARE #sptext NVARCHAR(MAX)
DECLARE #spname NVARCHAR(100)
DECLARE #counter INT
SET #counter = 1
WHILE #counter <= ( SELECT MAX(id)
FROM #sptexttable
)
BEGIN
BEGIN TRY
INSERT INTO #backup ( sptext, spname )
SELECT sptext,
spname
FROM #sptexttable
WHERE id = #counter
END TRY
BEGIN CATCH
END CATCH
IF NOT EXISTS ( SELECT [name]
FROM sysobjects
WHERE [name] = 'ce_LastIndexOf'
AND xtype = 'FN' )
BEGIN
EXEC
( 'CREATE FUNCTION ce_LastIndexOf
(
#strValue VARCHAR(4000),
#strChar VARCHAR(50)
)
RETURNS INT
AS BEGIN
DECLARE #index INT
SET #index = 0
WHILE CHARINDEX(#strChar, #strValue) > 0
BEGIN
SET #index = #index
+ CASE WHEN CHARINDEX(#strChar, #strValue) > 1
THEN ( LEN(#strValue) - LEN(SUBSTRING(#strValue,
CHARINDEX(#strChar, #strValue)
+ LEN(#strChar),
LEN(#strValue))) )
ELSE 1
END
SET #strValue = SUBSTRING(#strValue,
CHARINDEX(#strChar, #strValue)
+ LEN(#strChar), LEN(#strValue))
END
RETURN #index
END'
)
END
DECLARE #tempproc NVARCHAR(MAX)
DECLARE #procindex INT
DECLARE #beginindex INT
DECLARE #header NVARCHAR(MAX)
DECLARE #asindex INT
DECLARE #replacetext NVARCHAR(MAX)
SET #tempproc = ( SELECT sptext
FROM #sptexttable
WHERE id = #counter
)
IF ( SELECT CHARINDEX('CREATE PROC', UPPER(#tempproc))
) > 0
BEGIN
BEGIN TRY
SELECT #procindex = CHARINDEX('PROC', UPPER(#tempproc))
PRINT #procindex
SELECT #beginindex = CHARINDEX('BEGIN', UPPER(#tempproc))
PRINT #beginindex
SELECT #header = SUBSTRING(#tempproc, #procindex,
#beginindex - #procindex)
SELECT #asindex = ( SELECT dbo.ce_lastindexof(#header, 'AS')
- 2
)
SELECT #replacetext = STUFF(#header, #asindex, 10,
CHAR(13) + 'WITH ENCRYPTION'
+ CHAR(13) + 'AS' + CHAR(13))
SET #tempproc = REPLACE(#tempproc, #header, #replacetext)
END TRY
BEGIN CATCH
END CATCH
END
UPDATE #sptexttable
SET sptext = #tempproc
WHERE id = #counter
--PLAY HERE TO M AKE SURE ALL PROCS ARE ALTERED
UPDATE #sptexttable
SET sptext = ( SELECT REPLACE(sptext, 'CREATE PROC',
'ALTER PROC')
FROM #sptexttable
WHERE id = #counter
)
WHERE id = #counter
SELECT #sptext = sptext,
#spname = spname
FROM #sptexttable
WHERE id = #counter
BEGIN TRY
EXEC ( #sptext
)
UPDATE #backup
SET encrypttext = #sptext,
encryptstatus = 1
WHERE id = #counter
END TRY
BEGIN CATCH
PRINT 'the stored procedure ' + #spname
+ ' cannot be encrypted automatically'
END CATCH
SET #counter = #counter + 1
END
SELECT *
FROM #backup

I wrote a cursor, steps through and encrypts most objects.
DECLARE cur_ENCRYPT_ANTHING CURSOR READ_ONLY
FOR
SELECT STUFF(src.definition,
CASE WHEN CHARINDEX('AS' + CHAR(13),src.definition,1) = 0
THEN CASE WHEN CHARINDEX('AS ' + CHAR(13),src.definition,1) = 0 THEN CHARINDEX('AS ',src.definition,1)
ELSE CHARINDEX('AS ' + CHAR(13),src.definition,1)
END
ELSE CHARINDEX('AS' + CHAR(13),src.definition,1)
END,3,'WITH ENCRYPTION AS' + CHAR(13))
FROM (SELECT o.name
, STUFF(RIGHT(sm.definition,LEN(sm.definition) - CHARINDEX('CREATE ',sm.definition,1) + 1),1,6,'ALTER') AS definition
FROM sys.sql_modules AS sm
JOIN sys.objects AS o ON sm.object_id = o.object_id
WHERE CAST(CASE WHEN sm.definition IS NULL THEN 1
ELSE 0
END AS BIT) = 0
AND type <> 'TR'
) AS src
DECLARE #VLS NVARCHAR(MAX)
OPEN cur_ENCRYPT_ANTHING
FETCH NEXT FROM cur_ENCRYPT_ANTHING INTO #VLS
WHILE (##fetch_status <> -1)
BEGIN
IF (##fetch_status <> -2)
BEGIN
BEGIN TRY
EXEC (#VLS)
END TRY
BEGIN CATCH
PRINT ERROR_MESSAGE()
PRINT ''
PRINT #VLS
END CATCH
END
FETCH NEXT FROM cur_ENCRYPT_ANTHING INTO #VLS
END
CLOSE cur_ENCRYPT_ANTHING
DEALLOCATE cur_ENCRYPT_ANTHING

I have made an update to one of the above answers by removing the dependency on the initial Begin Tag. I had a situation where not all my stored procedures had BEGIN and END.
I used the AS clause instead and also used a case sensitive version of the charindex (by adding a collation)
Its not a perfect solution but helped in getting more of my stored procedures encrypted.
Here is my updated code:
IF OBJECT_ID('tempdb..#backup', 'U') IS NOT NULL
BEGIN
DROP TABLE #backup
END
CREATE TABLE #backup
(
id BIGINT IDENTITY(1, 1),
sptext NVARCHAR(MAX) NOT NULL,
spname NVARCHAR(100) NOT NULL,
encrypttext NVARCHAR(MAX) NULL,
encryptstatus BIT NOT NULL
DEFAULT ( 0 )
)
DECLARE #sptexttable TABLE
(
id BIGINT IDENTITY(1, 1),
sptext NVARCHAR(MAX),
spname NVARCHAR(100)
)
INSERT INTO #sptexttable ( sptext, spname )
SELECT [text],
[name]
FROM syscomments
JOIN sysobjects ON syscomments.id = sysobjects.id
AND sysobjects.xtype = 'p'
DECLARE #sptext NVARCHAR(MAX)
DECLARE #spname NVARCHAR(100)
DECLARE #counter INT
SET #counter = 1
WHILE #counter <= ( SELECT MAX(id)
FROM #sptexttable
)
BEGIN
BEGIN TRY
INSERT INTO #backup ( sptext, spname )
SELECT sptext,
spname
FROM #sptexttable
WHERE id = #counter
END TRY
BEGIN CATCH
END CATCH
IF NOT EXISTS ( SELECT [name]
FROM sysobjects
WHERE [name] = 'CaseSensitiveIndex'
AND xtype = 'FN' )
BEGIN
   
EXEC (
'CREATE FUNCTION dbo.CaseSensitiveIndex(#source nvarchar(max), #pattern VARCHAR(50))
RETURNS int
BEGIN
return CHARINDEX(#pattern COLLATE Latin1_General_CS_AS, #source COLLATE Latin1_General_CS_AS)
END; '
)
end
IF NOT EXISTS ( SELECT [name]
FROM sysobjects
WHERE [name] = 'ce_LastIndexOf'
AND xtype = 'FN' )
BEGIN
    
EXEC
( 'CREATE FUNCTION ce_LastIndexOf
    (#strValue VARCHAR(max),
    #strChar VARCHAR(50))
RETURNS INT
AS
BEGIN
DECLARE #index INT
    
SET #index = 0
WHILE CHARINDEX(#strChar, #strValue) > 0
    BEGIN
        SET #index = #index + CASE WHEN CHARINDEX(#strChar, #strValue) > 1
                     THEN
                        (LEN(#strValue) - LEN(SUBSTRING(#strValue,CHARINDEX(#strChar, #strValue) + LEN(#strChar),LEN(#strValue))))
                     ELSE
                        1
                     END
        SET #strValue = SUBSTRING(#strValue,CHARINDEX(#strChar, #strValue) + len(#strChar),LEN(#strValue))    
    END
    RETURN #index
END'
)
END
DECLARE #tempproc NVARCHAR(MAX)
DECLARE #procindex INT
DECLARE #beginindex INT
DECLARE #header NVARCHAR(MAX)
DECLARE #asindex INT
DECLARE #replacetext NVARCHAR(MAX)
SET #tempproc = ( SELECT sptext
FROM #sptexttable
WHERE id = #counter
)
IF ( SELECT CHARINDEX('CREATE PROC', UPPER(#tempproc))
) > 0
BEGIN
BEGIN TRY
SELECT #procindex = CHARINDEX('PROC', UPPER(#tempproc))
PRINT #procindex
SELECT #beginindex=(select dbo.CaseSensitiveIndex(#tempproc, 'AS'))
if(#beginindex=0) begin set #beginindex=( SELECT dbo.ce_lastindexof(#tempproc, 'AS'))end
SELECT #header = SUBSTRING(#tempproc, #procindex,
#beginindex )
SELECT #asindex = ( SELECT dbo.ce_lastindexof(#header, 'AS')
- 2
)
SELECT #replacetext = STUFF(#header, #asindex, 3,
CHAR(13) + 'WITH ENCRYPTION'
+ CHAR(13) + 'AS' + CHAR(13))
SET #tempproc = REPLACE(#tempproc, #header, #replacetext)
                    
END TRY
BEGIN CATCH
END CATCH
    
END
UPDATE #sptexttable
SET sptext = #tempproc
WHERE id = #counter
--PLAY HERE TO MAKE SURE ALL PROCS ARE ALTERED
UPDATE #sptexttable
SET sptext = ( SELECT REPLACE(sptext, 'CREATE PROC',
'ALTER PROC')
FROM #sptexttable
WHERE id = #counter
)
WHERE id = #counter
SELECT #sptext = sptext,
#spname = spname
FROM #sptexttable
WHERE id = #counter
BEGIN TRY
EXEC ( #sptext)
UPDATE #backup
SET encrypttext = #sptext,
encryptstatus = 1
WHERE id = #counter
END TRY
BEGIN CATCH
PRINT 'the stored procedure ' + #spname
+ ' cannot be encrypted automatically'
END CATCH
SET #counter = #counter + 1
END
SELECT *
FROM #backup where encryptstatus =0

1) I export Create code for SP and functions. Keep it backed up. for example D:\SP2.sql"
2) this transact SQL code, generate the script to delete existing sP & Functions
SELECT 'DROP PROCEDURE [' + SCHEMA_NAME(p.schema_id) + '].[' + p.NAME + ']' as A
FROM sys.procedures p
union
SELECT 'DROP FUNCTION ' + [name]
FROM sysobjects WHERE [type] IN (N'FN', N'IF', N'TF', N'FS', N'FT') AND category = 0
order by a
3) This Poweshell code
replace
AS
BEGIN
by
WITH ENCRYPTION
AS
BEGIN
The code
$File = "D:\SP2.sql"
$File2 = $File.Replace("SP2.sql","SP-WithEncrypt.sql")
$sortie=""
$SP = get-content -path $file
echo $SP.Count
For ($i = 0 ; $i -le $SP.Count)
{ if ($sp[$i] -eq "AS" -and $sp[$i+1] -eq "BEGIN")
{ $AEcrire = "`nWITH ENCRYPTION `n AS `n BEGIN"
$i+=1
}
else
{$AEcrire =$sp[$i]
}
$sortie += "`n$AEcrire"
$i+=1
$SP.Count-$i
}
$sortie| out-file $File2
Would be faster with a .replace( ,), but problem with End of lines...
4) run the SP-WithEncrypt.sql in SSMS

Related

How to find hardcoded values defined in text using MS SQL query without using functions

Requirement: I have a table for storing queries (SQL programs). I need to search and find out in this table such queries which have hardcoded values for a particular column (name) as shown below:
SELECT
*
FROM TABLE1 AC
WHERE
AC.name = 'hardcoded_value1'
UNION
SELECT
*
FROM TABLE2 BC
WHERE BC.name = 'hardcoded_value2'
I have tried and done this using a function and it works fine. But the requirement has a constraint which doesn't allow to make use of any function or stored procedure.
Below is the function definition for reference:-
CREATE OR ALTER FUNCTION [dbo].[GetConstantValue](#QueryID INT)
RETURNS
#Constantvalue TABLE
(
name_ NVARCHAR(2000)
)
AS
BEGIN
Declare #Query NVARCHAR(max) = SELECT code FROM QUERY_TABLE WHERE ID = #QueryID
Declare #StartIndex int = 0,#EndIndex int = 0,#Count int = 0,#ConstStr nvarchar(max) = ''
WHILE #Count <= LEN(#Query)
BEGIN
IF SUBSTRING(#Query,#Count, 1) = CHAR(39)
BEGIN
IF #StartIndex <> 0
BEGIN
SET #ConstStr = #ConstStr + CASE WHEN LEN(#ConstStr)>0 THEN '|' ELSE '' END+ SUBSTRING(#Query,#StartIndex+1,#Count-(#StartIndex+1))
SET #StartIndex = 0
SET #EndIndex = 0
END
ELSE
IF SUBSTRING(#Query,#Count-20, 20) LIKE '%name%[=]%'
SET #StartIndex = #Count
END
SET #Count = #Count + 1
END
INSERT INTO #Constantvalue
SELECT Value FROM string_split(#ConstStr,'|')
RETURN
END
Please suggest me a way to achieve this in the main query itself without making any function calls

Dynamic Database Stored Procedure on SQL Server 2016

I'm trying to build a stored procedure that will query multiple database depending on the databases required.
For example:
SP_Users takes a list of #DATABASES as parameters.
For each database it needs to run the same query and union the results together.
I believe a CTE could be my best bet so I have something like this at the moment.
SET #DATABASES = 'DB_1, DB_2' -- Two databases in a string listed
-- I have a split string function that will extract each database
SET #CURRENT_DB = 'DB_1'
WITH UsersCTE (Name, Email)
AS (SELECT Name, Email
FROM [#CURRENT_DB].[dbo].Users),
SELECT #DATABASE as DB, Name, Email
FROM UsersCTE
What I don't want to do is hard code the databases in the query. The steps I image are:
Split the parameter #DATABASES to extract and set the #CURRENT_DB Variable
Iterate through the query with a Recursive CTE until all the #DATABASES have been processed
Union all results together and return the data.
Not sure if this is the right approach to tackling this problem.
Using #databases:
As mentioned in the comments to your question, variables cant be used to dynamically select a database. Dynamic sql is indicated. You can start by building your template sql statement:
declare #sql nvarchar(max) =
'union all ' +
'select ''#db'' as db, name, email ' +
'from [#db].dbo.users ';
Since you have sql server 2016, you can split using the string_split function, with your #databases variable as input. This will result in a table with 'value' as the column name, which holds the database names.
Use the replace function to replace #db in the template with value. This will result in one sql statement for each database you passed into #databases. Then, concatenate the statements back together. Unfortunately, in version 2016, there's no built in function to do that. So we have to use the famous for xml trick to join the statements, then we use .value to convert it to a string, and finally we use stuff to get rid of the leading union all statement.
Take the results of the concatenated output, and overwrite the #sql variable. It is ready to go at this point, so execute it.
I do all that is described in this code:
declare #databases nvarchar(max) = 'db_1,db_2';
set #sql = stuff(
(
select replace(#sql, '#db', value)
from string_split(#databases, ',')
for xml path(''), type
).value('.[1]', 'nvarchar(max)')
, 1, 9, '');
exec(#sql);
Untested, of course, but if you print instead of execute, it seems to give the proper sql statement for your needs.
Using msForEachDB:
Now, if you didn't want to have to know which databases had 'users', such as if you're in an environment where you have a different database for every client, you can use sp_msForEachDb and check the structure first to make sure it has a 'users' table with 'name' and 'email' columns. If so, execute the appropriate statement. If not, execute a dummy statement. I won't describe this one, I'll just give the code:
declare #aggregator table (
db sysname,
name int,
email nvarchar(255)
);
insert #aggregator
exec sp_msforeachdb '
declare #sql nvarchar(max) = ''select db = '''''''', name = '''''''', email = '''''''' where 1 = 2'';
select #sql = ''select db = ''''?'''', name, email from ['' + table_catalog + ''].dbo.users''
from [?].information_schema.columns
where table_schema = ''dbo''
and table_name = ''users''
and column_name in (''name'', ''email'')
group by table_catalog
having count(*) = 2
exec (#sql);
';
select *
from #aggregator
I took the valid advice from others here and went with this which works great for what I need:
I decided to use a loop to build the query up. Hope this helps someone else looking to do something similar.
CREATE PROCEDURE [dbo].[SP_Users](
#DATABASES VARCHAR(MAX) = NULL,
#PARAM1 VARCHAR(250),
#PARAM2 VARCHAR(250)
)
BEGIN
SET NOCOUNT ON;
--Local variables
DECLARE
#COUNTER INT = 0,
#SQL NVARCHAR(MAX) = '',
#CURRENTDB VARCHAR(50) = NULL,
#MAX INT = 0,
#ERRORMSG VARCHAR(MAX)
--Check we have databases entered
IF #DATABASES IS NULL
BEGIN
RAISERROR('ERROR: No Databases Provided,
Please Provide a list of databases to execute procedure. See stored procedure:
[SP_Users]', 16, 1)
RETURN
END
-- SET Number of iterations based on number of returned databases
SET #MAX = (SELECT COUNT(*) FROM
(SELECT ROW_NUMBER() OVER (ORDER BY i.value) AS RowNumber, i.value
FROM dbo.udf_SplitVariable(#DATABASES, ',') AS i)X)
-- Build SQL Statement
WHILE #COUNTER < #MAX
BEGIN
--Set the current database
SET #CURRENTDB = (SELECT X.Value FROM
(SELECT ROW_NUMBER() OVER (ORDER BY i.value) AS RowNumber, i.value
FROM dbo.udf_SplitVariable(#DATABASES, ',') AS i
ORDER BY RowNumber OFFSET #COUNTER
ROWS FETCH NEXT 1 ROWS ONLY) X);
SET #SQL = #SQL + N'
(
SELECT Name, Email
FROM [' + #CURRENTDB + '].[dbo].Users
WHERE
(Name = #PARAM1 OR #PARAM1 IS NULL)
(Email = #PARAM2 OR #PARAM2 IS NULL)
) '
+ N' UNION ALL '
END
PRINT #CURRENTDB
PRINT #SQL
SET #COUNTER = #COUNTER + 1
END
-- remove last N' UNION ALL '
IF LEN(#SQL) > 11
SET #SQL = LEFT(#SQL, LEN(#SQL) - 11)
EXEC sp_executesql #SQL, N'#CURRENTDB VARCHAR(50),
#PARAM1 VARCHAR(250),
#PARAM2 VARCHAR(250)',
#CURRENTDB,
#PARAM1 ,
#PARAM2
END
Split Variable Function
CREATE FUNCTION [dbo].[udf_SplitVariable]
(
#List varchar(8000),
#SplitOn varchar(5) = ','
)
RETURNS #RtnValue TABLE
(
Id INT IDENTITY(1,1),
Value VARCHAR(8000)
)
AS
BEGIN
--Account for ticks
SET #List = (REPLACE(#List, '''', ''))
--Account for 'emptynull'
IF LTRIM(RTRIM(#List)) = 'emptynull'
BEGIN
SET #List = ''
END
--Loop through all of the items in the string and add records for each item
WHILE (CHARINDEX(#SplitOn,#List)>0)
BEGIN
INSERT INTO #RtnValue (value)
SELECT Value = LTRIM(RTRIM(SUBSTRING(#List, 1, CHARINDEX(#SplitOn, #List)-1)))
SET #List = SUBSTRING(#List, CHARINDEX(#SplitOn,#List) + LEN(#SplitOn), LEN(#List))
END
INSERT INTO #RtnValue (Value)
SELECT Value = LTRIM(RTRIM(#List))
RETURN
END

programmatically generate script for a table

I learned how to generate script for a table.
Eg for this table:
to generate script like this (I omitted something):
CREATE TABLE [dbo].[singer_and_album](
[singer] [varchar](50) NULL,
[album_title] [varchar](100) NULL
) ON [PRIMARY]
GO
INSERT [dbo].[test_double_quote] ([singer], [album_title]) VALUES (N'Adale', N'19')
GO
INSERT [dbo].[test_double_quote] ([singer], [album_title]) VALUES (N'Michael Jaskson', N'Thriller"')
GO
I tried programmatically generating the script using this shell code. And got error:
PS
SQLSERVER:\SQL\DESKTOP-KHTRJOJ\MSSQL\Databases\yzhang\Tables\dbo.test_double_quote>
C:\Users\yzhang\Documents\script_out_table.ps1 "DESKTOP-KHTRJOJ\MSSQL"
"yzhang" "dbo" "test_double_quote",
"C:\Users\yzhang\Documents\script_out.sql"
Multiple ambiguous overloads found for "EnumScript" and the argument
count: "1". At C:\Users\yzhang\Documents\script_out_table.ps1:41
char:16
+ foreach ($s in $scripter.EnumScript($tbl.Urn)) { write-host $s }
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest
Anybody can help? I don't know much about shell.
btw is shell the only way to generate scripts? Can we do it with some sql code? Thank you--
FYI, see this for how to manually generate script. In my case (sql server 2016 management studio) it's like
right click the database name (not table name) -> Tasks -> Generate scripts
choose a table or all tables
click advanced and select schema and data
this is a sql script to genrate table script
declare #vsSQL varchar(8000)
declare #vsTableName varchar(50)
select #vsTableName = '_PRODUCT'--- Your Table Name here
select #vsSQL = 'CREATE TABLE ' + #vsTableName + char(10) + '(' + char(10)
select #vsSQL = #vsSQL + ' ' + sc.Name + ' ' +
st.Name +
case when st.Name in ('varchar','varchar','char','nchar') then '(' + cast(sc.Length as varchar) + ') ' else ' ' end +
case when sc.IsNullable = 1 then 'NULL' else 'NOT NULL' end + ',' + char(10)
from sysobjects so
join syscolumns sc on sc.id = so.id
join systypes st on st.xusertype = sc.xusertype
where so.name = #vsTableName
order by
sc.ColID
select substring(#vsSQL,1,len(#vsSQL) - 2) + char(10) + ')'
Edit: c# code
public string GetScript(string strConnectionString
, string strObject
, int ObjType)
{
string strScript = null;
int intCounter = 0;
if (ObjType != 0)
{
ObjSqlConnection = new SqlConnection(strConnectionString.Trim());
try
{
ObjDataSet = new DataSet();
ObjSqlCommand = new SqlCommand("exec sp_helptext
[" + strObject + "]", ObjSqlConnection);
ObjSqlDataAdapter = new SqlDataAdapter();
ObjSqlDataAdapter.SelectCommand = ObjSqlCommand;
ObjSqlDataAdapter.Fill(ObjDataSet);
foreach (DataRow ObjDataRow in ObjDataSet.Tables[0].Rows)
{
strScript += Convert.ToString(ObjDataSet.Tables[0].Rows[intCounter][0]);
intCounter++;
}
}
catch (Exception ex)
{
strScript = ex.Message.ToString();
}
finally
{
ObjSqlDataAdapter = null;
ObjSqlCommand = null;
ObjSqlConnection = null;
}
}
return strScript;
}
To create Insert script use this store procedure
IF EXISTS (SELECT * FROM dbo.sysobjects
WHERE id = OBJECT_ID(N'[dbo].[InsertGenerator]') AND OBJECTPROPERTY(id,N'IsProcedure') = 1)
DROP PROCEDURE [dbo].[InsertGenerator]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
CREATE PROC [dbo].[InsertGenerator]
(
#tableName varchar(100),
#KeyColumn1 varchar(100)='',
#KeyColumn2 varchar(100)=''
)
AS
-- Generating INSERT statements in SQL Server
-- to validate if record exists - supports 2 field Unique index
--Declare a cursor to retrieve column specific information for the specified table
DECLARE cursCol CURSOR FAST_FORWARD FOR
SELECT column_name,data_type FROM information_schema.columns WHERE table_name = #tableName
OPEN cursCol
DECLARE #string nvarchar(max) --for storing the first half of INSERT statement
DECLARE #stringData nvarchar(max) --for storing the data (VALUES) related statement
DECLARE #dataType nvarchar(1000) --data types returned for respective columns
DECLARE #FieldVal nvarchar(1000) -- save value for the current field
DECLARE #KeyVal nvarchar(1000) -- save value for the current field
DECLARE #KeyTest0 nvarchar(1000) -- used to test if key exists
DECLARE #KeyTest1 nvarchar(1000) -- used to test if key exists
DECLARE #KeyTest2 nvarchar(1000) -- used to test if key exists
SET #KeyTest0=''
IF #KeyColumn1<>''
SET #KeyTest0='IF not exists (Select * from '+#tableName
SET #KeyTest1=''
SET #KeyTest2=''
SET #string='INSERT '+#tableName+'('
SET #stringData=''
SET #FieldVal=''
SET #KeyVal=''
DECLARE #colName nvarchar(50)
FETCH NEXT FROM cursCol INTO #colName,#dataType
IF ##fetch_status<>0
begin
print 'Table '+#tableName+' not found, processing skipped.'
close curscol
deallocate curscol
return
END
WHILE ##FETCH_STATUS=0
BEGIN
IF #dataType in ('varchar','char','nchar','nvarchar')
BEGIN
SET #FieldVal=''''+'''+isnull('''''+'''''+'+#colName+'+'''''+''''',''NULL'')+'',''+'
SET #KeyVal='''+isnull('''''+'''''+'+#colName+'+'''''+''''',''NULL'')+'',''+'
SET #stringData=#stringData+#FieldVal
END
ELSE
if #dataType in ('text','ntext','xml') --if the datatype is text or something else
BEGIN
SET #FieldVal='''''''''+isnull(cast('+#colName+' as varchar(max)),'''')+'''''',''+'
SET #stringData=#stringData+#FieldVal
END
ELSE
IF #dataType = 'money' --because money doesn't get converted from varchar implicitly
BEGIN
SET #FieldVal='''convert(money,''''''+isnull(cast('+#colName+' as varchar(200)),''0.0000'')+''''''),''+'
SET #stringData=#stringData+#FieldVal
END
ELSE
IF #dataType='datetime'
BEGIN
SET #FieldVal='''convert(datetime,'+'''+isnull('''''+'''''+convert(varchar(200),'+#colName+',121)+'''''+''''',''NULL'')+'',121),''+'
SET #stringData=#stringData+#FieldVal
END
ELSE
IF #dataType='image'
BEGIN
SET #FieldVal='''''''''+isnull(cast(convert(varbinary,'+#colName+') as varchar(6)),''0'')+'''''',''+'
SET #stringData=#stringData+#FieldVal
END
ELSE --presuming the data type is int,bit,numeric,decimal
BEGIN
SET #FieldVal=''''+'''+isnull('''''+'''''+convert(varchar(200),'+#colName+')+'''''+''''',''NULL'')+'',''+'
SET #KeyVal='''+isnull('''''+'''''+convert(varchar(200),'+#colName+')+'''''+''''',''NULL'')+'',''+'
SET #stringData=#stringData+#FieldVal
END
--Build key test
IF #KeyColumn1=#colName
begin
SET #KeyTest1 = ' WHERE [' + #KeyColumn1 + ']='
SET #KeyTest1 = #KeyTest1+#KeyVal+']'
end
IF #KeyColumn2=#colName
begin
SET #KeyTest2 = ' AND [' + #KeyColumn2 + ']='
SET #KeyTest2 = #KeyTest2+#KeyVal+']'
end
SET #string=#string+'['+#colName+'],'
FETCH NEXT FROM cursCol INTO #colName,#dataType
END
DECLARE #Query nvarchar(max)
-- Build the test string to check if record exists
if #KeyTest0<>''
begin
if #Keycolumn1<>''
SET #KeyTest0 = #KeyTest0 + substring(#KeyTest1,0,len(#KeyTest1)-4)
if #Keycolumn2<>''
begin
SET #KeyTest0 = #KeyTest0 + ''''
SET #KeyTest0 = #KeyTest0 + substring(#KeyTest2,0,len(#KeyTest2)-4)
end
SET #KeyTest0 = #KeyTest0 + ''')'
SET #query ='SELECT '''+substring(#KeyTest0,0,len(#KeyTest0)) + ') '
end
else
SET #query ='SELECT '''+substring(#KeyTest0,0,len(#KeyTest0))
SET #query = #query + substring(#string,0,len(#string)) + ') '
SET #query = #query + 'VALUES(''+ ' + substring(#stringData,0,len(#stringData)-2)+'''+'')'' FROM '+#tableName
exec sp_executesql #query
CLOSE cursCol
DEALLOCATE cursCol
GO
and use of InsertGenerator like below
DECLARE #return_value int
EXEC #return_value = [dbo].[InsertGenerator]
#tableName = N'_PRODUCT'
SELECT 'Return Value' = #return_value

Searching for records in Microsoft SQL Server

Is it possible to search for records in Microsoft SQL Server manager?
I mean something like in VS pressing Ctrl-F and searching by word?
There is no way in searching for objects in SQL Server management studio. There are views and tables that can be used to search for objects like:
SELECT * FROM sys.tables WHERE name LIKE '%...%'
To search for tables.
If you mean, searching for data within a table, I need to learn T-SQL.
There are non-free 3rd party tools (ex. Redgate's SQL Search), but I use the proc below. You have to create the proc in every [master] database so it's available server-wide, but you can pass in a search term and an optional database name (otherwise it searches through object definitions in all databases):
USE [master]
GO
/****** Object: StoredProcedure [dbo].[sp_FindTextOnServer] Script Date: 10/6/2017 3:39:19 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
/*-----------------------------------------------------------
This procedure will search all or a specified database objects
for the supplied text and return a table with the values
Examples:
EXEC sp_FindTextOnServer 'Billing', 'fcsCore'
EXEC sp_FindTextOnServer 'vJurisdiction'
-----------------------------------------------------------*/
ALTER PROCEDURE [dbo].[sp_FindTextOnServer]
#text VARCHAR(40),
#searchDB VARCHAR(100) = NULL
AS
DECLARE #DisplayText VARCHAR(100),
#sSql VARCHAR(1000),
#line VARCHAR(300),
#char CHAR,
#lineNo INTEGER,
#counter INTEGER,
#AddedRecord BIT,
#dbObjectType VARCHAR(100),
#dbObject VARCHAR(100),
#ObjectBody VARCHAR(7000),
#dbName VARCHAR(100)
SET #DisplayText = #Text
SET #text = '%' + #text + '%'
SET #AddedRecord = 0
CREATE TABLE #SearchResults
(
DBName VARCHAR(100) NOT NULL,
ObjectType VARCHAR(100) NOT NULL,
ObjectName VARCHAR(100) NOT NULL,
Line INT NOT NULL,
Reference VARCHAR(7000) NOT NULL
)
CREATE TABLE #tempSysComments
(
DBName VARCHAR(100),
DBObjectType VARCHAR(100),
DBObject VARCHAR(100),
TextString text
)
--Populate a table with the search results from all databases on the server that include the searched text
SET #sSql = 'USE [?]
SELECT
''?'' AS DBName,
LOWER(REPLACE(o.type_desc, ''_'', '' '')) + '' ('' + RTRIM(type) + '')'' AS DBObjectType,
OBJECT_NAME(sm.object_id) AS DBObject,
CAST(sm.definition AS VARCHAR(7000)) AS TextString
FROM sys.sql_modules AS sm
JOIN sys.objects AS o
ON sm.object_id = o.object_id
WHERE CAST(sm.definition AS VARCHAR(7000)) LIKE ''' + #text + '''
ORDER BY o.type_desc, OBJECT_NAME(sm.object_id)'
IF (#searchDB IS NULL)
BEGIN
INSERT INTO #tempSysComments
EXEC sp_MSFOREachDB #sSql
END
ELSE
BEGIN
SET #sSql = REPLACE(#sSql, '?', #searchDB)
INSERT INTO #tempSysComments
EXEC (#sSql)
END
DECLARE codeCursor CURSOR
FOR
SELECT DBName, DBObjectType, DBObject, TextString
FROM #tempSysComments
WHERE DBName IS NOT NULL
OPEN codeCursor
FETCH NEXT FROM codeCursor INTO #dbName, #dbObjectType, #dbObject, #ObjectBody
IF ##FETCH_STATUS <> 0
BEGIN
PRINT 'Text ''' + #DisplayText + ''' was not found in objects on server ' + ##SERVERNAME
-- Close and release code cursor.
CLOSE codeCursor
DEALLOCATE codeCursor
RETURN
END
-- Search each object within code cursor.
WHILE ##FETCH_STATUS = 0
BEGIN
SET #lineNo = 0
SET #counter = 1
-- Process each line.
WHILE (#counter <> Len(#ObjectBody))
BEGIN
SET #char = SUBSTRING(#ObjectBody, #counter,1)
-- Check for line breaks.
IF (#char = CHAR(13))
BEGIN
SET #lineNo = #lineNo + 1
-- Check if we found the specified text.
IF (PATINDEX(#text, #line) <> 0)
BEGIN
SET #AddedRecord = 1
INSERT #SearchResults
SELECT #dbName, #dbObjectType, #dbObject, #lineNo, LEFT(RTRIM(LTRIM(#line)), 7000)
END
SET #line = ''
END
SET #line = #line + #char
SET #counter = #counter + 1
END
IF (#AddedRecord = 0)
INSERT #SearchResults
SELECT #dbName, #dbObjectType, #dbObject, 0, SUBSTRING(#ObjectBody, 1, 7000)
SET #AddedRecord = 0
FETCH NEXT FROM codeCursor INTO #dbName, #dbObjectType, #dbObject, #ObjectBody
END
-- Close and release cursor.
CLOSE codeCursor
DEALLOCATE codeCursor
-- Return the info.
SELECT DISTINCT DBName, ObjectType, ObjectName, Line, RTRIM(LTRIM(REPLACE(REPLACE(Reference, CHAR(9), ' '), CHAR(13)+CHAR(10), ' '))) AS Reference
FROM #SearchResults
ORDER BY DBName, ObjectType, ObjectName, Line
-- Cleanup.
DROP TABLE #SearchResults
DROP TABLE #tempSysComments
RETURN

MSSQL stored procedure call from ADO - not running properly

I have sp in MSSQL server - code below. When I run it from job, or SSMS it runs OK. But I need to run it from VB6 app with ADODB.
My VB6 code:
Dim cmd As New ADODB.Command
cmd.ActiveConnection = CNN
cmd.CommandTimeout = 180
cmd.CommandText = "dbbackup"
cmd.CommandType = ADODB.CommandTypeEnum.adCmdStoredProc
cmd.Execute(, , ADODB.ConnectOptionEnum.adAsyncConnect)
Problem is: When database backup is almost done - about 90+%, cmd.State changes from Executing to Closed and VB6 code continue in executing (to this moment it waits for sp to complete). But there is a lot of code after backup which never run this way(old backup delete,...). I realized that “Last database backup” property on MSSQL database was not set and in table msdb.dbo.backupset there are no rows for my backup. But there si good restorable backup on HDD.
When i stops program for 5 minutes in debug, sp runs properly to end and everything is OK. This backup code is last code in app run and after it ends program closes all connections and exits. I added wait to VB6 code and it helps on some servers, but many other servers still has same problem.
I think main question is why MSSQL server returns control flow to VB6 code and sp is not completed yet.
Thanks
sp code:
PROCEDURE [dbo].[dbBackup]
AS
BEGIN
SET NOCOUNT ON;
EXEC sp_configure 'show advanced options', 1
RECONFIGURE
EXEC sp_configure 'xp_cmdshell', 1
RECONFIGURE
If OBJECT_ID('tempdb..#DBName','u') IS NULL
Create Table #DBName
(
ID int identity (1,1) ,
Name varchar(128) not null ,
RetentionPeriod int null,
BackupPath varchar(255) default(''),
DBSize float default(0)
)
If OBJECT_ID('tempdb..#ExistingBackups', 'u') IS NULL
Create Table #ExistingBackups
(
Name varchar(128) ,
ID int identity (1,1)
)
Declare #Path varchar(255)
Declare #sql varchar(1000)
Declare #Name varchar(128)
Declare #RetentionPeriod int
Declare #LastBackupToKeep varchar(8)
Declare #ID int
Declare #MaxID int
Declare #eName varchar(255)
Declare #eMaxID int
Declare #eID int
Declare #eTimeStamp varchar(20)
Declare #errMsg nvarchar(2048)
Declare #errCount int; set #errCount = 0;
Declare #freeSpace bigint
Declare #pageSize float
Declare #dbSize bigint
Declare #procDate datetime
Declare #Sklad char(3)
Declare #backupName as varchar(255)
Select #pageSize = v.low / 1024 From master..spt_values v (noLock) Where v.number = 1 And v.[type] = 'E'
Select Top 1 #sklad = sklad_id From dbo.pohyb (noLock) Where Convert(int, sklad_id) > 500
Set #procDate = GETDATE()
Truncate Table #DBName
Insert Into #DBName (Name, RetentionPeriod, BackupPath)
Select DBName, BackupsToStore, BackupPath
From dbo.databaseBackup (noLock)
Where runBackup = 1
Select #MaxID = max(ID), #ID = 0 From #DBName
While #ID < #MaxID
Begin
Select #ID = min(ID) From #DBName Where ID > #ID
Select #Name = Name, #RetentionPeriod = RetentionPeriod, #Path = BackupPath
From #DBName
Where ID = #ID
If SUBSTRING(#Path, Len(#Path), 1) <> '\' Set #Path = #Path + '\'
Set #sql = 'Update #DBName Set DBSize= (Select Round(Sum(size) *' + CONVERT(varchar, #pageSize) + '/1024, 0) From ' + #Name + '.dbo.sysfiles (noLock)) Where Name = ''' + #Name + ''''
Exec (#sql)
Select #dbSize = DBSize From #DBName
--Exec #freeSpace = dbo.getDiskFreeSpace #drive = #Path
--If #freeSpace > #dbSize
--Begin
Set #eTimeStamp = REPLACE(REPLACE(CONVERT(varchar, #procDate, 113), ' ', '_'), ':', '-')
Set #sql = #Path + #Name + '_' + #eTimeStamp + '.bak'
Set #errMsg = 'OK'
Begin Try
SET #backupName = 'Objednavky backup by job ' + CONVERT(varchar, GETDATE(), 104) + ' ' + CONVERT(varchar, GETDATE(), 108);
Backup Database #Name To Disk = #sql
WITH NAME = #backupName;
-------mazanie backupu begin
Truncate Table #ExistingBackups
Set #sql = 'dir /B /OD ' + #Path + #Name + '_*.bak'
Insert #ExistingBackups Exec master..xp_cmdshell #sql
If Exists (Select 1 From #ExistingBackups Where PATINDEX('%File Not Found%', Name) > 0)
Truncate Table #ExistingBackups
Delete From #ExistingBackups Where Name IS NULL
Select #eID = 0
Select #eMaxID = Max(ID) - #RetentionPeriod From #ExistingBackups
While #eID < #eMaxID
Begin
Select #eID = Min(ID) From #ExistingBackups Where ID > #eID
Select #eName = Name From #ExistingBackups Where ID = #eID
Set #sql = 'del ' + #Path + #eName
Exec master..xp_cmdshell #sql
End
Truncate Table #ExistingBackups
-------mazanie backupu end
End Try
Begin Catch
Set #errMsg = #errMsg + '||' + CONVERT(varchar,ERROR_MESSAGE())
Set #errCount = #errCount + 1;
End Catch
--End
--Else
--Set #errMsg = 'Pln? disk (Vo?n? miesto: ' + CONVERT(varchar, #freeSpace) + ' MB, potrebn? aspo?: ' + CONVERT(varchar, #dbSize) + ' MB)'
Insert Into [dbo].[databaseBackup_log] ([Sklad_id], [DBName], [BackupDate], [Status]) Values (#Sklad, #Name, #procDate, Ltrim(Rtrim(CONVERT(varchar,#errMsg))))
End
Drop Table #DBName
Drop Table #ExistingBackups
IF #errCount > 0 BEGIN
RAISERROR (#errMsg, 16, 2) WITH SETERROR
END
RETURN 0;
END

Resources