Calling stored procedure with multiple parameters in powershell - sql-server

I have an stored procedure with many parameters:
CREATE Procedure [dbo].[SQLdatabase]
(
#operation nvarchar(10) = null,
#mode nvarchar(10) = 'smart',
#LogToTable bit = 0
) As .....(it's a long procedure)
And want to call the SProcedure in Powershell code.In the beginning of the code I've called the parameters, if I need to define them in code?
$operation = 'index'
$mode = 'smart'
$LogToTable = $true
Don't know how to complete the below code and call parameters correctly.
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection
$sqlConnection.ConnectionString = "Data Source=$dataSource;Initial Catalog=$database;Integrated Security=False;User ID=$userNameSecret ;Password=$passwordSecret ;Connect Timeout=60;Encrypt=False;TrustServerCertificate=False"
$sqlConnection.Open()
$param =$sqlConnection .Parameters.Add("#operation",[Data.SqlDbType]::nvarchar ,"#mode",[Data.SqlDbType]::nvarchar,"#LogToTable",[Data.SqlDbType]::bit )
Write-Output "DATABASE COMMAND SQLdatabase"
$sqlConnection.ExecuteNonQuery()
$sqlConnection.Close()

I would just use Invoke-SqlCmd for this:
$operation = 'index'
$mode = 'smart'
$LogToTable = $true
$query = #"
exec MyProcedure $operation,$mode,$LogToTable
"#
Invoke-SqlCmd -Query $query -Database $database -ServerInstance $dataSource -Username $userNameSecret -Password $passwordSecret

Related

Dynamic SQL Where condition, in Powershell script

I am working on a Powershell script where output of query #1 is where condition feed for query #2 but it is not getting the feed, if someone please see and let me know what could be possible solution s for this.
Also please note, in real environment, both the queries are being run on different instances and no possibilities of linked server
Below example is what I was trying in the AdventureWorks database:
$instance="WIN2016-SQL01\SQLSERVER_01"
$database = "AdventureWorks2014"
$query1 = "SELECT TOP 10 [BusinessEntityID] FROM [AdventureWorks2014].[Person].[BusinessEntityAddress] where BusinessEntityID < 10 order by 1 "
$Q1 = (invoke-sqlcmd -query $query1 -ServerInstance $instance -Database $database)
$query2 = "SELECT * FROM [AdventureWorks2014].[Person].[Person] where BusinessEntityID in ($Q1)"
$Q2 = invoke-sqlcmd -query $query2 -ServerInstance $instance -Database $database
Or you can just build a delimited string to substitute for your IN clause:
$instance="localhost"
$database = "AdventureWorks2017"
$query1 = "SELECT TOP 10 [BusinessEntityID] FROM [Person].[BusinessEntityAddress] where BusinessEntityID < 10 order by 1 "
$Q1 = (invoke-sqlcmd -query $query1 -ServerInstance $instance -Database $database)
$ids = ""
foreach ($r in $Q1)
{
$ids += "," + $r.BusinessEntityID
}
$ids = $ids.Substring(1)
$query2 = "SELECT * FROM [Person].[Person] where BusinessEntityID in ($ids)"
$Q2 = invoke-sqlcmd -query $query2 -ServerInstance $instance -Database $database
$Q2 | format-table
The list of values can be passed as an XML parameter to the query, where XML methods can be used to extract the value. A JSON string value is an option in SQL 2016 and later but I see you are on SQL Server 2014.
The example below converts the Q1 result list of BusinessEntityID values to an XML parameter value. Since Invoke-SqlCmd doesn't support parameterized queries, it is necessary to use the SqlClient objects directly. An alternative to Invoke-SqlCmd is Invoke-DbaQuery from dbatools, which supports parameterized queries if you have that avaiable.
$instance="WIN2016-SQL01\SQLSERVER_01"
$database = "AdventureWorks2014"
$query1 = "SELECT TOP 10 [BusinessEntityID] FROM [AdventureWorks2014].[Person].[BusinessEntityAddress] where BusinessEntityID < 10 order by 1 "
$Q1 = (invoke-sqlcmd -query $query1 -ServerInstance $instance -Database $database)
$list = #()
foreach ($row in $Q1)
{
$list += $row["BusinessEntityID"]
}
$listXml = $list | ConvertTo-Xml -NoTypeInformation
$listXmlString = $x.Objects.InnerXml
$query2 = "SELECT *
FROM [AdventureWorks2014].[Person].[Person]
WHERE BusinessEntityID IN (
SELECT item.value('.','int')
FROM #list.nodes('/Object') AS list(item)
);"
$connectionString = "Data Source=$instance;Initial Catalog=$database;Integrated Security=SSPI"
$connection = New-Object System.Data.SqlClient.SqlConnection($connectionString)
$command = New-Object System.Data.SqlClient.SqlCommand($query2, $connection)
$dataAdapter = New-Object System.Data.SqlClient.SqlDataAdapter($command)
($command.Parameters.Add("#list", [System.Data.SqlDbType]::Xml)).Value = $listXmlString
$Q2 = New-Object System.Data.DataTable
$dataAdapter.Fill($Q2)

Powershell SQL Select statements using csv variables

In the code below, I'm trying to query a DB with multiple select statements using variables brought in from a csv and load a data-table using a reader.
The code runs without error but does not retrieve any data.
$csv = Import-Csv $filepath
$database = "DBNAME"
$connectionString = "Server=$dataSource;uid=$user; pwd=$pwd;Database=$database;Integrated Security=True;"
$connection = New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString = $connectionString
$connection.Open()
$sqlCommand = $connection.CreateCommand()
$Datatable = New-Object System.Data.DataTable
ForEach ($row in $csv){
$query = "Select Emailaddress,Column2 from Users Where [Emailaddress] = '$row.Email'"
$sqlCommand.CommandText = $query
$DataReader = $sqlCommand.ExecuteReader()
$DataTable.Load($DataReader)
}
$DataTable | export-csv "c:\Output\Seereader.csv" -NoTypeInformation
$connection.Close()
This:
$query = "Select Emailaddress,Column2 from Users Where [Emailaddress] = '$row.Email'"
Should probably be this:
$query = "Select Emailaddress,Column2 from Users Where [Emailaddress] = '$($row.Email)'"
Whilst the previous answer works, it is vulnerable to SQL injection.
Obligatory xkcd
If you're not sure what "SQL Injection" is; it's only a very worthy Google away...
i.e. you really need to go and find out!
The correct way...
Parameterise your queries!
# Your query; with a #param
$Query = "SELECT Emailaddress, Column2 FROM [Users] WHERE [Emailaddress] = #emailAddress";
# Set up your basic command
$command = $connection.CreateCommand()
$command.CommandText = $Query
# Fill in the parameters!
$command.Parameters.AddWithValue("#emailAddress", $row.Email)
# Run boy, run!
$results = $command.ExecuteReader()
# Resultification (that's definitely not a made up word)
$table = New-Object System.Data.DataTable
$table.Load($results)
Safe and sound :-)

SQL query for Bulk Update

I need to update a table using text file. Currently my code works fine if I perform Get-Content from txt file and then run the SQL update query, but only in case of small data. If the size of text is too long or it contains some special characters, it throws an error as following:
Exception calling "ExecuteReader" with "0" argument(s): "Incorrect syntax near
')</td><td style=\"border:1px solid #cccccc\">#fieldValueEmpty($issue.getCustom
FieldValue($componentTypeCf),'."
At C:\Users\d-mansings\Desktop\Scripted Field Configuration\Script\Prod_UpdateS
cript.ps1:78 char:37
+ $Reader = $Command.ExecuteReader <<<< ()
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
Following is the code I'm using:
Function DatabaseQueries(){
#To connect to the SQL database
$Connection = New-Object System.Data.SQLClient.SQLConnection
$Connection.ConnectionString = "Server=$IPSource ; Database=$DBNameSource ; User ID=$UserIDSource ; Password=$LoginPwdSource;"
$Connection.Open()
#Query to get the ID of the stored script field from propertyentry
$Command1 = New-Object System.Data.SQLClient.SQLCommand
$Command1.Connection = $Connection
$Command1.CommandText = "SELECT [ID] FROM [dbo].[propertyentry] WHERE [PROPERTY_KEY]='com.onresolve.jira.groovy.groovyrunner:customfields' "
$Reader = $Command1.ExecuteReader()
while ($Reader.Read()) {
$ID = $Reader.GetValue($1)
}
#To get the updated script file
$ScriptDir = $ParentDir + '\Script.txt'
$ScriptData = Get-Content "$ScriptDir"
$Connection.Close()
#Query to update the Script in JIRA database
$Connection.Open()
$Command = New-Object System.Data.SQLClient.SQLCommand
$Command.Connection = $Connection
$Command.CommandText = #"
Update [dbo].[propertytext] set [propertyvalue] ='$ScriptData' Where ID=$ID
"#
$Reader = $Command.ExecuteReader()
$Connection.Close()
}
It is difficult to write a complete solution if file contents and database structure are not specified. You surely encountered some kind of SQL injection. SQL Query concatenation is considered harmful and you should avoid it. Use ADO.NET parameters to pass variables ($Command.Parameters.AddWithValue in your example). See the following example:
function Invoke-Sql(
$ConnectionString,
$Query,
$Parameters
) {
$conn = New-Object System.Data.SqlClient.SqlConnection -ArgumentList $ConnectionString
$cmd = New-Object System.Data.SqlClient.SqlCommand -ArgumentList $Query,$conn
$conn.Open()
foreach ($arg in $Parameters.GetEnumerator()){
$cmd.Parameters.AddWithValue($arg.Key, $arg.Value) | Out-Null;
}
$reader = $cmd.ExecuteReader()
if ($reader.Read()) {
[string[]]$columns = 0..($reader.FieldCount-1) |
% { if ($reader.GetName($_)) { $reader.GetName($_) } else { "(no name $_)" } }
do {
$obj = #{}
0..($reader.FieldCount-1) | % { $obj.Add($columns[$_], $reader[$_]) }
New-Object PSObject -Property $obj
} while ($reader.Read())
}
$reader.Dispose()
$cmd.Dispose()
$conn.Dispose()
}
Invoke-Sql `
-ConnectionString "Server=.\SQL2014;Database=Test1;Integrated Security=true" `
-Query 'SELECT Name, Id [ObjectId], Id + 3, #arg FROM IdNameTest' `
-Parameters #{arg = 'Some text'''}
Invoke-Sql `
-ConnectionString "Server=.\SQL2014;Database=Test1;Integrated Security=true" `
-Query 'UPDATE IdNameTest SET Name=#name WHERE Id=#id' `
-Parameters #{name = "'DROP DATABASE Death;! %&#!$"; id=1}
Thanks for the response, I have figured out a way to execute the query by just using a replace function, as it was getting confused between the single inverted commas
select REPLACE(Cast(propertyvalue AS varchar(Max)), '''', '''''') FROM [dbo].[propertytext] WHERE ID=$ID

Calling SQL Server stored procedure from powershell

I'm trying to call a SQL Server stored procedure from PowerShell but I always get errors on parameters.
Stored procedure has 8 parameters, all with default values
#simchain nvarchar
#idSimulation int
#idCompany varchar
#modelName nvarchar
#simDate datetime
#mySim int
#statusFloor int
#statusCap int
From Management Studio I can call this procedure even without any parameter, so just executing EXEC [dbo].[E_simulations] works.
From PowerShell I create a connection and a command but I always get an error on missing parameters, for example
Procedure or function 'E_simulations' expects parameter '#simchain', which was not supplied.
Here is my test code (just to test proper execution)
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection;
$SqlConnection.ConnectionString = $ConnectionString;
$SqlCommand = $SqlConnection.CreateCommand();
$SqlCommand.CommandText = "EXEC [dbo].[E_simulations]";
$SqlConnection.Open();
$returnedValue = $SqlCommand.ExecuteNonQuery();
Am I missing something?
I have made quick test. I hope this will help
Made test proc:
CREATE PROC doTest (
#param1 INT = 1,
#param2 VARCHAR(10) = 'xxx'
)
AS
BEGIN
PRINT 'THIS ONE'
SELECT 1 As Data
END
Find PowerShell code, which executes proc:
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server=localhost;Database=ForTests;Integrated Security=True"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = "doTest"
$SqlCmd.Connection = $SqlConnection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.Fill($DataSet)
$SqlConnection.Close()
$DataSet.Tables[0]
Execute result gave this
Data
----
1
I done everything same what you wrote and I have result without assigning params

How do I call a SQL Server stored procedure from PowerShell?

I have a large CSV file and I want to execute a stored procedure for each line.
What is the best way to execute a stored procedure from PowerShell?
This answer was pulled from http://www.databasejournal.com/features/mssql/article.php/3683181
This same example can be used for any adhoc queries. Let us execute the stored procedure “sp_helpdb” as shown below.
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server=HOME\SQLEXPRESS;Database=master;Integrated Security=True"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = "sp_helpdb"
$SqlCmd.Connection = $SqlConnection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.Fill($DataSet)
$SqlConnection.Close()
$DataSet.Tables[0]
Here is a function that I use (slightly redacted). It allows input and output parameters. I only have uniqueidentifier and varchar types implemented, but any other types are easy to add. If you use parameterized stored procedures (or just parameterized sql...this code is easily adapted to that), this will make your life a lot easier.
To call the function, you need a connection to the SQL server (say $conn),
$res=exec-storedprocedure -storedProcName 'stp_myProc' -parameters #{Param1="Hello";Param2=50} -outparams #{ID="uniqueidentifier"} $conn
retrieve proc output from returned object
$res.data #dataset containing the datatables returned by selects
$res.outputparams.ID #output parameter ID (uniqueidentifier)
The function:
function exec-storedprocedure($storedProcName,
[hashtable] $parameters=#{},
[hashtable] $outparams=#{},
$conn,[switch]$help){
function put-outputparameters($cmd, $outparams){
foreach($outp in $outparams.Keys){
$cmd.Parameters.Add("#$outp", (get-paramtype $outparams[$outp])).Direction=[System.Data.ParameterDirection]::Output
}
}
function get-outputparameters($cmd,$outparams){
foreach($p in $cmd.Parameters){
if ($p.Direction -eq [System.Data.ParameterDirection]::Output){
$outparams[$p.ParameterName.Replace("#","")]=$p.Value
}
}
}
function get-paramtype($typename,[switch]$help){
switch ($typename){
'uniqueidentifier' {[System.Data.SqlDbType]::UniqueIdentifier}
'int' {[System.Data.SqlDbType]::Int}
'xml' {[System.Data.SqlDbType]::Xml}
'nvarchar' {[System.Data.SqlDbType]::NVarchar}
default {[System.Data.SqlDbType]::Varchar}
}
}
if ($help){
$msg = #"
Execute a sql statement. Parameters are allowed.
Input parameters should be a dictionary of parameter names and values.
Output parameters should be a dictionary of parameter names and types.
Return value will usually be a list of datarows.
Usage: exec-query sql [inputparameters] [outputparameters] [conn] [-help]
"#
Write-Host $msg
return
}
$close=($conn.State -eq [System.Data.ConnectionState]'Closed')
if ($close) {
$conn.Open()
}
$cmd=new-object system.Data.SqlClient.SqlCommand($sql,$conn)
$cmd.CommandType=[System.Data.CommandType]'StoredProcedure'
$cmd.CommandText=$storedProcName
foreach($p in $parameters.Keys){
$cmd.Parameters.AddWithValue("#$p",[string]$parameters[$p]).Direction=
[System.Data.ParameterDirection]::Input
}
put-outputparameters $cmd $outparams
$ds=New-Object system.Data.DataSet
$da=New-Object system.Data.SqlClient.SqlDataAdapter($cmd)
[Void]$da.fill($ds)
if ($close) {
$conn.Close()
}
get-outputparameters $cmd $outparams
return #{data=$ds;outputparams=$outparams}
}
Here is a function I use to execute sql commands. You just have to change $sqlCommand.CommandText to the name of your sproc and $SqlCommand.CommandType to CommandType.StoredProcedure.
function execute-Sql{
param($server, $db, $sql )
$sqlConnection = new-object System.Data.SqlClient.SqlConnection
$sqlConnection.ConnectionString = 'server=' + $server + ';integrated security=TRUE;database=' + $db
$sqlConnection.Open()
$sqlCommand = new-object System.Data.SqlClient.SqlCommand
$sqlCommand.CommandTimeout = 120
$sqlCommand.Connection = $sqlConnection
$sqlCommand.CommandText= $sql
$text = $sql.Substring(0, 50)
Write-Progress -Activity "Executing SQL" -Status "Executing SQL => $text..."
Write-Host "Executing SQL => $text..."
$result = $sqlCommand.ExecuteNonQuery()
$sqlConnection.Close()
}
Use sqlcmd instead of osql if it's a 2005 database
Consider calling osql.exe (the command line tool for SQL Server) passing as parameter a text file written for each line with the call to the stored procedure.
SQL Server provides some assemblies that could be of use with the name SMO that have seamless integration with PowerShell. Here is an article on that.
http://www.databasejournal.com/features/mssql/article.php/3696731
There are API methods to execute stored procedures that I think are worth being investigated. Here a startup example:
http://www.eggheadcafe.com/software/aspnet/29974894/smo-running-a-stored-pro.aspx
I include invoke-sqlcmd2.ps1 and write-datatable.ps1 from http://blogs.technet.com/b/heyscriptingguy/archive/2010/11/01/use-powershell-to-collect-server-data-and-write-to-sql.aspx. Calls to run SQL commands take the form: Invoke-sqlcmd2 -ServerInstance "<sql-server>" -Database <DB> -Query "truncate table <table>" An example of writing the contents of DataTable variables to a SQL table looks like: $logs = (get-item SQLSERVER:\sql\<server_path>).ReadErrorLog()
Write-DataTable -ServerInstance "<sql-server>" -Database "<DB>" -TableName "<table>" -Data $logs I find these useful when doing SQL Server database-related PowerShell scripts as the resulting scripts are clean and readable.
Adds CommandType and Parameters to #Santiago Cepas' answer:
function Execute-Stored-Procedure
{
param($server, $db, $spname)
$sqlConnection = new-object System.Data.SqlClient.SqlConnection
$sqlConnection.ConnectionString = 'server=' + $server + ';integrated security=TRUE;database=' + $db
$sqlConnection.Open()
$sqlCommand = new-object System.Data.SqlClient.SqlCommand
$sqlCommand.CommandTimeout = 120
$sqlCommand.Connection = $sqlConnection
$sqlCommand.CommandType= [System.Data.CommandType]::StoredProcedure
# If you have paramters, add them like this:
# $sqlCommand.Parameters.AddWithValue("#paramName", "$param") | Out-Null
$sqlCommand.CommandText= $spname
$text = $spname.Substring(0, 50)
Write-Progress -Activity "Executing Stored Procedure" -Status "Executing SQL => $text..."
Write-Host "Executing Stored Procedure => $text..."
$result = $sqlCommand.ExecuteNonQuery()
$sqlConnection.Close()
}
# Call like this:
Execute-Stored-Procedure -server "enter-server-name-here" -db "enter-db-name-here" -spname "enter-sp-name-here"
I added timeout and show how to reader a scalar or get results using a reader
function exec-query( $storedProcName,$parameters=#{},$conn,$timeout=60){
$cmd=new-object system.Data.SqlClient.SqlCommand
$cmd.CommandType=[System.Data.CommandType]'StoredProcedure'
$cmd.Connection=$conn
$cmd.CommandText=$storedProcName
$cmd.CommandTimeout=$timeout
foreach($p in $parameters.Keys){
[Void] $cmd.Parameters.AddWithValue("#$p",$parameters[$p])
}
#$id=$cmd.ExecuteScalar()
$adapter=New-Object system.Data.SqlClient.SqlDataAdapter($cmd)
$dataset=New-Object system.Data.DataSet
$adapter.fill($dataset) | Out-Null
#$reader = $cmd.ExecuteReader()
#$results = #()
#while ($reader.Read())
#{
# write-host "reached" -ForegroundColor Green
#}
return $dataSet.Tables[0]
}

Resources