Prevent single quotes from breaking sql command - sql-server

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

Related

How to store a SQL Server query result to Powershell Array?

I am new to PowerShell scripting and currently working on a script to load the result of SQL Server query to store as a PowerShell array. Below is my code for reference. :
$SQLServer = 'MyServer';
$Database = 'Test';
## - Connect to SQL Server using non-SMO class 'System.Data':
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection;
$SqlConnection.ConnectionString = `
"Server = $SQLServer; Database = $Database; Integrated Security = True";
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand;
$SqlCmd.CommandText = $("select distinct Servername from dbo.tableA
where Servername like '%hw%'");
$SqlCmd.Connection = $SqlConnection;
$SqlCmd.CommandTimeout = 0;
## - Extract and build the SQL data object '$DataSetTable':
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter;
$SqlAdapter.SelectCommand = $SqlCmd;
$DataSet = New-Object System.Data.DataSet;
$SqlAdapter.Fill($DataSet);
$SqlConnection.Close()
$Servername = #[SqlAdapter]
I expect $Servername to be an array having data elements store as "Server1', 'Server2', 'Server3', etc. based on the sqlquery result. I am planning to utilize $Servername array to loop through each server in future. For now, I am able to successfully connect to database, but I am still not able to get the query result to store in a PowerShell array. Can someone please guide on where I am making mistake?
$SqlAdapter.Fill($DataSet)
This fills the [System.Data.DataSet] instance stored in $DataSet with the query results, which is why you must use $DataSet to get the data you need (untested):
$serverNames = $DataSet.Tables[0].Server
.Tables[0] accesses the first and only [System.Data.DataTable] instance in the dataset containing the query results.
.Server retrieves the the values of the query result's Server column, courtesy of PowerShell's member-access enumeration.
Note that this means that if there's only one result row, $serverNames will contain a single string rather than a single-element array containing that string.
To ensure that an array is always returned, use $serverNames = #($DataSet.Tables[0].Server), or, with a (strong) type constraint, [string[]] $serverNames = $DataSet.Tables[0].Server
As for what you tried:
PowerShell statements only ever need to be separated with ; if they're placed on the same line, which means that all the ; instances in your code are unnecessary.
While $("select distinct Servername from dbo.tableA where Servername like '%hw%'") technically works, there is no reason to wrap a double-quoted string literal ("...") in the subexpression operator - just omit the $(...) enclosure.
As for $Servername = #[SqlAdapter]: perhaps that was just pseudo code, but, to be clear: #[...] isn't a valid syntax construct in PowerShell (at least as of PowerShell 7.3[1]).
[1] By curious coincidence, #[...] just came up as potential future syntax for simplifying PowerShell's [pscustomobject] object-literal syntax - see GitHub issue #18747.

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.

How to import data from .csv in SQL Server using PowerShell?

I'm using PowerShell and have to import data from a .csv file into a already created table on a SQL Server Database. So I don't need the header line from the csv, just write the data.
Here is what I have done so far:
#Setup for SQL Server connection
#SQL Server Name
$SQLServer = "APPLIK02\SQLEXPRESS"
#Database Name
$SQLDBName = "code-test"
#Create the SQL Connection Object
$SQLConn = New-Object System.Data.SQLClient.SQLConnection
#Create the SQL Command Object, to work with the Database
$SQLCmd = New-Object System.Data.SQLClient.SQLCommand
#Set the connection string one the SQL Connection Object
$SQLConn.ConnectionString = "Server=$SQLServer;Database=$SQLDBName; Integrated Security=SSPI"
#Open the connection
$SQLConn.Open()
#Handle the query with SQLCommand Object
$SQLCmd.CommandText = $query
#Provide the open connection to the Command Object as a property
$SQLCmd.Connection = $SQLConn
#Execute
$SQLReturn=$SQLCmd.ExecuteReader()
Import-module sqlps
$tablename = "dbo."+$name
Import-CSV .\$csvFile | ForEach-Object Invoke-Sqlcmd
-Database $SQLDBName -ServerInstance $SQLServer
#-Query "insert into $tablename VALUES ('$_.Column1','$_.Column2')"
#Close
$SQLReturn.Close()
$SQLConn.Close()
I wrote a blog post about using SQL with PowerShell, so you can read more about it here.
We can do this easily if you have the SQL-PS module available. Simply provide values for your database name, server name, and table, then run the following:
$database = 'foxdeploy'
$server = '.'
$table = 'dbo.powershell_test'
Import-CSV .\yourcsv.csv | ForEach-Object {Invoke-Sqlcmd `
-Database $database -ServerInstance $server `
-Query "insert into $table VALUES ('$($_.Column1)','$($_.Column2)')"
}
To be clear, replace Column1, Column2 with the names of the columns in your CSV.
Be sure that your CSV has the values in the same format as your SQL DB though, or you can run into errors.
When this is run, you will not see any output to the console. I would recommend querying afterwards to be certain that your values are accepted.

Create Schema Clones with RedGate sqlcompare & table of server names

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
}

Resources