SQL Server : drop all databases except the system ones - sql-server

In PowerShell I am using the following code to delete all non system SQL Server databases:
invoke-sqlcmd -ServerInstance $sqlInstanceName -U $sqlUser -P $sqlPass -Query "
EXEC sp_MSforeachdb
'IF DB_ID(''?'') > 4
BEGIN
ALTER DATABASE [?] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
DROP DATABASE [?]
END'
"
And it seems to do the job. But when I re-run it I get:
invoke-sqlcmd : Option 'SINGLE_USER' cannot be set in database 'master'.
Option 'SINGLE_USER' cannot be set in database 'tempdb'.
At C:\tmp\drop.ps1:19 char:5
+ invoke-sqlcmd -ServerInstance $sqlInstanceName -U $sqlUser -P $sq ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Invoke-Sqlcmd], SqlPowerShellSqlExecutionException
+ FullyQualifiedErrorId : SqlError,Microsoft.SqlServer.Management.PowerShell.GetScriptCommand
I thought IF DB_ID(''?'') > 4 would skip any system dbs:
How can I omit system databases and allow SQL Server 2008 agent job to move past ERROR_NUMBER 208?
How do I make it terminate gracefully if only system dbs (master, model, msdb, tempdb) are found?

I suspect what is happening here is syntax checking before the actual evaluation of IF. You need to introduce another level of "dynamism" to your query.
EXEC sp_MSforeachdb
'IF DB_ID(''?'') > 4
BEGIN
EXEC (''ALTER DATABASE [?] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
DROP DATABASE [?]'' )
END'

Related

How to use Invoke-Sqlcmd from inside SSMS?

I run the query below (from inside powershell), from different servers and all is fine:
Invoke-Sqlcmd -Query "
SELECT [ServerName]=##servername,m.Match_id, m.HostfamilyId, hf.NIEFlag, ra.DS2019Sent
FROM APIA_Repl_pub.dbo.repl_HostFamily hf
INNER JOIN APIA_Repl_pub.dbo.repl_Match m ON m.HostfamilyId = hf.HostFamilyID
INNER JOIN APIA_Repl_Pub.dbo.repl_Aupair ra on ra.AuPairID = m.AupairId
WHERE ra.JunoCore_applicationID = 459630
" -ServerInstance "CTSTGDB"
I even sometimes run the same query into several servers to compare them, see the result below as an example:
I just want to run the same query from inside SSMS, but see what I do and what I get:
(I have even simplified the query but still I get the error below)
GO
declare #sql varchar(8000)
SET #SQL=N'powershell.exe -command Invoke-Sqlcmd -Query "SELECT [ServerName]=##servername" -ServerInstance "CTSTGDB"'
IF OBJECT_ID('tempdb..#Radhe','U') IS NOT NULL
DROP TABLE #RADHE
CREATE TABLE #RADHE(I INT NOT NULL IDENTITY(1,1) PRIMARY KEY CLUSTERED, OUTPUT NVARCHAR(4000))
INSERT INTO #RADHE
EXEC xp_cmdshell #sql
SELECT * FROM #RADHE
GO
OUTPUT
Invoke-Sqlcmd : A positional parameter cannot be found that accepts argument
'[ServerName]=##servername'.
At line:1 char:1
+ Invoke-Sqlcmd -Query SELECT [ServerName]=##servername -ServerInstance ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Invoke-Sqlcmd], ParameterB
indingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.SqlServer.
Management.PowerShell.GetScriptCommand
NULL
I found out an interesting article:
6 methods to write PowerShell output to a SQL Server table
It is related or could be an alternative to the way I am working.
what exactly is the problem of Invoke-Sqlcmd from inside SSMS?
As I understand, you struggle with executing a query against a different server than the one the query is connected to. Well, I can think of at least 2 ways of doing this in SSMS:
If your current server has a linked server for the one you are after, you can use the AT clause of the EXEC statement, like this:
declare #Sql nvarchar(max) = N'SELECT [ServerName]=##servername,m.Match_id,
m.HostfamilyId, hf.NIEFlag, ra.DS2019Sent
FROM APIA_Repl_pub.dbo.repl_HostFamily hf
INNER JOIN APIA_Repl_pub.dbo.repl_Match m ON m.HostfamilyId = hf.HostFamilyID
INNER JOIN APIA_Repl_Pub.dbo.repl_Aupair ra on ra.AuPairID = m.AupairId
WHERE ra.JunoCore_applicationID = 459630';
-- This example assumes that the linked server name is the same as the remote server itself
exec (#Sql) at [CTSTGDB];
If linked server is not available, you may utilise the SQLCMD mode for the query:
:connect CTSTGDB
go
SELECT [ServerName]=##servername,m.Match_id, m.HostfamilyId, hf.NIEFlag, ra.DS2019Sent
FROM APIA_Repl_pub.dbo.repl_HostFamily hf
INNER JOIN APIA_Repl_pub.dbo.repl_Match m ON m.HostfamilyId = hf.HostFamilyID
INNER JOIN APIA_Repl_Pub.dbo.repl_Aupair ra on ra.AuPairID = m.AupairId
WHERE ra.JunoCore_applicationID = 459630;
go
:exit
go
This mode can be toggled by the Query -> SQLCMD Mode menu option.

How to delete SQL Server databases trough vNEXT build task properly

We have a PowerShell cleanup script for our test machines:
$sqlConnection = new-object system.data.SqlClient.SqlConnection("Data Source=.\SQLExpress;Integrated Security=SSPI;Initial Catalog=master")
try {
$sqlConnection.Open()
$commandText = #"
exec sp_msforeachdb 'IF ''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'')
BEGIN
drop database [?]
END'
"#
$sqlCommand = New-Object System.Data.SqlClient.SqlCommand
$sqlCommand.CommandText = $commandText
$sqlCommand.Connection = $sqlConnection
$SQLCommand.CommandTimeout = 0
$sqlCommand.ExecuteNonQuery()
}
finally{
$sqlConnection.Close()
}
Normally it works, but sometimes it cannot delete databases, since there seem to be some open connections and the build task fails to delete the databases as they are in use.
This also seems to occur at "some point" or "random".
Any advice to enhance the script?
(using lates TFS 2017 on prem and SQL Server 2014)
If you need to cut off all users with no warning, set the database offline before dropping it.
$commandText = #"
exec sp_msforeachdb 'IF ''?'' NOT IN (''master'', ''model'', ''msdb'', ''tempdb'')
BEGIN
alter database [?] set offline with rollback immediate;drop database [?];
END';
"#
if found a script here:
Drop all databases from server
-- drops all user databases
DECLARE #command nvarchar(max)
SET #command = ''
SELECT #command = #command
+ 'ALTER DATABASE [' + [name] + '] SET single_user with rollback immediate;'+CHAR(13)+CHAR(10)
+ 'DROP DATABASE [' + [name] +'];'+CHAR(13)+CHAR(10)
FROM [master].[sys].[databases]
where [name] not in ( 'master', 'model', 'msdb', 'tempdb');
SELECT #command
EXECUTE sp_executesql #command
it works as intended, still thanks for your help
Might I suggest using SMO?
push-location;
import-module sqlps -disablenamechecking;
pop-location
$serverName = '.';
$server = new-object microsoft.sqlserver.management.smo.server $servername;
foreach ($db in $server.Databases | where {$_.IsSystemObject -eq $false}) {
$server.killDatabase($db.Name);
}

xp_cmdshell Proxy not Executing

I created a Proxy account with Sysadmin access.
Users use this to execute a bcp command of their choosing.
However when the users want's to execute the proc they get: EXECUTE permission was denied on the object 'xp_cmdshell', database 'mssqlsystemresource', schema 'sys'
Here is my Proc:
CREATE PROCEDURE spCMDProxy
(
#SQLSTATEMENT VARCHAR(1000),
#FILENAME VARCHAR(1000)
)
WITH ENCRYPTION
AS
BEGIN
DECLARE #PrepStatement VARCHAR(1000)
SET #PrepStatement = 'bcp ' + '"'+ #SQLSTATEMENT + '" queryout C:\SamsungEDI\' + #FILENAME + ' -U************ -P************ -c -t"||" -S UECZAERP01'
EXEC xp_cmdshell #PrepStatement
END
My Proxy account is a local Sysadmin account.
I found the problem, Could not execute due to the fact that the user cannot execute on xp_cmdshell:
USE master
GRANT EXECUTE on xp_cmdshell to [mydomain\myAccount]
Also found this Article

How to rename the Physical Database Files

I have used tsql to detach a database like this:
EXEC sp_detach_db #dbname = 'my_db'
I then made use of PHP to rename the physical files. I was able to rename the mdf file but not the ldf file! I even tried a dos command REN but that didn't work for the ldf file either!
I wanted to ask, is there something special about the physical log files that allow it not to be renamed?
Is there a better way of doing this?
Thanks all
Detach the Database, Rename the files, Attach it again.
Backup the original database
Drop the original database
Restore the original database from the backup, but with different name; the files of the restored database will be also automatically named taking into account new database name.
The "ALTER DATABASE (your database) MODIFY FILE" command will only rename the logical names. This post shows how to use xp_cmdshell to also rename the physical files: http://www.mssqltips.com/sqlservertip/1891/best-practice-for-renaming-a-sql-server-database/
Please note the following:
1. xp_cmdshell will be executed under the user which the SQL Server process runs as, and might not have the file system permissions required to rename the database files
2. For security reasons, remember to disable xp_xmdshell
The following is an example of how the renaming can be done based on the mentioned blog post. It will replace the database MyDB with the database NewMyDB. The original MyDB (renamed to MyDB_OLD) will be left detached.
-- Enable xp_cmdshell:
sp_configure 'show advanced options', 1
RECONFIGURE WITH OVERRIDE
GO
sp_configure 'xp_cmdshell', 1
RECONFIGURE WITH OVERRIDE
GO
-- Get physical file names:
declare #MyDBOriginalFileName nvarchar(300) = (select physical_name FROM sys.master_files where name = 'MyDB')
declare #MyDBLogOriginalFileName nvarchar(300) = (select physical_name FROM sys.master_files where name = 'MyDB_log')
declare #NewMyDBOriginalFileName nvarchar(300) = (select physical_name FROM sys.master_files where name = 'NewMyDB')
declare #NewMyDBLogOriginalFileName nvarchar(300) = (select physical_name FROM sys.master_files where name = 'NewMyDB_log')
declare #Command nvarchar(500)
declare #Sql nvarchar(2000)
IF (EXISTS (select * from sys.databases where name = 'NewMyDB')
AND EXISTS (select * from sys.databases where name = 'MyDB'))
BEGIN
USE master
ALTER DATABASE MyDB SET SINGLE_USER WITH ROLLBACK IMMEDIATE
ALTER DATABASE NewMyDB SET SINGLE_USER WITH ROLLBACK IMMEDIATE
-- Set new database name
ALTER DATABASE MyDB MODIFY NAME = MyDB_OLD
ALTER DATABASE NewMyDB MODIFY NAME = MyDB
-- Update logical names
ALTER DATABASE MyDB_OLD MODIFY FILE (NAME=N'MyDB', NEWNAME=N'MyDB_OLD')
ALTER DATABASE [MyDB] MODIFY FILE (NAME=N'NewMyDB', NEWNAME=N'MyDB')
EXEC master.dbo.sp_detach_db #dbname = N'MyDB_Old'
EXEC master.dbo.sp_detach_db #dbname = N'MyDB'
-- Rename physical files
SET #Command = 'RENAME "' + #MyDBOriginalFileName + '" "MyDB_OLD.mdf"'; PRINT #Command
EXEC xp_cmdshell #Command
SET #Command = 'RENAME "' + #MyDBLogOriginalFileName + '" "MyDB_OLD_log.mdf"'; PRINT #Command
EXEC xp_cmdshell #Command
SET #Command = 'RENAME "' + #NewMyDBOriginalFileName + '" "MyDB.mdf"'; PRINT #Command
EXEC xp_cmdshell #Command
SET #Command = 'RENAME "' + #NewMyDBLogOriginalFileName + '" "MyDB_log.mdf"'; PRINT #Command
EXEC xp_cmdshell #Command
-- Attach with new file names
declare #NewMyDBFileNameAfterRename nvarchar(300) = replace(#NewMyDBOriginalFileName, 'NewMyDB', 'MyDB')
declare #NewMyDBLogFileNameAfterRename nvarchar(300) = replace(#NewMyDBOriginalFileName, 'NewMyDB_log', 'MyDB_log')
SET #Sql = 'CREATE DATABASE MyDB ON ( FILENAME = ''' + #NewMyDBFileNameAfterRename + '''), ( FILENAME = ''' + #NewMyDBLogFileNameAfterRename + ''') FOR ATTACH'
PRINT #Sql
EXEC (#Sql)
ALTER DATABASE MyDB SET MULTI_USER
END
-- Disable xp_cmdshell for security reasons:
GO
sp_configure 'show advanced options', 1
RECONFIGURE WITH OVERRIDE
GO
sp_configure 'xp_cmdshell', 0
RECONFIGURE WITH OVERRIDE
GO
You can do it using an ALTER DATABASE statement - like this:
ALTER DATABASE database_name
MODIFY FILE ( NAME = logical_file_name,
FILENAME = ' new_path/os_file_name_with_extension ' )
You need to modify each file separately, e.g. if you have multiple data files, you need to modify each of those.
For details, see the Technet documentation on this topic.
The simplest way to rename SQL server physical database files is:
Open and connect to the SQL server where the database you wanted to rename is located.
Execute the following script in the query window in order to change the physical and logical names. Remember to replace all the "OldDatabaseName" with the new name of the database ("NewDatabaseName") you want to change its name to. Replace all NewDatabaseName with the new name you want to set for your database
use OldDatabaseName
ALTER DATABASE OldDabaseName MODIFY FILE (NAME='OldDatabaseName', FILENAME='C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\NewDatabaseName.mdf');
ALTER DATABASE OldDatabaseName MODIFY FILE (NAME='OldDatabaseName_log', FILENAME='C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\NewDatabaseName_log.ldf');
ALTER DATABASE OldDatabaseName MODIFY FILE (NAME = OldDatabaseName, NEWNAME = NewDatabaseName);
ALTER DATABASE OldDatabaseName MODIFY FILE (NAME = OldDatabaseName_log, NEWNAME = NewDatabaseName_log);
And then Right click on the OldDatabaseName, select Tasks and then choose Take Offline
Go to the location (C:\Program Files\Microsoft SQL Server\MSSQL10_50.MSSQLSERVER\MSSQL\DATA\...) where the physical files are located and rename them to the NewDatabaseName you specified in number 2. Remember to check the absolute path of these files to be used on your computer.
Go back to Microsoft SQL Server Management Studio. Right click on the OldDatabaseName, select Tasks and then choose Bring Online.
Finally, go ahead and rename your OldDatabaseName to the NewDatabaseName. You are done :-)
Detach (right click on database)
Rename both files (ldf and mdf) :
C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\DATA
Attach (right click on "Databases" top folder)

Shrinklog all user databases in SQL2008

Hi I am using this script in the process of weekly maintenance, suggest best approach/scripts to do shrinklog. Currently am getting an error with the below script
declare #s nvarchar(4000)
set #s= '
if ''?'' not in (''tempdb'',''master'',''model'',''msdb'')
begin
use [?]
Alter database [?] SET Recovery simple
end '
exec sp_msforeachdb #s
set #s= '
if ''?'' not in (''tempdb'',''master'',''model'',''msdb'')
begin
use [?]
Declare #LogFileLogicalName sysname
select #LogFileLogicalName=Name from sys.database_files where Type=1
DBCC Shrinkfile(#LogFileLogicalName,1)
end'
exec sp_msforeachdb #s
Error Description:
ShrinkLog Execute SQL Task Description: Executing the query "declare #s nvarchar(4000) set #s= ' ..." failed with the following error: "Option 'RECOVERY' cannot be set in database 'tempdb'. Cannot shrink log file 2 (DBServices_Log) because total number of logical log files cannot be fewer than 2. DBCC execution completed. If DBCC printed error messages, contact your system administrator.
note: I am avoiding tempdb(all System db) in my script, but error message shows tempdb?
This is probably the worst maintenance script I've seen in the past year. A maintenance script that every week breaks the log chain and makes the database unrecoverable?? OMG. Not to mention that the simple premise of shrinking the log on a maintenance task is wrong. If the database log has grown to a certain size, than that size is needed. Schedule log backups more frequently to prevent this, but don't schedule log shrink operations.
You can avoid the 5058 error by using an exec statement to alter the database recovery model. The following script will set each user database to a simple recovery model. (It uses Jimmy's answer to detect system databases.)
exec sp_msforeachdb 'if exists (select name from sys.databases d where case when d.name in (''master'',''model'',''msdb'',''tempdb'') then 1 else d.is_distributor end = 0
and name = ''?'' and recovery_model_desc != ''SIMPLE'')
begin
declare #previousRecoveryModel varchar(100) = (select recovery_model_desc from sys.databases where name = ''?'');
print ''Changing recovery model for ? from '' + #previousRecoveryModel + '' to SIMPLE.'';
use master;
-- Using exec avoids a compile-time 5058 error about tempdb, which is a branch that will never be executed in this code.
exec (''alter database [?] set recovery simple'');
end';
Then you won't get the compile-time error about tempdb because the exec statement will compile its statement with the database name variable already substituted.

Resources