Using sp_executesql parameter to open a master key - sql-server

I can use
DECLARE #p1 VARCHAR(128) = 'spt_fallback_db';
EXEC master.dbo.sp_executesql N'SELECT * FROM sys.tables WHERE name = #p1;', N'#p1 varchar(128)', #p1 = #p1;
which executes
SELECT * FROM sys.tables WHERE name = 'enter param here';
similarly, I'd like to execute
OPEN MASTER KEY DECRYPTION BY PASSWORD = 'enter param here';
it looks the same as above and here is what I have tried:
DECLARE #p1 VARCHAR(128) = 'secure_password';
EXEC UserDatabase.dbo.sp_executesql N'OPEN MASTER KEY DECRYPTION BY PASSWORD = #p1;', N'#p1 nvarchar(128)', #p1 = #OpenSesame;
Unfortunately I get:
Incorrect syntax near '#p1'.
Can anyone help explain what I'm doing wrong? Thanks in advance
For context, this is a controlled test effort. I have a number of databases where I need to open the master key, run some code, close it. I thought I could use one central database to run this from. I could do the following:
DECLARE #SecurePassword VARCHAR(128) = '123';
DECLARE #Context VARCHAR(128) = 'UserDatabase.dbo.sp_executesql';
DECLARE #Command NVARCHAR(128) = CONCAT('OPEN MASTER KEY DECRYPTION BY PASSWORD = ''', #SecurePassword, ''';');
EXEC #Context #Command;
I guess I thought I was being clever making the password a parameter

Related

Passing Variable Into OpenQUERY SQL Server 2016

EDIT: SQL Server Version
I'm trying to pass this variable into my open query using this guide from Microsoft: Link
I'm running into this error message "Statement(s) could not be prepared." Which I believe means something is wrong with the OpenQuery. I'm just not sure what is wrong.
Here's the code:
DECLARE #ticketid INT, #QLFD VARCHAR(8000)
SELECT #ticketid = '296272348'
SELECT #QLFD = 'SELECT
*
FROM
OPENQUERY(
[Server_name],''
SELECT
ticket_id
, QLFD_SPD_AMT
FROM [database].[dbo].[table]
WHERE ticket_id = #ticketid
'')'
EXEC (#QLFD)
Could you help me identify the error? I prefer to do it passing the whole query as one.
Thanks!
Edit:
After looking at suggestions made by #Larnu. I have adjusted my code to:
DECLARE #ticketid INT--, #QLFD NVARCHAR(Max)
SELECT #ticketid = '296272348'
DECLARE #QLFD NVARCHAR(Max) = 'SELECT
*
FROM
OPENQUERY(
[Server_name],''
SELECT
ticket_id
, QLFD_SPD_AMT
FROM [database].[dbo].[table]
WHERE ticket_id = QUOTENAME(#ticketid, '''''''')
'')';
EXEC (#QLFD);
As I mentioned, you can't parametrise a query with OPENQUERY you have safely inject the values.
Normally that would be with QUOTENAME or REPLACE, but you don't actually need to do that here, due to the value being a numerical data type, so you can just concatenate it in:
DECLARE #ticketid int = 296272348; --Don't wrap numerical datatypes with quotes.
DECLARE #SQL nvarchar(MAX),
#OpenQuery nvarchar(4000);
SET #OpenQuery = CONCAT(N'SELECT QLFD_SPD_AMT
FROM [database].[dbo].[table]
WHERE ticket_id = ',#ticketid,N';'); --As it's an int we dont need to quote
SET #SQL = CONCAT(N'SELECT #ticketid AS ticket_id, QLFD_SPD_AMT
FROM OPENQUERY([servername],N''',REPLACE(#OpenQuery,'''',''''''),N''');';
EXEC sys.sp_executesql #SQL, N'#ticketid int', #ticketid;

Add Random Characters to a new Login - SQL Server

Trying to add two random character to the end of a newly created login name. So let's say the hard coded login name is 'TestUser'. I want to be able to add random letters and numbers to the end of 'TestUser' so it can be 'TestUser13', or 'TestUserA5'.
However I am getting an syntax error:
Incorrect syntax near the keyword 'LEFT'.
Below is my code where the syntax error gets returned after executing:
DECLARE #NewPassword VARCHAR(100)
DECLARE #Sql NVARCHAR(500)
DECLARE #RandomChar NVARCHAR(50)
SET #RandomChar = N'SELECT LEFT(NEWID(), 2)'
EXEC sp_executesql #RandomChar
--Calls function to generate random password
EXEC #NewPassword = dbo.GenerateRndPassword
--Creates login with random password
SET #Sql = 'CREATE LOGIN TestUser' + #RandomChar + 'WITH PASSWORD = ''' + #NewPassword + ''', DEFAULT_DATABASE = [master], CHECK_EXPIRATION = OFF, CHECK_POLICY = OFF'
EXEC sp_executesql #Sql
--PRINT #Sql
SELECT 'TestUser' + #RandomChar AS Login
SELECT #NewPassword AS Password
Anyone help would be appreciated.
You have two issues going on here. First is you have to convert your newid to a varchar so you can get the left two characters. But really don't need dynamic sql here anyway. The second issue is you need a space before WITH PASSWORD. Your final code would look something like this.
DECLARE #NewPassword VARCHAR(100)
DECLARE #Sql NVARCHAR(500)
DECLARE #RandomChar NVARCHAR(50)
SELECT #RandomChar = LEFT(convert(varchar(50), NEWID()), 2)
--Calls function to generate random password
EXEC #NewPassword = dbo.GenerateRndPassword
--Creates login with random password
SET #Sql = 'CREATE LOGIN TestUser' + #RandomChar + ' WITH PASSWORD = ''' + #NewPassword + ''', DEFAULT_DATABASE = [master], CHECK_EXPIRATION = OFF, CHECK_POLICY = OFF'
EXEC sp_executesql #Sql
--PRINT #Sql
SELECT 'TestUser' + #RandomChar AS Login
SELECT #NewPassword AS Password
I think NEWID() would need to be converted or cast to a string before LEFT() can operate on it.
(this really ought to be a comment but I don't have enough rep.)

SQL query results to mail- Receiving Error formatting query, probably invalid parameters error

I am trying to send the invalid codes to mail. However when I execute the job for stored procedure, it completed fine and did not send any email. When I execute the query alone, I am getting the results. Did I miss any configuration setup?
CREATE PROCEDURE sp_Send_Email_Invalid_code
#MONTH AS int = 0
AS
BEGIN
DECLARE #Email_Subject AS nvarchar(250) = 'Codes Missing in master table';
DECLARE #Email_Receipients_To AS nvarchar(max) = 'xxx.yyy.com';
DECLARE #IsUseEmailConfig AS bit = 1;
DECLARE #Email_Category AS nvarchar(250);
SET #IsUseEmailConfig = 0
SET #Email_Category = 'Codes Missing in master table'
SELECT DISTINCT CODE
INTO #temp_1
FROM tblRegCode nolock
SELECT
*
FROM
#temp_1
DECLARE #Query nvarchar(max);
SET #Query = 'SELECT * FROM #temp_1';
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'psmEmailer',
#recipients = 'xxx.yyy.com',
#query = #Query,
#subject = 'Missing codes',
#attach_query_result_as_file = 1 ;
DROP TABLE #temp_1;
END
My SQL Server agent job is
DECLARE #MONTH AS int = 0
DECLARE #dt datetime = GETDATE()
SET #MONTH = DATEPART(mm, DATEADD(mm, -1, #dt))
EXEC sp_Send_Email_Invalid_code #MONTH
This is difficult to answer because there isn't anything obvious in the code above that is coursing the issue. Therefore the problem must be else where. My thoughts are 1) your query result set isn't returning anything. 2) Your email config is correct meaning the email can't be sent.
For number 1, we can't resolve this without checking your data, so moving on.
For number 2, try sending a simple email from the SQL instance using the send mail procedure in isolation. Like this:
EXEC msdb.dbo.sp_send_dbmail
#recipients = 'you#somewhere.com',
#subject = 'Send Mail Test',
#body = 'Testing',
#body_format = 'HTML';
Then is you don't receive an email check the failure logs in MSDB with the following query.
USE [msdb]
GO
SELECT
l.[description] AS 'Error failure',
f.[recipients],
f.[subject],
f.[sent_date],
f.[body],
f.[body_format]
FROM
[dbo].sysmail_faileditems f
JOIN [dbo].sysmail_event_log l
ON f.[mailitem_id] = l.[mailitem_id]
ORDER BY
f.[sent_date] DESC
If you do receive the test email I'd suggest the problem is with your query results that your trying to attach.
Just another thought. You may also need to use the param #execute_query_database and #query_attachment_filename.
Check https://msdn.microsoft.com/en-us/library/ms190307.aspx for details.
So, you can make from your SP just this:
CREATE PROCEDURE sp_Send_Email_Invalid_code
#MONTH AS int
AS
BEGIN
DECLARE #Query nvarchar(max) = 'SELECT [CODE ] FROM tblRegCode nolock';
EXEC msdb.dbo.sp_send_dbmail
#profile_name = 'psmEmailer',
#recipients = 'xxx.yyy.com',
#query = #Query,
#subject = 'Codes Missing in master table',
#attach_query_result_as_file = 1 ;
END
GO
In the SP you are not using #MONTH, why?
Try to add WITH EXECUTE AS OWNER in your SP, and run it via job.

How to retrieve a part of a stored procedure's header?

I have a C# WinForms application that manages stored procedures used by different services. What the users see is something like that:
exec stored_procedure_name param1, param2, param3
And since param1 doesn't mean anything to them (they can't see the stored procedure), I would like to present to them small descriptions of the parameters which are normally saved in the header of the stored procedure.
A typical stored procedure would like:
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[stored_procedure_name]') AND type in (N'P', N'PC'))
DROP PROCEDURE [dbo].[stored_procedure_name]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
/*************************************************************************************
[Procedure Info]
Author = myName
Description = this stored procedure returns results.
**************************************************************************************
[Parameters Info]
#param1 = this is parameter one
#param2 = this is parameter two
#param3 = this is parameter three
**************************************************************************************
[Changes]
2015-06-17 The stored procedure is optimized.
*/
CREATE PROCEDURE [dbo].[stored_procedure_name]
#param1 int,
#param2 nvarchar(20),
#param3 nvarchar(10)
AS
BEGIN
-- SP code here
END
GO
From the above, I want to get the descriptions in the Parameters info. I know that I can use the following SQL to retrieve the stored procedure as text/table:
CREATE TABLE #tmpHeader
(
TEXT NVARCHAR(1000)
)
INSERT INTO #tmpHeader
EXEC sp_helptext 'stored_procedure_name';
SELECT * FROM #tmpHeader
DROP TABLE #tmpHeader
Any ideas or suggestions how to proceed from here in order to get the content of the parameters info?
I am also open to any other suggestions.
Consider using extended properties to store meta-data. This is much cleaner than parsing the module text.
EDIT:
The example below returns all parameters plus descriptions for those parameters with extended properties. In your code, you can pass the schema and object names as parameters instead of the local variables used here for illustration.
EXEC sp_addextendedproperty #name = N'Description',
#value = 'this is parameter one', #level0type = N'Schema',
#level0name = 'dbo', #level1type = N'Procedure',
#level1name = 'stored_procedure_name', #level2type = N'Parameter',
#level2name = '#param1';
GO
EXEC sp_addextendedproperty #name = N'Description',
#value = 'this is parameter two', #level0type = N'Schema',
#level0name = 'dbo', #level1type = N'Procedure',
#level1name = 'stored_procedure_name', #level2type = N'Parameter',
#level2name = '#param2';
GO
EXEC sp_addextendedproperty #name = N'Description',
#value = 'this is parameter three', #level0type = N'Schema',
#level0name = 'dbo', #level1type = N'Procedure',
#level1name = 'stored_procedure_name', #level2type = N'Parameter',
#level2name = '#param3';
GO
DECLARE
#SchemaName sysname = 'dbo'
, #ObjectName sysname = 'stored_procedure_name';
SELECT properties.objtype
, properties.objname
, parms.name
, properties.value
FROM sys.parameters AS parms
LEFT JOIN fn_listextendedproperty('Description', 'Schema', #SchemaName, 'Procedure',
#ObjectName, 'Parameter', DEFAULT) AS properties ON
properties.objname COLLATE DATABASE_DEFAULT = parms.name
WHERE
parms.object_id = OBJECT_ID(QUOTENAME(#SchemaName) + '.' + QUOTENAME(#ObjectName));
GO
This should make the think your looking for.
SELECT SUBSTRING(definition,CHARINDEX(N'[Parameters Info]',definition),CHARINDEX(N'[',definition,CHARINDEX(N'[Parameters Info]',definition)+1)-CHARINDEX(N'[Parameters Info]',definition))
FROM sys.sql_modules
WHERE object_id = OBJECT_ID('YOUR PROCEDURE!!!')
It searches for Parameters Info and goes any further until if finds another header block (beginning with [). You can also specify that it should search for *.

"<xyz> must be the first statement in a query batch" when using generated DDL

I have a server with over 100 databases linked to other database servers. These databases have views of the linked tables. I need to update the views weekly for any object changes in the linked servers, ie column adds.
I create this script to loop through all the databases and grab all the views and refresh them by doing alter view. sp_refreshview does not work with linked servers.
When I print the #sql variable, it works fine in another query window. When I try to execute the #sql variable, it gives me the following error:
Msg 111, Level 15, State 1, Line 3
'ALTER VIEW' must be the first statement in a query batch.
I think it has something to do with the LF/CR. I have tried many ways with no luck.
Any ideas?
DECLARE #command varchar(1000)
CREATE TABLE #tempViewSQL (DBName VARCHAR(255)
,ViewSQL VARCHAR(4000))
SELECT #command = 'IF ''?'' NOT IN(''master''
, ''model''
, ''msdb''
, ''tempdb''
,''pubs''
,''AuditProduction''
,''AuditProductionTest''
,''IID_Support''
,''Insurance_Files''
,''LoansAnalysis''
,''QualityAudit''
,''QualityAuditTest'')
BEGIN
USE ?
INSERT INTO #tempViewSQL
SELECT TABLE_CATALOG, replace(view_definition,''create view'',''alter view'')
FROM information_schema.views
WHERE TABLE_NAME NOT IN (''syssegments'',''sysconstraints'')
END'
EXEC sp_MSforeachdb #command
DECLARE #SQLCursor VARCHAR(2000)
DECLARE #SQL VARCHAR(2000)
DECLARE #DbName VARCHAR(255)
DECLARE MyCursor CURSOR FOR
SELECT DBName, ViewSQL FROM #tempViewSQL
OPEN MyCursor
FETCH NEXT FROM MyCursor INTO #DbName,#SQLCursor
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SQL = 'USE ' + #DBName + CHAR(10) + CHAR(13) + 'GO' + CHAR(10) + CHAR(13) + #SQLCursor
--PRINT (#SQL)
EXECUTE (#SQL)
FETCH NEXT FROM MyCursor INTO #DbName,#SQLCursor
END
CLOSE MyCursor
DEALLOCATE MyCursor
DROP TABLE #tempViewSQL
"GO" is not actually valid T-SQL. It's just a string which various SQL tools such as SSMS recognize as a batch separator (as if you ran each chunk separately).
So you probably got an error along the lines of "Incorrect syntax near 'GO'" as well.
In order to create a view in another database, you will need to run sp_executesql in the context of that database, such as:
EXEC OtherDatabase.dbo.sp_executesql #SQL;
Credit to Bob Pusateri's blog for that insight.
However, you have a dynamic database name, which makes it extra complicated. I believe you could probably EXEC dynamic SQL which contained the sp_executesql command qualified with the dynamic database name, Incpetion-style. But you'll have to be careful about your single quote encoding.
You can try to execute query inside of specific database through using sp_executesql SP like below::
DECLARE #AlterQuery NVARCHAR(MAX) = N'ALTER VIEW v1 AS SELECT * from T1'
DECLARE #DbName NVARCHAR(MAX) = 'Test'
DELARE #Query NVARCHAR(MAX) = 'exec [' + #DbName + '].sys.sp_executesql N''' + REPLACE( #AlterQuery, '''', '''''' ) + ''''
EXECUTE( #Query )
I like to use Powershell for deployments like this where I store all of the server/database combinations in a table on a central server and then use this table to populate a list of servers to run across and then loop through them to run some logic. It's not a pure SQL solution, but it can be easily modified to get the job done...
function Get-ProductionDatabases
{
param
(
[Parameter(Mandatory=$true)]
[string]$centralServer,
[Parameter(Mandatory=$true)]
[string]$centralDatabase
)
$conn = New-Object System.Data.SqlClient.SqlConnection "Server=$centralServer;Database=$centralDatabase;Integrated Security=SSPI;";
$dt = New-Object System.Data.DataTable;
$cmd = $conn.CreateCommand();
$cmd.CommandType = [System.Data.CommandType]::Text
$cmd.CommandText = "Select [ServerName],
[DatabaseName]
From [dbo].[ProductionDatabases];";
$conn.Open();
$dt.Load($cmd.ExecuteReader());
$conn.Close();
$dt
}
$productionDatabases = Get-ProductionDatabases -centralServer "ProductionServer\Instance" -centralDatabase "CentralDatabase"
foreach($db in $productionDatabases)
{
$conn = New-Object System.Data.SqlClient.SqlConnection "Server=$($db.ServerName);Database=$($db.DatabaseName);Integrated Security=SSPI;";
$queryOut = New-Object System.Data.DataTable;
$cmd = $conn.CreateCommand();
$cmd.CommandType = [System.Data.CommandType]::Text
$cmd.CommandText = "Exec sp_refreshview;";
$conn.Open();
try
{
$queryOut.Load($cmd.ExecuteReader());
$conn.Close();
}
catch
{
"Warning: Error connecting to $($db.ServerName)."
}
}

Resources