public/Invoke-OledbSql.ps1


function Invoke-OledbSql {
    <#
    .SYNOPSIS
    Invokes SQL commands through an OleDB Connection.
 
    .DESCRIPTION
    When a Select is invoked, returns System.Management.Automation.PSCustomObject
    objects. When an Update, Insert or Delete is invoked, it returns the affected
    count.
 
    .PARAMETER SQL
    SQL command to be invoked.
 
    .PARAMETER Connection
    Any valid OleDB connection string or a System.Data.OleDb.OleDbConnection object.
    Strings are passed to New-OledbConnection to be resolved.
    Defaults to using the variable $Conn if set to a SQLConnection object.
 
    .PARAMETER Timeout
    Sets the command timeout in seconds.
 
    .OUTPUTS
    System.Management.Automation.PSCustomObject
    A custom type can be set with TypeName parameter.
 
    .EXAMPLE
    Invoke-OledbSql 'select 1 as Ping' 'Provider=sqloledb; Data Source=ServerName; Initial Catalog=DatabaseName;Integrated Security=SSPI;'
 
    Tests SQL connectivity.
 
    .EXAMPLE
    $cs = 'Provider=sqloledb; Data Source=ServerName; Initial Catalog=DatabaseName;Integrated Security=SSPI;'
    $c = New-OledbConnection $cs
    Get-Content 'Update_statements_one_per_line.txt' | WHERE { (Invoke-OledbSql -Sql $_ -Connection $c -KeepOpen) -eq 0 } | Set-Content 'failed.txt' -PassThru
    $c.Close()
 
 
    .NOTES
    Though this will handle more than one SQL statement in a single call, it is not recommended.
 
    SEE ALSO
        about_OledbSql
 
    #>

        [CmdletBinding()]
        [OutputType([PsCustomObject])]
        param(
            [Parameter(Mandatory=$True, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true,
                HelpMessage='Enter SQL command'
            )]
            [string[]]$Sql,

            [Parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true,
                HelpMessage='Enter a type name for the object returned
            '
)]
            [string[]]$TypeName,

            [Parameter(HelpMessage='Enter an OLEDB connection string')]
            $Connection,

            [int]$Timeout,

            [switch]$KeepOpen
        )


        begin {
            # Will use the variable $Conn if it is set in the current context.
            if ((-not $Connection) -and ($Conn -is [System.Data.OleDb.OleDbConnection])) {$Connection = $Conn}

            if ($Connection -isnot [System.Data.OleDb.OleDbConnection]) {
                $Connection = New-OledbConnection -Connection:$Connection
            }

            if ($Connection -isnot [System.Data.OleDb.OleDbConnection]) {
                throw "No OleDB connection or string provided."
            }

            function Uniqueify ([string[]]$Columns) {
                <#
                .SYNOPSIS
                    Helper funciton that appends incrementing numbers to any duplicate
                    values in an incoming array.
                #>

                $rtn = @()
                for ($i = 0; $i -lt $Columns.Length; $i++) {
                    $cnt = 0
                    for ($j = 0; $j -lt $Columns.Length -and $j -lt $i; $j++) {
                        if ($Columns[$i] -eq $Columns[$j]) { $cnt++ }
                    }
                    if ($cnt -gt 0) {
                        $rtn += ('{0}{1}' -f $Columns[$i], $cnt)
                    } else {
                        $rtn += $Columns[$i]
                    }
                }
                return $rtn
            }

        }


        process {
            # Handles an array or piped list of sql statements.
            Foreach ($s in $SQL) {
                $command = New-Object System.Data.OleDb.OleDbCommand $s, $Connection
                if ($Timeout) {$command.CommandTimeOut = $Timeout}
                try {
                    # If the connection started open, keep it open.
                    if ($Connection.State -eq 'Open') {
                        $KeepOpen = $true
                    } else {
                        $Connection.Open()
                    }

                    Write-Verbose ('SQL: {0}' -f $s)
                    [System.Data.OleDB.OleDbDataReader]$reader = $command.ExecuteReader()

                    # while $reader.NextResult(), handles multiple queries/results in a single SQL request.
                    do {
                        # For scalar values, return the records affected.
                        if ( -not ($reader.HasRows)) { return $reader.RecordsAffected }

                        # Create an object template, with the uniqueified column names
                        # as properties and a custom PSTypename if specified.
                        #TODO: Look at maybe using $reader.GetSchemaTable()
                        $columns = 0 .. ($reader.VisibleFieldCount -1) | ForEach-Object {$reader.GetName($_)}
                        $columns = @(Uniqueify $columns)
                        $columnHash = [ordered]@{}
                        if ($TypeName) { $columnHash['Pstypename'] = $TypeName }
                        $columns | ForEach-Object { $columnHash[$_] = $null }
                        $objectTemplate = [pscustomobject]$columnHash

                        while ($reader.Read()) {
                            $o = $objectTemplate.PsObject.Copy()
                            for ($i=0; $i -lt $columns.Count; $i++) {
                                $column = $columns[$i]
                                Write-Debug ('{0} [{1}] ({2}) = [{3}]' -f $i, $column, $reader.GetFieldType($i), $reader.GetValue($i))
                                $o.$column = if ($reader.IsDBNull($i)) { $null } else { $reader.GetValue($i) }
                            }
                            Write-Output $o
                        }
                    } while ( $reader.NextResult() )

                    if ($reader -and (-not $reader.IsClosed)) {$reader.Close()}
                }
                catch {
                    Write-Verbose ('Message: {0}' -f $_.exception.InnerException.message)
                    throw $_
                }
                finally {
                    $reader = $null
                    $command = $null
                    if (-not $KeepOpen) {
                        $Connection.Close()
                        $Connection = $null
                    }
                }
            }
        }
    }