Stored procedure called from Powershell does not execute via SMO object - sql-server

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

Related

The given value of type String from the data source cannot be converted to type uniqueidentifier of the specified target column

What I'm trying here is, to load the data from .xml file to the SQL server database using PowerShell Script.
My Script goes like:
$dataSource = 'dtsrc';
$database = 'tempdb'; #DB
$connection = New-Object System.Data.SqlClient.SqlConnection; #setting connection
$connection.ConnectionString = "Server=$dataSource;Database=$database;Integrated Security=True;";
$connection.Open();
$command = New-Object System.Data.SqlClient.SqlCommand;
$command.Connection = $connection;
$as = New-Object System.Data.SqlClient.SqlDataAdapter;
$as.SelectCommand = $command;
$filename = 'C:\DCT\XML\apc.xml'; #file to be loaded into sql server database table
$ds = New-Object System.Data.DataSet;
$ds.ReadXml($filename); #reading from the file -- line gives output InferSchema
$dt = New-Object System.Data.DataTable;
$dt.Columns.Add("StartTime",[datetime]);
$dt.Columns.Add("EndTime",[datetime]);
$dt.Columns.Add("Status",[int]);
$dt.Columns.Add("ProcessedTime",[datetime]);
$dt.Columns.Add("ScheduleId",[guid]);
$dt.Columns.Add("Model",[string]);
$dt.Columns.Add("ModelVersion",[string]);
$dt.Columns.Add("ModelVersionState",[string]);
$dt.Columns.Add("ModifiedTime",[datetime]);
$dt.Columns.Add("WriteBackLastRunTime",[datetime]);
$dt.Columns.Add("ModifiedBy",[string]);
$dt.Columns.Add("ModelType",[int]);
$dt.Columns.Add("IsTimeRange",[int]);#b
$dt.Columns.Add("WriteBackStatus",[int]);
$dt.Columns.Add("RetryWriteBack",[int]);#b
$dt.Columns.Add("NumOfRetry",[int]);
$dt.Columns.Add("FrequencyUnits",[int]);
$dt.Columns.Add("ScheduleType",[int]);
$dt.Columns.Add("CustomType",[int]);
$dt.Columns.Add("ShiftStartTime",[datetime]);
$dt.Columns.Add("StartWeekDay",[int]);
$dt.Columns.Add("EndWeekDay",[int]);
$dt.Columns.Add("WritebackProcessedTime",[datetime]);
$dt.Columns.Add("DiagStatus",[int]);
$dt.Columns.Add("AccountingPeriodCase_PK_ID",[guid]);
$dt = $ds.Tables[0];
$bcp = New-Object 'Data.SqlClient.SqlBulkCopy' $connection; #bulkcopy to the destination table.
$bcp.DestinationTableName = 'dbo.tempor';
#$bcp.ColumnMappings.Count;
$bcp.ColumnMappings.Clear();
$bcp.ColumnMappings.Add('StartTime','StartTime');
$bcp.ColumnMappings.Add('EndTime','EndTime');
$bcp.ColumnMappings.Add('Status','Status');
$bcp.ColumnMappings.Add('ProcessedTime','ProcessedTime');
$bcp.ColumnMappings.Add('ScheduleId','ScheduleId');
$bcp.ColumnMappings.Add('Model','Model');
$bcp.ColumnMappings.Add('ModelVersion','ModelVersion');
$bcp.ColumnMappings.Add('ModelVersionState','ModelVersionState');
$bcp.ColumnMappings.Add('ModifiedTime','ModifiedTime');
$bcp.ColumnMappings.Add('WriteBackLastRunTime','WriteBackLastRunTime');
$bcp.ColumnMappings.Add('ModifiedBy','ModifiedBy');
$bcp.ColumnMappings.Add('ModelType','ModelType');
$bcp.ColumnMappings.Add('IsTimeRange','IsTimeRange');
$bcp.ColumnMappings.Add('WriteBackStatus','WriteBackStatus');
$bcp.ColumnMappings.Add('RetryWriteBack','RetryWriteBack');
$bcp.ColumnMappings.Add('NumOfRetry','NumOfRetry');
$bcp.ColumnMappings.Add('FrequencyUnits','FrequencyUnits');
$bcp.ColumnMappings.Add('ScheduleType','ScheduleType');
#$bcp.ColumnMappings.Add('CustomType','CustomType');
#$bcp.ColumnMappings.Add('ShiftStartTime','ShiftStartTime');
#$bcp.ColumnMappings.Add('StartWeekDay','StartWeekDay');
#$bcp.ColumnMappings.Add('EndWeekDay','EndWeekDay');
$bcp.ColumnMappings.Add('WritebackProcessedTime','WritebackProcessedTime');
$bcp.ColumnMappings.Add('DiagStatus','DiagStatus');
$bcp.ColumnMappings.Add('AccountingPeriodCase_PK_ID','AccountingPeriodCase_PK_ID');
if ($connection.State -ne [Data.ConnectionState]::Open) {
'Connection to DB is not open.'
Exit
}
$bcp.WriteToServer($dt); #writing to server
$connection.Close();
The error I'm facing is:
Exception calling "WriteToServer" with "1" argument(s): "The given value of type String from the data source cannot be converted to type uniqueidentifier of the specified target column." At C:\Documents\DCT\SavedScripts\XMLtoDB\AccountingPeriodCases\sample.ps1:91 char:1
+ $bcp.WriteToServer($dt); #writing to server
+ ~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : InvalidOperationException
and the table I created has the columns with datatypes:
The thing is, I guess i need to convert the String that is in the datatable under the column ScheduleId and AccountingPeriodCase_PK_ID as they are not getting converted to uniqueidentifier.
I tried using
[System.Guid]::Parse($dt.Columns[4]);
[System.Guid]::Parse($dt.Columns[24]);
but the error repeats.
The xml content to be loaded under thr ScheduleId and AccountingPeriodCase_PK_ID looks like:
<ScheduleId>db6f3178-4702-456c-904b-2cd833b54efa</ScheduleId>
<AccountingPeriodCase_PK_ID>9544940d-8d1a-e711-80d3-0050569c2fb6</AccountingPeriodCase_PK_ID>
Could anyone help me resolve this issue?
Thank you
From a scenario, which I had faced earlier, it comes down to the source of your input.
The thing is if you are reading from an XML file, then you are bound to get an error, because while reading from a flat-file(txt, csv) or an XML, PowerShell will resolve the undefined types to String. The trick is to have some sort of control over the source. If your source is a PowerShell variable such as datatable or an array, then you can directly insert values from this variable to your destination table, since the original data types of the columns in a table are preserved.
This is the approach which I followed. In my case, the source was a result of the Invoke-Sqlcmd which preserved the original data types and hence made insertion error less.
Again as mentioned in the comment by OP, he was parsing values based on columns and not rows. This could also lead to an error. For example, there is one more way of inserting values in a table which is using the INSERT statement. Mind you that, the SQL Server INSERT statement inserts value on a row-level and not a column level. In such a scenario, parsing the values column wise may fail.

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

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.

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
}

How can I create a database table from a system.Data.datatable object?

Let us assume, that I have the following PowerShell code:
$connectionstring = "... some connection ..."
$sql = "Select * from Sometable"
$tablename= 'Copy_of_Sometable'
$cmd = New-Object system.Data.OleDb.OleDbCommand($sql,$connectionstring)
$da = New-Object system.Data.OleDb.OleDbDataAdapter($cmd)
$dt = New-Object system.Data.datatable
[void]$da.fill($dt)
I know, that I can derive a create table script from $dt and use it to create a new table with a given name cf. for example.
But is there a more direct way to create a new table with a supplied name (empty or already filled) from the datatable object?
I created a function called add-sqltable which will take a datatable as input and create a SQL Server table using SMO.
The function is available on PoshCode and Scripting Guy Script Repository. I blogged about it for a Scripting Guy guest post.
Using a System.Data.Datatable you pretty much have to use the method described.
However a more direct method is available if you're using SQL 2008. The Invoke-SqlCmd cmdlet will let you do as Ekkehard.Horner suggested SELECT * INTO newtable FROM oldtable. (sadly copy-item isn't supported)
Of course the system.Data.OleDb.OleDbCommand works to do the same thing.
Depending on the driver's capabilities, you may succeed with a single SQL statement like:
"SELECT * INTO newtable FROM oldtable"
With ODBC and Jet the tables may even be IN (keyword) different databases.
Because I really like the "INSERT/SELECT INTO IN" command(s) and never before used Powershell for database 'work':
(Edited) Powershell session:
PS C:\Documents and Settings\eh\My Documents\WindowsPowerShell>
$CS="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=<SOMEWHERE>\Nwind.mdb"
$SQL="SELECT * INTO CusCopy FROM Customers"
$cmd = New-Object system.Data.OleDb.OleDbCommand($SQL,$CS)
$cmd.Connection.Open()
$cmd.ExecuteNonQuery()
91
Please take the "91" as evidence that the command affected==inserted the 91
customers from the original table to the copy.
$SQL="SELECT * INTO [customer.csv] IN '' 'text;HDR=YES;Database=<SOMEWHERE>' FROM Customers"
$cmd = New-Object system.Data.OleDb.OleDbCommand($SQL,$CS)
$cmd.Connection.Open()
$cmd.ExecuteNonQuery()
91
dir
Directory: <SOMEWHERE>
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 14.02.2011 22:09 13395 customer.csv
-a--- 14.02.2011 22:01 2576384 Nwind.mdb
-a--- 14.02.2011 22:09 394 schema.ini
A table .csv and a schema.ini (entry) were newly/dutifully created.

Resources