I'm trying to read data from a SQL table in a powershell script. I can see the data in reader object, but when reading it using While (readerobject.read()){}
, it's not going inside the loop.
Powershell:
$cmd = $sqlConn.CreateCommand()
$cmd.CommandText ="SELECT * from user"
$movedUserDetails = $cmd.ExecuteReader()
while ($movedUserDetails.Read())
{
"[0] : " + $movedUserDetails.GetValue(0)
}
$movedUserDetails.Close()
The syntax is correct, but you're not doing anything with the value once inside the loop. You'll want to persist it somehow. Here's an example of running some basic SQL inside of powershell, with two different types of commands (Text/SP) and two different methods of execution (DataAdapter/DataReader). Either one of each should work fine.
# config
$svr = "serverName"
$db = "databaseName"
# connection
$sqlConnection = New-Object System.Data.SqlClient.SqlConnection
$sqlConnection.ConnectionString = "Server=$svr;Database=$db;Integrated Security=True"
$sqlConnection.Open()
# command A - text
$sqlCmd = New-Object System.Data.SqlClient.SqlCommand
$sqlCmd.Connection = $sqlConnection
$sqlCmd.CommandText = "SELECT name AS TABLE_NAME FROM sys.tables"
# command B - stored procedure
$sqlCmd = New-Object System.Data.SqlClient.SqlCommand
$sqlCmd.Connection = $sqlConnection
$sqlCmd.CommandText = "sys.sp_tables"
$sqlCmd.CommandType = [System.Data.CommandType]::StoredProcedure
$sqlCmd.Parameters.Add("@table_owner", "dbo")
# execute A - data reader
$reader = $sqlCmd.ExecuteReader()
$tables = @()
while ($reader.Read()) {
$tables += $reader["TABLE_NAME"]
}
$reader.Close()
# execute B - data adapter
$dataTable = New-Object System.Data.DataTable
$sqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$sqlAdapter.SelectCommand = $sqlCmd
$sqlAdapter.Fill($dataTable)
$sqlConnection.Close()
First off, if you're just doing some quick & dirty work with SQL Server or running file-based scripts, save yourself a ton of trouble and just use Invoke-Sqlcmd
. It's written and maintained by really smart people, so will likely serve you well.
If you're needing to run a lot of queries in a short period and could benefit from reusing your connection. Or want the safety/integrity of parameterized queries, SqlConnection
, SqlCommand
and SqlDataReader
make more sense.
Bearing in mind that PowerShell is a pipeline oriented construct, it behooves us to think in terms of the pipeline and leverage it effectively. That said, rather than dump all of the records into a DataTable
only to iterate them again downstream, why not leverage the dynamic nature PowerShell and pass a "callback" (i.e. [ScriptBlock]
) to perform some operation on each IDataRecord
as you iterate the IDataReader
.
The following function Invoke-SqlCommand
requires a: Connection String, Query and Callback which can be used for row projection/analysis etc.
Note: If a persisted
SqlConnection
is required, simply replace the$ConnectionString
parameter with$Connection
.
function Invoke-SqlCommand {
param(
[Parameter(Mandatory=$True,
ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$True,
HelpMessage="The connection string.")]
[string] $ConnectionString,
[Parameter(Mandatory=$True,
HelpMessage="The query to run.")]
[string] $Query,
[Parameter(Mandatory=$True,
HelpMessage="The work to perform against each IDataRecord.")]
[scriptblock] $ScriptBlock
)
$conn = New-Object System.Data.SqlClient.SqlConnection
$conn.ConnectionString = $ConnectionString
$cmd = $conn.CreateCommand()
$cmd.CommandText = $Query
try {
$conn.Open()
$rd = $cmd.ExecuteReader()
while($rd.Read()){
Write-Output (Invoke-Command $ScriptBlock -ArgumentList $rd)
}
}
finally {
$conn.Close()
}
}
Please do not use this in production without specifying a
catch {...}
, omitted here for brevity.
This format affords you the opportunity to perform some operation and projection against each IDataRecord
AND yield it into the pipeline for downstream processing.
$connectionString = "your connection string"
$query = "SELECT * FROM users"
Invoke-SqlCommand $connectionString $query {
param(
[Parameter(Mandatory=$True)]
[System.Data.SqlClient.SqlDataReader]$rd)
$obj = New-Object -TypeName PSObject -Property @{ user_id = $rd.GetValue($rd.GetOrdinal("geoname_id"))}
$obj.psobject.typenames.insert(0,'MyAwesome.Object')
Write-Output $obj
}
The use of New-Object
here is simply to provide us with consistent field ordering without having to rely on an ordered hash table and helps us identify our custom PSObject
more easily when running things like Get-Member
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With