Dynamic SQL Where condition, in Powershell script - sql-server

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)

Related

Calling stored procedure with multiple parameters in powershell

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

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 :-)

Powershell - proper way to execute SQL query with multiple select statements and result tables

I'm trying to execute an SQL query with few select statements, that returns multiple tables as a result. The problem is that I can't find a way to read and use the tables separately.
Expected results:
Actual results: (it is printed row by row)
Purpose: I've made a script that creates an empty excel file with multiple sheets and each of the sheets will be used to contain each resultset of the query.
The only thing left is to put the needed text into the sheets. Here is my code for that part only:
$ConnectionString = "Data Source=...;Initial Catalog=...;User Id=...;Password=..."
$DBServerName = $ConnectionString.split('=')[1].split(';')[0]
$DBName = $ConnectionString.split('=')[2].split(';')[0]
$DBUser = $ConnectionString.split('=')[3].split(';')[0]
$DBPassword = $ConnectionString.split('=')[4].split(';')[0]
$CurrentFilePath = "C:\SQLqueryWithManyResultsets.sql"
$query = Get-Content -literalPath $CurrentFilePath | Out-String #getting the query string from file
$resultTables = Invoke-Sqlcmd -Query $query -ServerInstance $DBServerName -Database $DBName -DisableVariables -Password $DBPassword -Username $DBUser -ErrorAction Stop
foreach ($result in $resultTables) {
$result | Format-Table #where the magic happens
}
I've made a lot of research, but I cannot find a proper way to store and read the tables the way i need.
Try this:
Clear-Host;
$objConnection = New-Object System.Data.SqlClient.SqlConnection;
$objConnection.ConnectionString = "...";
$ObjCmd = New-Object System.Data.SqlClient.SqlCommand;
$ObjCmd.CommandText = "...";
$ObjCmd.Connection = $objConnection;
$ObjCmd.CommandTimeout = 0;
$objAdapter = New-Object System.Data.SqlClient.SqlDataAdapter;
$objAdapter.SelectCommand = $ObjCmd;
$objDataSet = New-Object System.Data.DataSet;
$objAdapter.Fill($objDataSet) | Out-Null;
for ($i=0; $i -lt $objDataSet.Tables.Count; $i++) {
Write-Host ($objDataSet.Tables[$i] | Format-Table | Out-String);
}
$query = $null;
$objDataSet = $null;
$objConnection.Close();
$objConnection = $null;

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

Powershell Invoke-Sqlcmd to DataTable rowcount is incorrect when its 1 record

$sql=("select top 1 * FROM CollectionProfile")
$CollectionProfile = New-Object System.Data.DataTable
$CollectionProfile = Invoke-Sqlcmd -ServerInstance $Instance -Database $db -Query $sql -ErrorAction Stop
$CollectionProfile.Rows.Count
RETURNS :0
But if I change the TOP count to 2 -
$sql=("select top 2 * FROM CollectionProfile")
RETURNS :2
Driving me crazy and yes, I could not find a single reference to this on the "innernets". I must be doing something wrong, but WHAT?
When you use the query with TOP 1, Invoke-SqlCmd returns a DataRow.
When you use the query with TOP 2, Invoke-SqlCmd returns an Array of DataRows. Invoke-SqlCmd does not return a DataTable. You could change your code to force an array to be returned (see here: force array), and then check the Count on it:
$sql = ("select top 1 * FROM CollectionProfile")
$CollectionProfile = #(Invoke-Sqlcmd -ServerInstance $Instance -Database $db -Query $sql -ErrorAction Stop)
$CollectionProfile.Count #Returns 0 with Null, 1 with TOP 1, and 2 with TOP 2
Use one of the column name from select statement in place of Rows, which will give correct result count.
Here in my example I gave name in place of rows which is my first column name in the select statement "Select top 1 * from Sysdatabases". This will give you correct result for top 1 or top 2 ..
$sql=("select top 1 * FROM sysdatabases")
$sysdatabases = New-Object System.Data.DataTable
$sysdatabases = Invoke-Sqlcmd -ServerInstance $Instance -Database $db -Query $sql -ErrorAction Stop
$sysdatabases.name.Count

Resources