Create Schema Clones with RedGate sqlcompare & table of server names - sql-server

Ok, so I have taken an old batch file that previous DBA's have used to "clone" the schema from Production databases on multiple servers and drop them in one network directory. This batch file was updated manually so I am trying to automate it. We have a support dbase with a table that has all of the server names in it so I was figuring easiest would be a cursor (I know, but for this not an issue) to while through the list of server names executing the "sqlcompare" command line stuff for each. Apparently though it makes a new command line line for every individual database on each server so my variable is returning more than one result. There HAS to be a better way to do this, I feel like I must be going about this all wrong, any help is appreciated:
DECLARE #RowCount INT = 1
,#index INT = 1
,#outputfolder varchar(100)
,#servername varchar(100)
,#environment varchar(50)
,#OutputPath varchar(100)
,#sqlcmd nvarchar (100)
SET #outputfolder = GETDATE()
SET #OutputPath = '\\<network location to store output files>\'
-- set #outputfolder = '2014.03.10_0900'
SET #servername = '<servername stored in table>'
SET #environment = '<environment variable in table>'
SET #sqlcmd = ':connect '+#servername
DECLARE #redgatecmd varchar(255)
SET #redgatecmd = ''
--SELECT '::'+#environment
SET #redgatecmd = (SELECT DISTINCT 'sqlcompare /s1:'+#servername+' /db1:'+'name'+' /mkscr:"'+#OutputPath+#outputfolder+'\'+#environment+'\'+'name'+'" /options:iw,iu,isn,ie,ic,iup,iweo,infr,idc,idsn,isoa,isb"' from sysdatabases where name not in ( 'tempdb'))
PRINT #redgatecmd
--EXEC master..xp_cmdshell #redgatecmd

I've done something very similar using PowerShell. And connecting from there to the DB to pull the details. Tweaked it for your example (though the output directory isn't quite the same and it doesn't have the compare options set)
It was based on this great article from DataOrge
#Create your SQL connection string, and then a connection
$ServerAConnectionString = "Data Source=hostname\instance;Initial Catalog=databasename;Integrated Security=SSPI" # Or instead of integrated auth ;User Id=XXX;PWD=XXX"
$ServerAConnection = new-object system.data.SqlClient.SqlConnection($ServerAConnectionString);
#Create a Dataset to hold the DataTable
$dataSet = new-object "System.Data.DataSet" "ServerList"
$query = "SET NOCOUNT ON;"
$query = $query + "SELECT name, environment, db "
$query = $query + "FROM dbo.Servers; "
#Create a DataAdapter which you'll use to populate the DataSet with the results
$dataAdapter = new-object "System.Data.SqlClient.SqlDataAdapter" ($query, $ServerAConnection)
$dataAdapter.Fill($dataSet) | Out-Null
#Close the connection as soon as you are done with it
$ServerAConnection.Close()
$dataTable = new-object "System.Data.DataTable" "Servers"
$dataTable = $dataSet.Tables[0]
#For every object
$dataTable | FOREACH-OBJECT {
"Name: $($_.name)"
"Database: $($_.db)"
"Environment: $($_.environment)"
$cmd = "sqlcompare.exe /s1:$($_.name) /db1:$($_.db) /mkscr:$($_.environment+"_"+$_.db)"
write-host $cmd
Invoke-Expression $cmd
}

Related

How to delete record from database if no longer existed

If I have a new data/record in my CSV file then I'm importing it to my SQL Server database using PowerShell like this.
$CSVImport = Import-CSV $Global:ErrorReport
ForEach ($CSVLine1 in $CSVImport) {
$CSVHold1 = $CSVLine1.Hold
$CSVGSID1 = $CSVLine1.GSID
$CSVSource1 = $CSVLine1.Source
$CSVTYPE1 = $CSVLine1.TYPE
$CSVMessage1 = $CSVLine1.Message
$CSVCreatedDate1 = $CSVLine1.Time
$query = "USE $Global:Database
IF NOT EXISTS (SELECT * FROM Lit_Hold_Err
WHERE GSID = '$CSVGSID1'
AND Source = '$($CSVSource1 -replace "'","''")')
BEGIN
INSERT INTO Lit_Hold_Err (Hold, GSID, Source, Error, Type,Date)
VALUES('$CSVHold1', '$CSVGSID1', '$($CSVSource1 -replace "'","''")','$CSVMessage1','$CSVTYPE1', '$CSVCreatedDate1')
END"
Invoke-Sqlcmd -Query $query -ServerInstance $Global:Server -Database $Global:Database
I'm just wondering how should I write another query that compare my CSV file with my database and delete a particular/s record from my database if it's no longer existed in my CSV file?.
I know I'll need to use DELETE Statement but I'm kinda stuck on WHERE Clause so Any help or suggestion would be really appreciated.
A suggestion would be using DBATool's Import-DbaCsv to read the CSV file and bulk load it to a table on tempdb, then use a merge instruction to make the inserts/updates/deletes.
The import command would be something like:
$Credential = Get-Credential
Import-DbaCsv -Path .\test.csv -AutoCreateTable -SqlInstance my-instance -Database tempdb -SqlCredential $Credential -SupportsMultiline

Prevent single quotes from breaking sql command

I have a powershell module that builds a string out of several variables in order to insert into sql server.
$QueryInsert =
"
insert into dbo.Table
(
data
)
values
(
'$data'
)
"
Invoke-Sqlcmd -ServerInstance 'server_name' `
-Database db_name `
-Query $QueryInsert `
-QueryTimeout 20 `
-OutputSqlErrors $True `
-ConnectionTimeout 5 `
-ErrorAction Continue
The problem with this methodology is that it's not particularly safe, and will fail if the variable $data contains any single quotes. Is there a way to prevent this? Is there a way to paramaterize this to keep it safe?
If you have access to the database, you can write the query as a stored procedure and then call the stored procedure with PowerShell. You can pass parameters to a stored procedure in a safe manner that would not allow injecting code through a string like you can with the code above.
You might want to have a look at this question to see how to write a stored procedure and also this one.
In order to call a SPROC from PowerShell, you can use code similar to this.
$sql='NameOfSprocBeingCalled'
$sqlConnection = new-object System.Data.SqlClient.SqlConnection
$sqlConnection.ConnectionString = $SqlConnectionString
$sqlConnection.Open()
$sqlCommand = new-object System.Data.SqlClient.SqlCommand
$sqlCommand.Connection = $sqlConnection
$sqlCommand.CommandText= $sql
$sqlCommand.CommandType = [System.Data.CommandType]::StoredProcedure
$sqlCommand.Parameters.Add("#Param1",[system.data.SqlDbType]::VarChar).value =$Param1
$sqlCommand.Parameters.Add("#Param2",[system.data.SqlDbType]::VarChar).value = $EventType
$Datatable = New-Object System.Data.DataTable
$DataReader = $sqlCommand.ExecuteReader()
$Datatable.Load($DataReader)
$sqlConnection.Close()
You just need to make sure you pass in the right type for parameters, using [System.Data.SqlDbType]::
This is an enum with the following types available:
# [enum]::GetValues([System.Data.SqlDbType])
BigInt
Binary
Bit
Char
DateTime
Decimal
Float
Image
Int
Money
NChar
NText
NVarChar
Real
UniqueIdentifier
SmallDateTime
SmallInt
SmallMoney
Text
Timestamp
TinyInt
VarBinary
VarChar
Variant
Xml
Udt
Structured
Date
Time
DateTime2

Stored procedure called from Powershell does not execute via SMO object

I'm trying to execute a stored procedure from a Powershell terminal by using the following code, but the procedure does not seem to execute and there is no error thrown in the terminal.
add-type -AssemblyName "Microsoft.SqlServer.Smo, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91"
$so = New-Object -TypeName Microsoft.SqlServer.Management.Smo.Server -argumentList 'PC-1001'
$db = New-Object Microsoft.SqlServer.Management.Smo.Database($so, "TestDB")
$sproc = $db.StoredProcedures.Item("dproc")
$sproc.Execute
The supporting SQL code is:
create table dummytable (id int identity(1,1), ranwhen datetime default getdate(), dummyval varchar(10));
create procedure dproc as
begin
set nocount on
insert into dummytable (dummyval)
select char(datepart(hour, getdate()) * datepart(minute, getdate()) * datepart(second, getdate()) % 256)
end
If I execute the procedure in SSMS (exec dproc) it works (data is inserted into the table).
Any ideas why it's not working from Powershell? (no data is being inserted into the dummy table)
Update:
I've changed the declaration of the $db and $sproc variable to:
$db = $so.Databases.Item("TestDB")
$sproc = $db.StoredProcedures.Item("dproc")
And when checking the contents of the $sproc object, I can see that every property is correct (T-SQL code is there, URN value is correct and references the correct DB and schema).
The StoredProcedure class does not provide a means of executing the stored procedure it represents.
You might try, and I have taken no steps to validate this is possible, using:
$so.ConnectionContext.ExecuteNonQuery("dproc")
Failing that, you might simply fall back to using System.Data.SqlClient.
I suggest you to use System.Data.SqlClient as well. I used to run stored procedures like this:
$SQL = New-Object System.Data.SqlClient.SqlConnection
$ConnectionStrig = "Server=localhost;Database=testtaskdb;Integrated Security=True;"
$SQL.ConnectionString = $ConnectionStrig
$SQL.Open()
$CMD = $SQL.CreateCommand()
$CMD.CommandText = "exec myproc"
#if you need to just run the stored procedure
$null=$CMD.ExecuteReader()
#if you need to get the output
$Table = New-Object System.Data.DataTable
$Table.Load($CMD.ExecuteReader())
Write-Output $Table

How do I execute a SELECT query against a SQLServer database and iterate results using PowerShell

Say I have a table with 3 columns - "Column1", "Column2", and "Column3" - datatype is varchar(100) for all 3.
Using PowerShell, how do I connect to SQL Server and use SqlDataReader and ForEach operator to view the contents of "Column2"?
Here's roughly how I'm doing it:
$SqlServer = 'sql.example.com';
$SqlDatabase = 'MyDB';
$SqlConnectionString = 'Data Source={0};Initial Catalog={1};Integrated Security=SSPI' -f $SqlServer, $SqlDatabase;
$SqlQuery = "SELECT Name FROM dbo.Person ORDER BY Name;";
$SqlConnection = New-Object -TypeName System.Data.SqlClient.SqlConnection -ArgumentList $SqlConnectionString;
$SqlCommand = $SqlConnection.CreateCommand();
$SqlCommand.CommandText = $SqlQuery;
$SqlConnection.Open();
$SqlDataReader = $SqlCommand.ExecuteReader();
#Fetch data and write out to files
while ($SqlDataReader.Read()) {
Write-Output $SqlDataReader['Name'];
}
$SqlConnection.Close();
$SqlConnection.Dispose();
If I remember right, I basically refactored the code from the MSDN example.
For those wondering why I'm using SqlDataReader: Most of my scripts use SqlDataAdapter, but this one retrieves about 8,000 PDFs from a database so I wasn't really interested in calling SqlDataAdapter.Fill(). In exchange for holding shared locks on the table much longer than SqlDataAdapter.Fill() would, SqlDataReader.Read() keeps memory usage down to a manageable level for the client by fetching one record at a time.

Remove column header from SQL Server query result

I want to remove column header from SQL Server query output. I did the search but not found any solution. I have a query eg.
select cc.DepartmentID , cc.Name from HumanResources.Department cc
When I run this query I am getting output like this.
ID Name
12 Document Control
1 Engineering
16 Executive
14 Facilities and Maintenance
10 Finance
9 Human Resources
I want to remove ID and Name (Column Header) from the output in SQL Server.
I will run this query by script to generate csv file.
Edit:
When i run the query by script i got the csv file as output and it look like this.
#TYPE System.Data.DataRow
ID Name
Update:
I am putting powershell script.
$Database = "temp"
$Server = "localhost"
$AttachmentPath = "output.csv"
# Connect to SQL and query data, extract data to SQL Adapter
$SqlQuery = "select cc.DepartmentID , cc.Name from HumanResources.Department cc"
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Data Source=$Server;Initial Catalog=$Database;Integrated Security = True"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $SqlQuery
$SqlCmd.Connection = $SqlConnection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object System.Data.DataSet
$nRecs = $SqlAdapter.Fill($DataSet)
$nRecs | Out-Null
#Populate Hash Table
$objTable = $DataSet.Tables[0]
#Export Hash Table to CSV File
$objTable | Export-CSV $AttachmentPath
I want to remove column header from output.
In SSMS Under Tools/Options/Query Results/SQL Server/Results to Text there is a checkbox to 'Include column headers in the result set'. Similar for results to grid.
If you are using sqlcmd via powershell you can use /h-1 to disable the headers.
This setting corresponds to the environment variable SQLCMDHEADERS.
Tips and Tricks
Use a value of -1 to specify that no headers should be
printed. If -1 is supplied, there must be no space between the
parameter and the setting, that is, -h-1. Otherwise, SQLCMD interprets
it as a separate option and fails.
Example (modified from [TechNet])1:
sqlcmd -q /h-1 "SELECT * FROM AdventureWorks2012.Person.Person"
will also work with -h-1
In management studio at query window right click and select Query options. Look for Result>Text at a tree in the left and check out Include column headers in result set option. I think Hamlet Hakobyan is right, it is client that add column headers.
Replace your last line $objTable | Export-CSV $AttachmentPath with
$objTable | ConvertTo-Csv -NoTypeInformation | select -Skip 1 | out-file $AttachmentPath
Using the Save As option would not include the attribute (column) names.
This work correctly and column header not exists in out-file:
$workpath = "C:\myworkdir"
$InvSQLParams = #{
ServerInstance = "SQL2016"
Database = "testdb"
InputFile = "$($workpath)\selectclause.sql"
}
Invoke-Sqlcmd #InvSQLParams | ConvertTo-Csv -NoTypeInformation | select -Skip 1 | out-file "$($workpath)\result.csv"
in your script, pipe (|) the output to the "tail +3" command.
this will skip the first 2 lines of output from the SQL.
set this after connecting to database
SET HEADING OFF

Resources