Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ExecuteReader() in Powershell script

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() 
like image 322
Geeth Avatar asked May 21 '15 13:05

Geeth


2 Answers

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()
like image 192
KyleMit Avatar answered Sep 25 '22 11:09

KyleMit


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.

like image 33
pim Avatar answered Sep 22 '22 11:09

pim