I am trying to run a SQL from Power Shell(which is on my windows 7 64 bit desktop) and the remote database host is MS SQL Server 2012.
The code is:
$var1 = 'string';
function Get-ODBC-Data{
param(
[string]$query=$('
SELECT COUNT(*)
FROM [master].[sys].[table_name]
WHERE col2 = ''$var1''
;
'),
[string]$username='db_user_name',
[string]$password='db_password'
)
$conn = New-Object System.Data.Odbc.OdbcConnection
$conn.ConnectionString = "DRIVER={SQL Server};Server=123.456.78.90;Initial Catalog=master;Uid=$username;Pwd=$password;"
$conn.open()
$cmd = New-object System.Data.Odbc.OdbcCommand($query,$conn)
$ds = New-Object system.Data.DataSet
(New-Object system.Data.odbc.odbcDataAdapter($cmd)).fill($ds) | out-null
$conn.close()
$ds.Tables[0]
}
$result = Get-ODBC-Data
Write-Host "SQL_Output: " $result[0];
If I use 'string' in the SQL's where clause instead of $var1 then te script works fine and gives expected result.
Quetion
But I want to be able to pass any string as $var1 to the script as parameter. Then use it in the where clause of the SQL. How can I achieve this?
What I tried
I have tried to enclose $var1 in 1,2 or 3 single quotes in the where clause in attempt to escape the single quote. Also tried adding/removing single quote from 'string' when $var1 is assigned value. I did try [string]$var1 = 'string' as well but none of these worked and I keep getting error mostly related to SQL syntax.
Try this:
function Get-ODBC-Data{
param(
[string]$query=$("
SELECT COUNT(*)
FROM [master].[sys].[table_name]
WHERE col2 = '$($var1)'
;
"),
[string]$username='db_user_name',
[string]$password='db_password'
)
$conn = New-Object System.Data.Odbc.OdbcConnection
$conn.ConnectionString = "DRIVER={SQL Server};Server=123.456.78.90;Initial Catalog=master;Uid=$username;Pwd=$password;"
$conn.open()
$cmd = New-object System.Data.Odbc.OdbcCommand($query,$conn)
$ds = New-Object system.Data.DataSet
(New-Object system.Data.odbc.odbcDataAdapter($cmd)).fill($ds) | out-null
$conn.close()
$ds.Tables[0]
}
$result = Get-ODBC-Data
Write-Host "SQL_Output: " $result[0];
The following runs fine on my setup, and shows the correct results:
$var1 = "test22"
function Get-ODBC-Data{
param(
[string]$query=$("
SELECT COUNT(*)
FROM [master].[sys].[table_name]
WHERE col2 = '$($var1)'
;
"),
[string]$username='db_user_name',
[string]$password='db_password'
)
return $query
}
$result = Get-ODBC-Data
Write-Host " ################### Query ######################## "
Write-Host $result
However, you may have a much easier time just passing the entire query into the function as a parameter rather than just one variable part of the query.
Or setting it inside the function and passing $var1 as a mandatory parameter like so:
function Get-ODBC-Data{
param(
[parameter(Mandatory=$true)][string]$var1,
[string]$username='db_user_name',
[string]$password='db_password'
)
$query="
SELECT COUNT(*)
FROM [master].[sys].[table_name]
WHERE col2 = '$($var1)'
;"
return $query
}
$result = Get-ODBC-Data -var1 "working"
Write-Host " ################### Query ######################## "
Write-Host $result
Related
I stole the following code that allows me to query a csv file with via a SQL query. The code reliably outputs a table which is nice. Ultimately what I would like to do is perform an action if the results of my query is zero records.
How would I go about doing that?
$firstRowColumnNames = "Yes"
$delimiter = ","
$provider = 'Microsoft.ACE.OLEDB.16.0'
$connstring = "Provider=$provider;Data Source=$(Split-Path $csv);Extended Properties='text;HDR=$firstRowColumnNames;';"
$tablename = (Split-Path $csv -leaf).Replace(".","#")
$sql = "SELECT * from [$tablename] Where sSamAccountName='acco'"
$sql
$conn = New-Object System.Data.OleDb.OleDbconnection
$conn.ConnectionString = $connstring
$conn.Open()
$cmd = New-Object System.Data.OleDB.OleDBCommand
$cmd.Connection = $conn
$cmd.CommandText = $sql
# Load into datatable
$dt = New-Object System.Data.DataTable
$dt.Load($cmd.ExecuteReader("CloseConnection"))
#Clean up
$cmd.dispose | Out-Null; $conn.dispose | Out-Null
#Output results
$dt | Format-Table -AutoSize
Following Dai's helpful comment, on PowerShell syntax you can check this using the -not logical operator as another alternative to Rows.Count -eq 0:
$dt = [System.Data.DataTable]::new()
$dt.Columns.AddRange(#('col1','col2'))
# DataTable only has 2 columns defined but now Rows
-not $dt.Rows.Count # -not 0 or -not $null -> $true // [int]$null = 0
[bool]$dt.Rows.Count # [bool]$null or [bool]0 -> $false
# Add a new Row to the DataTable
$row = $dt.NewRow()
$row['col1'] = 'ExampleVal1'
$row['col2'] = 'ExampleVal2'
$dt.Rows.Add($row)
-not $dt.Rows.Count # not 0 or not null when negated -> $false
[bool]$dt.Rows.Count # not 0 or not null in boolean expression -> $true
$dt.Dispose()
I would like to setup a new PowerShell script that invokes my Database Stored Procedure concurrently. I am currently having a Control table that has a Job_ID column and a Code column. There might be more than one Job_ID for a code value in the Control table. Based on the code value I pass in the PowerShell along with a date, I would like the PowerShell to trigger the Stored Procedure which is expecting "Job_ID" and "MyDate" as input parameters.
FYI, I am using PowerShell and SQL Server 2016.
PS C:\PowerShell> $PSVersionTable.PSVersion
Major Minor Build Revision
----- ----- ----- --------
2 0 -1 -1
Here is some sample data for your reference:
CREATE TABLE control_table(JOB_ID INT, CODE VARCHAR(5));
INSERT INTO control_table(1, 'ABC');
INSERT INTO control_table(2, 'ABC');
INSERT INTO control_table(3, 'ABC');
INSERT INTO control_table(1, 'DEF');
INSERT INTO control_table(1, 'GHI');
CREATE PROCEDURE myschema.run_job (#JOB_ID INT, #MyDate DATE)
AS
BEGIN
-- Do Something
END
When I run the PowerShell script by passing 'ABC" as code, it should execute all the three jobs concurrently by reading the control table.
Something like
.\test.ps1 –MyCode “ABC” –Dt “12/27/2018”
As an alternative to jobs you can use async methods of built-in SQL client. Below is the sample code. I assume you already has some "run_job" procedure that can execute other procedures (jobs) by id.
$code = "ABC"
$date = "2018-12-31"
$jobs = #{} # this will store results of async jobs
$str = "Server = YourServer; Database = YourDB; Integrated Security = True;"
#--------
function Async-Sql { param($connStr, $sql, [switch]$GetDataTable)
$conn = New-Object System.Data.SqlClient.SqlConnection $str
$cmd = $conn.CreateCommand()
$conn.Open()
$cmd.CommandText = $sql
if($GetDataTable) {
$dt = New-Object System.Data.DataTable "result"
$r = $cmd.ExecuteReader()
$dt.Load($r)
$conn.Close()
return #(,$dt)
} else {
$w = $cmd.ExecuteNonQueryAsync()
return [PSCustomObject]#{result=$w; conn = $conn} }
}
# ---------------------------------------------
# get a list of jobs from your control table, this will run synchronously
$jobList = Async-Sql -connStr $str -sql "select job_id, code from test.control_table where code = '$code'" -GetDataTable
# main loop. You should call your stored procedure here. Each iteration will create a new connection and execute command asynchronously
foreach($id in $jobList.job_id) {
$command = "EXEC run_job $id, $date"
$r = Async-Sql -connStr $str -sql $command
$jobs.Add( $id, $r )
}
# wait for all jobs to complete
while ($False -in $jobs.Values.result.isCompleted) { sleep -Milliseconds 500 }
# print results / close connections. If you see status as RanToCompletion the job is completed successfuly
foreach($j in $jobs.Keys) {
$res = $jobs[$j].result
[PSCustomObject]#{JobId=$j; isCompleted = $res.isCompleted; Status = $res.Status; result = $res.Result }
$jobs[$j].conn.close()
}
Since you have powershell V2, I'm adding a solution with PS Jobs. Save the code below as sqlExec.ps1:
param($connStr, $sql)
$conn = New-Object System.Data.SqlClient.SqlConnection $connStr
$cmd = $conn.CreateCommand()
$conn.Open()
$cmd.CommandText = $sql
$r = $cmd.ExecuteNonQuery()
$conn.Close()
return $r
Then use this code as a master script:
$str = "Server = YourServer; Database = YourDB; Integrated Security = True;"
$code = "ABC"
$date = (get-date).ToString("yyyy-MM-dd")
$execSript = "path\to\sqlExec.ps1"
# get a list of ids for code. You can achive the same with Invoke-sqlcmd or similar cmdlet.
$conn = New-Object System.Data.SqlClient.SqlConnection $str
$cmd = $conn.CreateCommand()
$conn.Open()
$cmd.CommandText = "select job_id, code from test.control_table where code = '$code'"
$dt = New-Object System.Data.DataTable "result"
$r = $cmd.ExecuteReader()
$dt.Load($r)
$conn.Close()
# main loop
$jobs = #{}
foreach($id in $dt.job_id) { $top = $id*3
$sql = "EXEC run_job $id, $date"
$jobs.Add($id,(Start-Job -FilePath $execSript -ArgumentList $str, $sql))
}
# Wait for jobs and get result
$jobs.Values | Wait-Job | Receive-Job
If you have Invoke-Sqlcmd module you can use it for sql code execution (instead of creating $conn,$cmd,etc)
I have the following powershell script that is supposed to run a process command on the cubes after ordering their sequence in the table
param($App_input, $Script) #%2 and %3 arguments from cmd line input;
Function Query($Query) {
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server=$Server;Initial Catalog=$Database;Integrated Security=SSPI"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.Connection = $SqlConnection
$SqlCmd.CommandText = $Query
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object System.Data.DataSet
$a=$SqlAdapter.Fill($DataSet)
$SqlConnection.Close()
$DataSet.Tables[0] }
#$cube = Query "SELECT DISTINCT cube_name, sequence FROM [dbo].[$cubeTable] WHERE [application] = '$App_input' ORDER BY [sequence]" | Select -ExpandProperty cube_name;
$Table = Query "SELECT * from [dbo].[$cubeTable]"
# -ExpandProperty trims the column name, so it only outputs the value, not with header!
$App = Query "SELECT DISTINCT[application] FROM [dbo].[$cubeTable] WHERE [application] = '$App_input'" | Select -ExpandProperty application;
$i=0
$CUBE = #()
foreach($row in $Table | where { $_.application -match $App })
{
$i++
$CUBE += $row.Item("cube_name")
write-host " > CUBE $($i):" $CUBE[$CUBE -1]
cmd /c "runPowerShell.bat $Script $CUBE" #where $Script is Process.ps1 script that processes on each CUBE selected from table in order
}
This is the hypothetical table:
for example: if i were to pass in the app parameter and the script parameter as such:
someBatchFile.bat SSP Process.ps1
the script first should check that the loop is only returning results where [application] column = SSP, so avoiding the 3rd row in this case. however, the where clause i have $_.application -match $App for some reason is not having any effect, and the powershell returns all rows regardless...
after that check is done, the script should order the rows (from 2, 2, 1 to 1, 2, 2) returning CUBE2, CUBE1 (just one time since its distinct) which is passed in each iteration to $CUBE in the cmd line.
essentially, in this examples, the iterations should look like this:
cmd /c "runPowerShell.bat $Process.ps1 CUBE2"
cmd /c "runPowerShell.bat $Process.ps1 CUBE1"
I know order by would be helpful in this case, but i dont know how can i use that in the case of foreach and $row.Item
Your Query functions returns DataTable type object which means $Table variable is DataTable too.
For DataTable type variable you want to use Select() method to get filtered results. Like so:
$Table.Select("application = '$App'")
Also. Check out your whole code, this things looks really wierd for me.
param($App_input, $Script)
$App = Query "SELECT DISTINCT[application] FROM [dbo].[$cubeTable] WHERE [application] = '$App_input'" | Select -ExpandProperty application;
You are passing $application to the script to next pass it for query that should return application value from database table while that query's filter condition is equals to application value.
You could just use application value already.
I figured it out:
I could have just done the checks in $Table
$Table = Query "SELECT * from [dbo].[$cubeTable] WHERE [application] = '$App_input' AND [active] = 'TRUE' ORDER BY [sequence]"
$i=0
$CUBE = #()
foreach($row in $Table)
{
$i++
$CUBE += $row.Item("cube_name")
write-host " > CUBE $($i): $($CUBE[-1])`n"
cmd /c "runPowerShell.bat $Script $($CUBE[-1])" #where $Script is Process.ps1 script that processes on each CUBE selected from table in order
}
try this
$App = Query "SELECT DISTINCT cube_name FROM [dbo].[$cubeTable] WHERE [application] = '$App_input'"
$i=0
$App | %{
$i++
$CUBE=$_.Item("cube_name")
" > CUBE $i : $CUBE"
cmd /c "runPowerShell.bat $Script $CUBE"
}
$sf = "\\\\domain\\dept\\dcgsi\\Extracts\\Tableau_Unlicensed_Users.csv"
if (Test-Path $sf){
Remove-Item $sf
}
$query = #"
\\copy (SELECT Name
FROM _users
WHERE licensing_role_name = 'Unlicensed')
TO $sf
WITH CSV DELIMITER ','
"#
$conn = New-Object -ComObject ADODB.Connection
# use existing 64 bit ODBC System DSN that we set up manually
$conn.Open('PostgreSQL30')
$conn.Execute($query)
$conn.Close()
I keep getting an error about "\" on the line with the $conn.Execute() when I try and do this. I assume it has to do with character escaping and maybe I am doing it wrong.
Is there a better way to do this with PowerShell if I just need to get the name field of any record from _users and output it to CSV?
Eventually I will be adding more to this to loop through each record in the CSV and execute a tabcmd to remove all the users that are unlicensed.
$sf = "\\domain\dept\dcgsi\Extracts\Tableau_Unlicensed_Users.csv"
if (Test-Path $sf){
Remove-Item $sf
}
$query = #"
SELECT Name
FROM _users
WHERE licensing_role_name = 'Unlicensed'
"#
function Get-ODBC-Data{
$conn = New-Object System.Data.Odbc.OdbcConnection
$conn.ConnectionString = "DSN=PostgreSQL30;"
$conn.Open()
$cmd = New-object System.Data.Odbc.OdbcCommand($query, $conn)
$ds = New-Object system.Data.DataSet
(New-Object System.Data.Odbc.OdbcDataAdapter($cmd)).Fill($ds) | Out-Null
$conn.Close()
$ds.Tables[0] | Export-Csv $sf
}
Get-ODBC-Data
This did like 99% of what I need; I just have to process the csv now and drop the first two lines. The first line is a type info message and the second is the column header.
Is there a way to execute an arbitrary query on a SQL Server using Powershell on my local machine?
For others who need to do this with just stock .NET and PowerShell (no additional SQL tools installed) here is the function that I use:
function Invoke-SQL {
param(
[string] $dataSource = ".\SQLEXPRESS",
[string] $database = "MasterData",
[string] $sqlCommand = $(throw "Please specify a query.")
)
$connectionString = "Data Source=$dataSource; " +
"Integrated Security=SSPI; " +
"Initial Catalog=$database"
$connection = new-object system.data.SqlClient.SQLConnection($connectionString)
$command = new-object system.data.sqlclient.sqlcommand($sqlCommand,$connection)
$connection.Open()
$adapter = New-Object System.Data.sqlclient.sqlDataAdapter $command
$dataset = New-Object System.Data.DataSet
$adapter.Fill($dataSet) | Out-Null
$connection.Close()
$dataSet.Tables
}
I have been using this so long I don't know who wrote which parts. This was distilled from others' examples, but simplified to be clear and just what is needed without extra dependencies or features.
I use and share this often enough that I have turned this into a script module on GitHub so that you can now go to your modules directory and execute git clone https://github.com/ChrisMagnuson/InvokeSQL and from that point forward invoke-sql will automatically be loaded when you go to use it (assuming your using PowerShell v3 or later).
You can use the Invoke-Sqlcmd cmdlet
Invoke-Sqlcmd -Query "SELECT GETDATE() AS TimeOfQuery;" -ServerInstance "MyComputer\MyInstance"
http://technet.microsoft.com/en-us/library/cc281720.aspx
This function will return the results of a query as an array of powershell objects so you can use them in filters and access columns easily:
function sql($sqlText, $database = "master", $server = ".")
{
$connection = new-object System.Data.SqlClient.SQLConnection("Data Source=$server;Integrated Security=SSPI;Initial Catalog=$database");
$cmd = new-object System.Data.SqlClient.SqlCommand($sqlText, $connection);
$connection.Open();
$reader = $cmd.ExecuteReader()
$results = #()
while ($reader.Read())
{
$row = #{}
for ($i = 0; $i -lt $reader.FieldCount; $i++)
{
$row[$reader.GetName($i)] = $reader.GetValue($i)
}
$results += new-object psobject -property $row
}
$connection.Close();
$results
}
Here's an example I found on this blog.
$cn2 = new-object system.data.SqlClient.SQLConnection("Data Source=machine1;Integrated Security=SSPI;Initial Catalog=master");
$cmd = new-object system.data.sqlclient.sqlcommand("dbcc freeproccache", $cn2);
$cn2.Open();
if ($cmd.ExecuteNonQuery() -ne -1)
{
echo "Failed";
}
$cn2.Close();
Presumably you could substitute a different TSQL statement where it says dbcc freeproccache.
If you want to do it on your local machine instead of in the context of SQL server then I would use the following. It is what we use at my company.
$ServerName = "_ServerName_"
$DatabaseName = "_DatabaseName_"
$Query = "SELECT * FROM Table WHERE Column = ''"
#Timeout parameters
$QueryTimeout = 120
$ConnectionTimeout = 30
#Action of connecting to the Database and executing the query and returning results if there were any.
$conn=New-Object System.Data.SqlClient.SQLConnection
$ConnectionString = "Server={0};Database={1};Integrated Security=True;Connect Timeout={2}" -f $ServerName,$DatabaseName,$ConnectionTimeout
$conn.ConnectionString=$ConnectionString
$conn.Open()
$cmd=New-Object system.Data.SqlClient.SqlCommand($Query,$conn)
$cmd.CommandTimeout=$QueryTimeout
$ds=New-Object system.Data.DataSet
$da=New-Object system.Data.SqlClient.SqlDataAdapter($cmd)
[void]$da.fill($ds)
$conn.Close()
$ds.Tables
Just fill in the $ServerName, $DatabaseName and the $Query variables and you should be good to go.
I am not sure how we originally found this out, but there is something very similar here.
There isn't a built-in "PowerShell" way of running a SQL query. If you have the SQL Server tools installed, you'll get an Invoke-SqlCmd cmdlet.
Because PowerShell is built on .NET, you can use the ADO.NET API to run your queries.
Invoke-Sqlcmd -Query "sp_who" -ServerInstance . -QueryTimeout 3
To avoid SQL Injection with varchar parameters you could use
function sqlExecuteRead($connectionString, $sqlCommand, $pars) {
$connection = new-object system.data.SqlClient.SQLConnection($connectionString)
$connection.Open()
$command = new-object system.data.sqlclient.sqlcommand($sqlCommand, $connection)
if ($pars -and $pars.Keys) {
foreach($key in $pars.keys) {
# avoid injection in varchar parameters
$par = $command.Parameters.Add("#$key", [system.data.SqlDbType]::VarChar, 512);
$par.Value = $pars[$key];
}
}
$adapter = New-Object System.Data.sqlclient.sqlDataAdapter $command
$dataset = New-Object System.Data.DataSet
$adapter.Fill($dataset) | Out-Null
$connection.Close()
return $dataset.tables[0].rows
}
$connectionString = "connectionstringHere"
$sql = "select top 10 Message, TimeStamp, Level from dbo.log " +
"where Message = #MSG and Level like #LEVEL"
$pars = #{
MSG = 'this is a test from powershell'
LEVEL = 'aaa%'
};
sqlExecuteRead $connectionString $sql $pars
You can even format string and pass parameters as you want.
case "ADDSQLSERVERUSER":
//0 = coprorateName;
//1 = user password
//2 = servername
command = #"$sqlQuery = Use JazzUWS_'{0}'
Create login UWSUser_'{0}' with password='{1}';
Create user UWSUser_'{0}' for login UWSUser_'{0}';
Grant Execute to UWSUser_'{0}';
Use ReportSvrUWS_'{0}'
Create user UWSUser_'{0}' for login UWSUser_'{0}';
Grant Execute to UWSUser_'{0}';
Invoke-Sqlcmd -Query $sqlQuery -ServerInstance '{2}'";
break;
C# Code for remote execution(you can organize your way)
string script = PowershellDictionary.GetPowershellCommand("ADDSQLSERVERUSER");
script = String.Format(script, this.CorporateName, password, this.SQLServerName)
PowerShellExecution.RunScriptRemote(_credentials.Server, _credentials.Username, _credentials.Password, new List<string> { script });
You could use the best SQL Server module around: DBATOOLS. You would also benefit from running a query to multiple sql instances.
Install-Module dbatools -Scope CurrentUser
$sql = 'SQL1','SQL1\INSTANCE1','SQL2'
$query = "SELECT 'This query would run on all SQL instances'"
Invoke-DbaQuery -SqlInstance $sqlinstances -Query $query -AppendServerInstance