Configure SQL Server startup parameters from batch file - sql-server

I need to add start-up trace flags to a SQL Service from a batch file. I have struggled to work out how to do this, and have finally come-up with the following, slightly clumsy approach, of using undocumented TSQL procedure and then calling it with SQLCMD. The problem is that it throws an error: RegCreateKeyEx() returned error 5, 'Access is denied.' However, another script on the same server, allows me to change other registry values, such as TCP Port. Any idea what I'm doing wrong, or a more elegant way of doing it?
DECLARE #VALUE nvarchar(200)
DECLARE #Key nvarchar(2000)
SET #VALUE = '-T1118 -3604 -E'
SET #Key = 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL11.' + (select SUBSTRING(##SERVERNAME,CHARINDEX('\',##SERVERNAME)+1,LEN(##SERVERNAME)-1)) + '\MSSQLServer\Parameters'
EXECUTE master..xp_regwrite
'HKEY_LOCAL_MACHINE',
#Key,
'SQLArg3',
'REG_SZ',
#VALUE
The expression to create the #Key variable elavuates to: SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL11.MYINSTANCE\MSSQLServer\Parameters
which seems correct to me.

The registry key for the default instance of SQL Server is:
SET #Key = 'SOFTWARE\Microsoft\Microsoft SQL Server
\MSSQL11.MSSQLSERVER\MSSQLServer\Parameters';
(I wrapped the key above for readability - you need this all on one line.)
Unless you have named instances, you can use this #Key instead of yours. If you have named instances on the servers, you'd need to inspect SOFTWARE\Microsoft\Microsoft SQL Server\InstalledInstances to obtain the name(s) of the instance(s) on the server.
I imagine there is more elegant way of doing this using PowerShell.

Related

Table-Based Function using variables and OpenQuery

I am attempting to query data from our Oracle server via our SQL server. To perform this in a thin-client manner, I am using OpenQuery. I would like to build a single table-based function that can be used to query various linked tables as needed. I can't quite figure out the syntax to make this work. Here is what I have so far. Any help is greatly appreciated!
CREATE FUNCTION [dbo].[fnTEST](
#Table varchar (100),
#Fields varchar (1000),
#Condition varchar(5000)
)
RETURNS
#TEST TABLE()
AS
BEGIN
DECLARE #OPENQUERY nvarchar(4000);
DECLARE #TSQL nvarchar(4000);
SET #OPENQUERY = 'SELECT * FROM OPENQUERY([TEST-Link],'''
SET #TSQL = 'SELECT ' + #Fields + ' FROM TEST.' + #Table + ' WHERE ' + #Condition + ''')'
EXEC (#OPENQUERY+#TSQL)
END;
The error I am currently getting is:
Msg 102, Level 15, State 1, Procedure fnTEST, Line 12 [Batch Start Line 7]
Incorrect syntax near ')'.
Highlighted at #TEST TABLE()
This is all not recommended for a number of reasons, but here the big one is that, as indicated in the MS doc, you cannot use dynamic SQL from a user-defined function:
Before You Begin
Limitations and restrictions
...
User-defined functions cannot make use of dynamic SQL or temp tables. Table variables are allowed.
...
Here are some of the other problems with this approach:
Your dynamic SQL is injectable. You should never use dynamic SQL unless you understand what SQL Injection is and how to prevent it in your dynamic SQL code.
Using dynamic sql has potential security requirements and restrictions. In this case the dynamic SQL may not have the same rights as your account and may not be able to use an OPENQUERY.
The nature of Database and Server Trustworthy settings may block this anyway.
IMHO, OPENQUERY is not recommended (some disagree), and remote queries are better handled with Linked Servers and the Remote EXEC command.
You are trying to write a "Universal Query" here. Universal Queries are generally not a good idea and have security problems, even after you fix the SQL Inject issues. It's better to define the specific queries needed by your app and code them as stored procedures and/or fixed queries using parameters only for WHERE conditions.
A SQL Function is not the right place for all of this anyway. You should regard a SQL table function as akin to a View, but with parameters for your WHERE clause. You should not treat it as a way to magically do anything.
The way that I would do something link this is as follows:
Define the explicit queries/datasets that your app needs from the Oracle Database.
Write those queries as stored procedures, on the Oracle database.
Setup a Linked Server definition in your SQL Server database to the Oracle database. Configure the security for each side appropriately.
Write specific stored procedures on your SQL Server to call the corresponding procedures in the Oracle database. Use remote EXEC's to do this through the Linked Server definition.
(NOTE: Remote EXEC execution is done with the AT <linkedServer> clause).
Enable the linked server for rpc out and simplify this to
EXEC (#sql) at [TEST-Link]

Getting the sql server default backup folder on Linux with T-SQL

For SQL Server on Windows for getting default backup folder we can use master.dbo.xp_instance_regread:
DECLARE #HkeyLocal nvarchar(18) = N'HKEY_LOCAL_MACHINE';
DECLARE #MSSqlServerRegPath nvarchar(31) = N'SOFTWARE\Microsoft\MSSQLServer';
DECLARE #InstanceRegPath sysname = #MSSqlServerRegPath + N'\MSSQLServer';
DECLARE #BackupDirectory nvarchar(512)
if 1=isnull(cast(SERVERPROPERTY('IsLocalDB') as bit), 0)
SET #BackupDirectory=cast(SERVERPROPERTY('instancedefaultdatapath') as nvarchar(512))
else
EXEC master.dbo.xp_instance_regread #HkeyLocal, #InstanceRegPath, N'BackupDirectory', #BackupDirectory OUTPUT;
SELECT #BackupDirectory AS SQLServerBackupDirectory;
But for Linux it does not work. Can anyone help with T-SQL approach (I only find SSMS solution here)?
I've never found a non-registry way to get the backup directory reliably in t-sql. I usually just do a replace of /data/ to /backup/ since at default install it's relation to the data directory is clear. This does not directly answer your question but it's a working way provided you have not done individual directory customization during install.
-- for one of my databases, [dharma] in my linux test-bed.
declare #bkup nvarchar(1024)
set #bkup = replace(cast(SERVERPROPERTY('instancedefaultdatapath') as nvarchar(512)),'/data/','/backup/') + 'dharma.bak'
backup database dharma to disk = #bkup
In SQL Server v2019, Microsoft enhanced ServerProperty to expose InstanceDefaultBackupPath. Spoke of same here ( https://learningintheopen.org/2020/06/08/sql-server-configuration-default-directories/ )

Query multiple SQL Servers in One query

I am trying to set up a query that will grab the Windows version of each SQL Server I have and throw it into a table. I have the query that grabs the version but I think there is a better way to get the information needed than connecting to each indiviual server one by one to run the query. I am not opposed to using XP_cmdshell I am just wondering if there is a way to run one query that will grab the version of each Windows OS I have on the sql servers. Also I do have a list of servers to use.
EDIT: I know I wil have to in some way touch each server. I would just like a way to get around having the RDP to each server and open SQL server and query it or haveing to connect to each server within sql server and running the query one by one.
All I have right now code wise is a simple INSERT STATEMENT I get here and I draw a blank on where to go next of even hoe to tackle the problem. The table below has two columns ServerName and Win_Ver ServerName is already populated with all the servers I have.
INSERT INTO mtTable
(Win_Ver)
SELECT ##Version
Given that:
there are "roughly 112 servers"
the servers being a "mixture between 2008 - 2012"
"There is table we are keeping with all of our DB server Statistics."
and "We periodically get asked to produce these statistics"
one option is to cycle through that table of servers using a cursor, and for each one, execute xp_cmdshell to call SQLCMD to run the query. You would use a table variable to capture the result set from SQLCMD as returned by xp_cmdshell. Something like:
DECLARE #ServerName sysname,
#Command NVARCHAR(4000),
#CommandTemplate NVARCHAR(4000);
DECLARE #Results TABLE ([ResultID] INT IDENTITY(1, 1) NOT NULL, [Result] NVARCHAR(4000));
SET #CommandTemplate = N'SQLCMD -S {{SERVER_NAME}} -E -h-1 -Q "PRINT ##VERSION;"';
DECLARE srvrs CURSOR LOCAL READ_ONLY FAST_FORWARD
FOR SELECT [ServerName]
FROM ServerStats;
OPEN srvrs;
FETCH NEXT
FROM srvrs
INTO #ServerName;
WHILE (##FETCH_STATUS = 0)
BEGIN
SET #Command = REPLACE(#CommandTemplate, N'{{SERVER_NAME}}', #ServerName);
INSERT INTO #Results ([Result])
EXEC xp_cmdshell #Command;
-- Get results via SELECT [Result] FROM #Results ORDER BY [ResultID];
-- Do something with the data in #Results
DELETE FROM #Results;
FETCH NEXT
FROM srvrs
INTO #ServerName;
END;
CLOSE srvrs;
DEALLOCATE srvrs;
And it wouldn't hurt to throw in a TRY / CATCH in there :-).
Even if not the most ideal of solutions, it is at least doesn't require adding 112 Linked Servers, and is dynamic and will adjust to servers being added and removed.
In SQL Server you are able to create a Linked Server that you can query from another server.
On the server you wish to write the query in:
Open the Object Explorer
Go to Server Objects
Right Click Linked Servers and add a New Linked Server
Add the Name of your networked server, select SQL server and make sure to define security roles.

Static Variable in TSQL?

I have bunch of servers that I need their information such as servername and instance name and add all these information into a table in Server X. I am using sqlcmd to connect to server X, here is a simple code:
declare #servername varchar(30)
set #servername=##servername
:connect to X
insert into X.table values(#servername)
so i thought #servername gets the current servername and then it goes to line 3 and add it to new server(server X), but I was wrong and once it connects to server X it retrieves the data (#servername) from this server. meaning that it can't hold the data. So my question is how to hold the data from old server and not overwrite it, something like Static variable in java.
SQLCMD happens before processing the script, so you can't use it for your intended purpose.
Have you tried using a linked server and the EXEC AT command?
EXEC ('INSERT INTO MyTable VALUES (?)', #servername) AT [LinkedServer]
http://www.mssqltips.com/sqlservertip/1757/dynamic-sql-execution-on-remote-sql-server-using-exec-at/
https://msdn.microsoft.com/en-us/library/ms188332.aspx
If you have the server names before hand, you can use :setvar myvariable variablevalue to set a variable that you can access in your sqlcmd script via $(myvariable).
In this situation I would probably do a delimited string of server names to parse.
Take a look at the MSDN https://msdn.microsoft.com/en-us/library/ms188714.aspx , for more examples.

In SQL server is there any way to get the 'use database' command to accept a variable

I would like to write a 'generic' script to operate on a number of databases. The 'use databasename' commands will be distributed in the body of the script. I would rather show the database name information at the top of the script. So ideally:
declare #db varchar(100)
set #db = 'data_in'
use #db
This fails. So perhaps
declare #db varchar(100)
set #db = 'data_in'
exec ('use '+#db)
This executes, but presumably changes the database only in the context of the query in the string.
So is there any other way without placing the whole script into a string and executing it that way ie. a cure worse than the disease?
Check out Scripting Variables in SQLCMD.
This enables you to put variables into your scripts in the form:
USE $(db1_name)
...some code...
USE $(db2_name)
...some code...
And have the variable values interpolated from environment variables, parameters provided at runtime, or hard coded value assignments.
Ed already mentioned SQLCMD, a very good choice for scripting.
If that doesn't do it for you, and you have sa rights on the server, and you don't mind the risk of using undocumented features and modifying the master database, you might look into user-defined system stored procedures.
A user-defined system stored procedure (UDSSP) is created in the master database, prefixed with "sp_", and marked as a system object with the undocumented system proc sp_MS_marksystemobject (SQL2005).
It takes its database context from the current connection, or a three-part name if so called.
Sample invocation:
declare #db sysname
declare #sql nvarchar(max)
set #db = 'yourdatabase'
set #sql = 'exec '+quotename(#db)+'..sp_#yourproc'
exec (#sql)
Notes:
If you go this route, I strongly suggest using a unique prefix that sorts toward the top, like sp_#yourproc, rather than sp_yourproc, so you can find them again later, and other people know that they are something special.
Once the procedure is marked as system, it can't be updated. To make changes, you must drop, recreate and remark as system.
Don't do this unless you know what you are doing and have done a little more research. Don't do this if you are risk-averse. Don't do this if you don't have a development instance to test on first.
Backup the UDSSPs to file or CSV. A server upgrade can wipe them out.
You could NOT put a use statement in your script, and supply the database with the osql command (or whatever you're using) like this:
osql -S servername -d databasename -U username -P password -i script.sql
I think your presumption’s right; I had the same problem yesterday. I solved it by putting the commands into a single string. Why don’t you like that solution?

Resources