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
.