Error executing a stored procedure in Powershell - sql

I have a powershell script that accepts user input that I plan to use to execute a stored procedure. The sproc accepts multiple parameters to customize the data that is returned. However, when I run this powershell code, I get the following error:
Cannot index into a null array.
At C:\Report.ps1:119 char:42
+ foreach ($Row in $global:DataSet.Tables[ <<<< 0].Rows)
+ CategoryInfo : InvalidOperation: (0:Int32) [], RuntimeException
+ FullyQualifiedErrorId : NullArray
This is the powershell code I'm using to execute the call to the stored procedure.
# Create Shared Helper Objects
$nl = [Environment]::NewLine
function msg
{
param($message,$fgcolor="white",$bgcolor="black")
$output = Write-Host $message -ForegroundColor $fgcolor -BackgroundColor $bgcolor
write-output $output
}
Set-Location "C:\"
$month = Read-Host "Enter Report Month"
$year = Read-Host "Enter Report Year"
msg "$nl Choose a product" "White"
$prompt = ' [D] Desktop [M] Mobile'
switch (Read-Host $prompt) {
"D" {$product = "Desktop"; break}
"M" {$product = "Mobile"; break}
}
msg "$nl Select a Make" "White"
$prompt = ' [F] First [L] Last '
switch (Read-Host $prompt) {
"F" {$SiteMake = "First"; break}
"L" {$SiteMake = "Last"; break}
}
msg "$nl Select a Product" "White"
$prompt = ' [E] Extra [B] Basic'
switch (Read-Host $prompt) {
"E" {$SitePackage = "Extra"; break}
"B" {$SitePackage = "Basic"; break}
}
# Create Database Connection Function
function ConnectSproc
{
param ($SQLServer, $SQLDBName, $SqlQuery)
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $SQLDBName; Integrated Security = True"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $SqlQuery
$SqlCmd.CommandType = [System.Data.CommandType]::StoredProcedure
# Report Month
$SqlCmd.Parameters.Add("#ReportMonth",[system.data.SqlDbType]::VarChar) | out-Null
$SqlCmd.Parameters["#ReportMonth"].Direction = [system.data.ParameterDirection]::Input
$SqlCmd.Parameters["#ReportMonth"].value = $month
# Report Year
$SqlCmd.Parameters.Add("#ReportYear",[system.data.SqlDbType]::VarChar) | out-Null
$SqlCmd.Parameters["#ReportYear"].Direction = [system.data.ParameterDirection]::Input
$SqlCmd.Parameters["#ReportYear"].value = $year
# Product
$SqlCmd.Parameters.Add("#Product",[system.data.SqlDbType]::VarChar) | out-Null
$SqlCmd.Parameters["#Product"].Direction = [system.data.ParameterDirection]::Input
$SqlCmd.Parameters["#Product"].value = $product
# Site Make
$SqlCmd.Parameters.Add("#SiteMake",[system.data.SqlDbType]::VarChar) | out-Null
$SqlCmd.Parameters["#SiteMake"].Direction = [system.data.ParameterDirection]::Input
$SqlCmd.Parameters["#SiteMake"].value = $SiteMake
# Site Package
$SqlCmd.Parameters.Add("#SitePackage",[system.data.SqlDbType]::VarChar) | out-Null
$SqlCmd.Parameters["#SitePackage"].Direction = [system.data.ParameterDirection]::Input
$SqlCmd.Parameters["#SitePackage"].value = $SitePackage
$SqlCmd.Connection = $SqlConnection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.Fill($DataSet) | Out-Null
$SqlConnection.Close()
$DataSet.Tables[0]
}
ConnectSproc "servername" "databasename" "mysproc"
I'm at a loss. Any help would be appreciated!
Update: it actually looks like the procedure isn't even executing correctly. That would probably explain the error that I'm getting. Now I'm not sure why the sproc isn't executing.

I figured it out. It ended up being a scoping issue with the $DataSet variable being created inside the function and not prefixed with "$global:"
To fix it, I changed
$DataSet = New-Object System.Data.DataSet to $global:DataSet = New-Object System.Data.DataSet and referenced it as $global:DataSet.Tables[0] outside of the function.

Maybe the call to Fill() failed. Temporarily comment out the | Out_Null part of $SqlAdapter.Fill($DataSet) | Out-Null, and see if that sheds any light.

Related

Powershell connect to several INSTANCES of SQL Server

I need to create a script that do:
Get services where name are like MSSQL$* (in order to get all SQL Server instances services)
If the service are running, goes to check name
If the name of the service is like MSSQL$MICROSOFT##WID, throw a System.Exception
If not, try to connect to every instances running on the server, if one of them are inaccessible, throw a System.Exception
My current script:
$ErrorActionPreference = "Stop"
try {
$running = Get-Service | where {($_.Status -eq "Running") -and $_.Name -like "MSSQL*"}
$services = (Get-Service -Name 'MSSQL*')
$running.Status | Foreach-object {
if ($running.status -contains 'Running'){
if ($_.Name -ne 'MSSQL$MICROSOFT##WID'){
$instances = (get-itemproperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server').InstalledInstances
foreach ($instance in $instances){
$server = "localhost"
$database = "master"
$sql = "select name from sys.databases"
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server=$server;Database=$database;Integrated Security=SSPI"
$SqlConnection.Open()
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $sql
$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]
$return = ($DataSet.Tables[0])
if ($return -ne $null){return 1} else {[System.Exception]}
}}
} else {[System.Exception]}
}}catch{[System.Exception]}
finally{$ErrorActionPreference = "Stop"}
Check status of services with MSSQL*
If status of services is running, check name of service, if the name of service is equal to MSSQL$MICROSOFT##WID throw a System.Exception, if not, list all instances and then, foreach instance in instances, try to connect
If the connection is successful, return 1, if not, throw System.Exception
My questions are: with this script, doesn't matter how many instances have already installed in the server, only check the default... how can I make try the connection for every instance that is running?
I need to check the connection to each instance running on the server, if there is someone stopped, or inaccessible must throw a System.Exception.
And too, is not possible use dbatools, and invokesql...
Somebody knows how to make the connection to every instances running on the server, if one of them are inaccessible, throw a System.Exception?
------------------- UPDATED ------------------------
this string succesfully connect with an example of instance
$SqlConnection.ConnectionString = "Server='localhost\NEWINSTANCE';Database=master;Integrated Security=SSPI"
I just try to send the server trough variable, but give me error...
if ($_.Name -ne 'MSSQL$MICROSOFT##WID'){
$SQLinstancesold = dir "SQLSERVER:\SQL\(local)"
$SQLinstances = $SQLinstancesold | Format-table -HideTableHeaders
foreach ($SQLinstance in $SQLInstances) {
$server = 'localhost\'+$SQLinstance;
$database = "master"
$sql = "select name from sys.databases"
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server='localhost\NEWINSTANCE';Database=master;Integrated Security=SSPI"
$SqlConnection.Open()
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $sql
$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]
$return = ($DataSet.Tables[0])
if ($return -ne $null){return 1} else {echo "else of return 1"}
}}
I dont know how can I pass the variable to connectionstring in order to do the connection for each instance
--------------------------- UPDATED 2 -----------------------
I just modified the script:
Import-Module SQLPS -DisableNameChecking
$ErrorActionPreference = "Stop"
try {
$running = Get-Service | where {($_.Status -eq "Running") -and $_.Name -like "MSSQL*"}
$services = (Get-Service -Name 'MSSQL*')
$running.Status | Foreach-object {
if ($running.status -contains 'Running'){
if ($_.Name -ne 'MSSQL$MICROSOFT##WID'){
$SQLinstancesold = dir "SQLSERVER:\SQL\(local)"
$SQLinstances = $SQLinstancesold | Format-table -HideTableHeaders
$server = 'localhost\'+$SQLinstance;
foreach ($SQLinstance in $SQLInstances) {
$server = 'localhost\'+$SQLinstance;
$return = Invoke-Sqlcmd -ServerInstance $server -Database master -Query "select name from sys.databases"
if ($return -ne $null){return 1} else {echo "else of return 1"}
}}
} else {echo "here"}
}
}
catch{echo "hello?"}
finally{$ErrorActionPreference = "Stop"}
This way, list all instances, and for each instance declare variable server and try to invoke-sqlcmd
But, when I try to invoke $server on $return variable, give me an error, if I write for example the name of my instance NEWINSTANCE, return 1, that is correct
How can I put variable $server in order to get all instances on invokesqlcmd?

Powershell run stored procedure returns rows aswell as the input parameters

I have a stored procedure which takes a date as a paramter and returns rows, in the return from the function in powershell an object array is returned containing the parameters and the rows.
I know I can get around this by referencing the rows and working with them but I want to know why it returns the parameter. Can anyone shed some light please?
function Invoke-SQL($dataSource, $database, $sqlCommand, $tradeDate) {
$result = New-Object System.Data.DataTable
$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)
$command.CommandType = [System.Data.CommandType]::StoredProcedure
$parameter = New-Object System.Data.SqlClient.SqlParameter ("#TradeDateParam", $tradeDate)
$command.Parameters.Add($parameter);
$adapter = New-Object System.Data.sqlclient.sqlDataAdapter $command
$connection.Open()
$adapter.Fill($result) | Out-Null
$connection.Close()
return $result
}
$resultsDataTable = New-Object system.Data.DataTable
$resultsDataTable = Invoke-SQL "server" "db" "sproc" "dateparam"
ALTER PROCEDURE [dbo].[spCheckTradeActivityBetweenTradeDates]
#TradeDateParam date = null
AS
DECLARE #TradeDate date = #TradeDateParam;
SELECT 'HEllO' AS hello
return 0
Image of returned object array
So after some reading on Michael Sorens answer it appears functions in Powershell can sometimes return more output than the value you are returning. I used an Out-Null pipe to suppress the output of the paramter as you can see below, it is worth noting that the Out-Null pipe is not the most efficient way to handle this.
Whilst the pipe does work I still do not know why it added a parameter to a SQL command generates output.
function Invoke-SQL($dataSource, $database, $sqlCommand, $tradeDate) {
$result = New-Object System.Data.DataSet
$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)
$command.CommandType = [System.Data.CommandType]::StoredProcedure
# $parameter = New-Object System.Data.SqlClient.SqlParameter ("#TradeDateParam", $tradeDate)
$command.Parameters.AddWithValue("#TradeDateParam", $tradeDate) | Out-Null # <--Added this pipe to suppress parameter output
$adapter = New-Object System.Data.sqlclient.sqlDataAdapter $command
$connection.Open()
$adapter.Fill($result) | Out-Null
$connection.Close()
return $result.Tables
}

PowerShell Function multiple SQL queries output as CSV

I'm having problems getting PowerShell to run multiple SQL queries and export the results as CSV.
I'm trying to accomplish this using a Function but the problem occurs in the Process block when I expect two queries to run and output two CSV files.
I tried creating one function to run the query and a second function to create the CSV files but that didn't even run the SQL queries. I'm doing this without SQL being installed where this powershell script is executed from. -thanks!
Function Run-Query {
param([string[]]$queries,[string[]]$sheetnames)
Begin{
$SQLServer = 'ServerName'
$Database = 'DataBase'
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $Database; Integrated Security = True"
}#End Begin
Process{
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.CommandText = $queries
$SqlCmd.Connection = $SqlConnection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.Fill($DataSet)
$DataSet.Tables[0] | Export-Csv -NoTypeInformation -Path "C:\Scripts\$sheetnames.csv"
}#End Process
End{
$SqlConnection.Close()
}
}#End function run-query.
$queries = #()
$queries += #'
Select * from something
'#
$queries += #'
Select * from something2
'#
$sheetnames = #()
$sheetnames += 'Cert'
$sheetnames += 'Prod'
Run-Query -queries $queries
I'm not sure if SQL processes multiple queries separately so while you might be passing two different queries the SQL server might be interpreting them as one query (Not 100% sure this is happening, just a guess really)
You've put your queries in an array so we can easily loop through the array, run each query by itself and put the results into a CSV.
Here's how i'd modify your code to start with:
Function Run-Query
{
param([string[]]$queries,[string[]]$sheetnames)
Begin
{
$SQLServer = 'ServerName'
$Database = 'DataBase'
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $Database; Integrated Security = True"
}#End Begin
Process
{
# Loop through each query
For($i = 0; $i -lt $queries.count; $i++)
{
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
# Use the current index ($i) to get the query
$SqlCmd.CommandText = $queries[$i]
$SqlCmd.Connection = $SqlConnection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $SqlCmd
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.Fill($DataSet)
# Use the current index ($i) to get the sheetname for the CSV
$DataSet.Tables[0] | Export-Csv -NoTypeInformation -Path "C:\Scripts\$($sheetnames[$i]).csv"
}
}#End Process
End
{
$SqlConnection.Close()
}
}#End function run-query.
$queries = #()
$queries += #'
Select * from something
'#
$queries += #'
Select * from something2
'#
$sheetnames = #()
$sheetnames += 'Cert'
$sheetnames += 'Prod'
Run-Query -queries $queries -sheetnames $sheetnames

Powershell array correlation to SQL table dataset from powershell

First off, I'm new to stack. I have referenced stack many times in the past, but recently I have been stuck on this issue for quite sometime. So here goes.
My goal:
I am attempting to correlate an array output from VMware that matches a custom value on each VM machine. ( an asset ID ) to a value ( ID Key ) on a microsoft SQL 2000 server.
As such, since this server is pre 2005 I am unable to use the invoke-sqlcmd powershell command. I have to utilize the full SQL connection string and command structure to return a value out of this database. This sql statement and script works fine on its own. Meaning that the sql portion of this script, functioning on its own will pull results out of the database with a manual tag number put in place of my variable "$etag". I'm fairly new to powershell, and sql use from powershell.
So here is my script with names of the protected taken out.
#========================================================================
# Created on: 12/4/2013 2:01 PM
# Created by: Shaun Belcher
# Filename:
#========================================================================
function get-inventory
{
Add-PSSnapin VMware.VimAutomation.Core
$date=get-date
$vcenterserver = #("srv-1","srv-2","srv-3")
Connect-VIServer -server $vcenterserver
$toAddr="user#domain.com"
$fromAddr="user#domain.com"
$smtpsrv="mail.domain.com"
#Variables
$mdesks=#()
$sqlServer = "serverdb"
$sqlDBNAME = "instance"
$sqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection
$DataSet = New-Object System.Data.DataSet
$sqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.connection = $sqlConnection
$sqlAdapter.SelectCommand = $sqlCmd
#db Connection
$sqlConnection.ConnectionString = "Server = $sqlServer; Database = $sqlDBname; Integrated Security=True;"
$SqlCmd.connection = $SqlConnection
$SqlCmd.commandtext = $sqlQuery
$sqlAdapter.SelectCommand = $sqlCmd
$sqlQuery += "SELECT INVHARDW_PropTag as proptag, invhardw_clientID as ClientID, invhardw_notes as Notes FROM INV_Hardware where invhardw_proptag = '$etag';"
$SqlCmd.commandtext = $sqlQuery
$sqlAdapter.SelectCommand = $sqlCmd
$sqlAdapter.Fill($DataSet)
$DataSet.Tables[0]
$sqlConnection.Close()
$mdesks = #($DataSet.Tables[0] | select propTag, ClientID, Notes)
$virtuals= #(Get-VM | select Name,vmhost,memoryMB,#{N="Datastore";E={[string]::Join(',',(Get-Datastore -Id $_.DatastoreIdList | Select -ExpandProperty Name))}})
$etags = #(Get-vm | Get-Annotation |select value,#{N="mDeskNote";E={[string]::Join(',',($mdesk | Where-Object {$mdesks.propTag = $_;}))}},#{N="mDeskClientID";E={[string]::Join(',',($mdesk | Where-Object {$mdesks.propTag = $_;}))}})
if($virtuals -ne $null){
$body = #("
<center><table border=1 width=50 % cellspacing=0 cellpadding=8 bgcolor=Black cols=3>
<tr bgcolor=White><td>Virtual Machine</td><td>Host Machine</td><td>Memory Allocated</td><td>DatastoreList</td><td>Asset Tag</td><td>App Note</td><td>App Client ID</td></tr>")
$i = 0
do {
#if($i % 2){$body += "<tr bgcolor=#D2CFCF><td>$($virtuals[$i].Name)</td></tr>";$i++}
#else {$body += "<tr bgcolor=#EFEFEF><td>$($virtuals[$i].Name)</td></tr>";$i++}
if($i % 2){$body += "<tr bgcolor=#D2CFCF><td>$($virtuals[$i].Name)</td><td>$($virtuals[$i].VMHost)</td><td>$($virtuals[$i].MemorymB)</td><td>$($virtuals[$i].datastore)</td><td>$($etags[$i].value)</td><td>$mdesks[$i].notes</td><td>$mdesks[$i].ClientID</td></tr>";$i++}
else {$body += "<tr bgcolor=#EFEFEF><td>$($virtuals[$i].Name)</td><td>$($virtuals[$i].VMHost)</td><td>$($virtuals[$i].memorymb)</td><td>$($virtuals[$i].datastore)</td><td>$($etags[$i].value)</td><td>$mdesks[$i].notes</td><td>$mdesks[$i].ClientID</td></tr>";$i++}
}
while ($virtuals[$i] -ne $null)
$body += "</table></center>"
# Send email.
if($attachmentPref){
$virtuals | Export-CSV "Inventory $($date.month)-$($date.day)-$($date.year).csv"
Send-MailMessage -To "$toAddr" -From "$fromAddr" -Subject "$vcenterserver Inventory = $countvms" -Body "$body" -Attachments "Inventory $($date.month)-$($date.day)-$($date.year).csv" -SmtpServer "$smtpsrv" -BodyAsHtml
Remove-Item "Inventory $($date.month)-$($date.day)-$($date.year).csv"
}
Else{
Send-MailMessage -To "$toAddr" -From "$fromAddr" -Subject "Inventory $vcenterserver = $countvms" -Body "$body" -SmtpServer "$smtpsrv" -BodyAsHtml
}
}
Disconnect-VIServer -Server $vcenterserver -Confirm:$false exit
get-inventory
This returns the information and sends it in an email with columns and rows of the information. Again, these are two working scripts that just do not return the result that is sought after.

How to retrieve OUTPUT statement when calling stored proc through Powershell

I am running the following script in powershell, however I don't seem to be able to retrieve any PRINT statements or error messages? How do I capture all outputs within the powershell session please?
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlConnection.ConnectionString = "Server=$sql_server;Database=$sql_db;user ID=$sql_usr;password=$sql_pwd"
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlCmd.Connection = $SqlConnection
$SqlCmd.CommandText = "$storedProc"
$SqlCmd.CommandType = [System.Data.CommandType]::StoredProcedure
$SqlCmd.Parameters.Add("#COBDate", "$dateVariable")
$handler = [System.Data.SqlClient.SqlInfoMessageEventHandler] {param($sender, $event) Write-Host $event.Message };
$SqlConnection.add_InfoMessage($handler);
$SqlConnection.FireInfoMessageEventOnUserErrors = $true;
$SqlConnection.Open()
$SqlCmd.ExecuteNonQuery()
$SqlCmd.Parameters.value
$SqlConnection.Close()
The way I've implemented Eventhandler is as follows:
#Method 1 use hidden method
$Sqlconnection.FireInfoMessageEventOnUserErrors=$true
#...
$handler = [System.Data.SqlClient.SqlInfoMessageEventHandler] {Write-Host "$($_)"}
$Sqlconnection.add_InfoMessage($handler)
#OR Method 2 use Register-ObjecEvent
Register-ObjectEvent -InputObject $SqlConnection-EventName InfoMessage -Action { Write-Host " $($Event.SourceEventArgs)" } -SupportEvent
#...
$SqlConnection.Open()