Get-SQL.ps1

if (-not $Global:DbSessions ) { $Global:DbSessions = @{}  }

Function Get-SQL {
<#
    .Synopsis
        Queries an ODBC or SQL Server database
    .Description
        Get-SQL queries SQL databases using either ODBC or the native SQL-Server client.
        Connections to databases are kept open and reused to avoid the need to make connections for every query,
        but the first time the command is run it needs a connection string; this come from $DefaultDBConnection.
        (e.g. set in your Profile) rather than being passed as a parameter: if it is set you can run
        sql "Select * From Customers"
        without any other setup; PowerShell will assume "sql" means "GET-SQL" if there is no other command named SQL.
         
        Get-SQL -Connection allows a connection to be specified explictly; -MsSQLserver forces the use of the native SQL Server driver,
        and -Excel or -Access allow a file name to be used without converting it into an ODBC connection string.
         
        The global variable $DbSessions holds objects for each open connection until Get-SQL is run with -Close.
        Get-Sql will also build simple queries; for example
        Get-SQL -Table Authors
        Will run the "Select * from Authors" and a condition can be specified with
        Get-SQL -Table Authors -Where Name -like "*Smith"
        Get-SQL -ShowTables will show the available tables, and Get-SQL -Describe Authors will show the design of the table.
    .Parameter SQL
        A SQL statement. If other parameters (such as -Table, or -Where) are provided, it becomes the end of the SQL statement.
        If no statement is provided, or none can be built from the other parameters, Get-SQL returns information about the connection.
    .Parameter Connection
        An ODBC connection string or an Access or Excel file name or the name of a SQL Server
        It can be in the form "DSN=LocallyDefinedDSNName;" or
        "Driver={MySQL ODBC 5.1 Driver};SERVER=192.168.1.234;PORT=3306;DATABASE=xstreamline;UID=johnDoe;PWD=password;"
        A default connection string can be set in in $DBConnection so that you can just run "Get-SQL " «SQL Statement» ".
    .Parameter Excel
        Specifies that the string in -Connection is an Excel file path to be converted into an ODBC connection string.
    .Parameter Access
        Specifies that the string in -Connection is an Access file path to be converted into an ODBC connection string.
    .Parameter MsSQLserver
        Specifies the SQL Native client should be used instead of ODBC and string in -Connection is a SQL one.
    .Parameter Session
        Allows a database connection to be Identified by name: this sets the name used in the global variable $DBSessions.
        In addition an alias is added: for example, if the session is named "F1" you can use the command F1 in place of Get-SQL -Session F1
    .Parameter ForceNew
        If specified, makes a new connection for the default or named session.
        If a connection is already established, -ForceNew is required to change the connection string.
    .Parameter ChangeDB
        For SQL server and ODBC sources which support it (like MySQL) switches to a different database at the same server.
    .Parameter Close
        Closes a database connection.
    .Parameter Table
        Specifies a table to select or delete from or to update.
    .Parameter Where
        If specified, applies a SQL WHERE condition to the selected table. -Where specifies the field and the text in -SQL supplies the condition.
    .Parameter GT
        Used with -Where specifies the > operator should be used, with the operand for the condition found in -SQL.
    .Parameter GE
        Used with -Where specifies the >= operator should be used, with the operand for the condition found in -SQL.
    .Parameter EQ
        Used with -Where specifies the = operator should be used, with the operand for the condition found in -SQL.
    .Parameter NE
        Used with -Where specifies the <> operator should be used, with the operand for the condition found in -SQL.
    .Parameter LE
        Used with -Where specifies the <= operator should be used, with the operand for the condition found in -SQL.
    .Parameter LT
        Used with -Where specifies the < operator should be used, with the operand for the condition found in -SQL.
    .Parameter Like
        Used with -Where specifies the Like operator should be used, with the operand for the condition found in -SQL. "*" in -SQL will be replaced with "%".
    .Parameter NotLike
        Used with -Where specifies the Not Like operator should be used, with the operand for the condition found in -SQL. "*" in -SQL will be replaced with "%".
    .Parameter Select
        If Select is omitted, -Table TableName will result in "SELECT * FROM TableName";
        Select specifies field-names (or other text) to use in place of "*".
    .Parameter Distinct
        Specifies that "SELECT DISTINCT ..." should be used in place of "SELECT ...".
    .Parameter OrderBy
        Specifies fields to be used in a SQL ORDER BY clause added at the end of the query.
    .Parameter Delete
        If specified, changes the query from a SELECT to a DELETE. This allows a query to be tested as a SELECT before adding -Delete to the command.
        -Delete requires a WHERE clause and not all ODBC drivers support deletion.
    .Parameter Set
        If specified, changes the query from a Select to a Update -Set Specifies the field(s) to be updated.
        -Set requires a WHERE clause.
    .Parameter Values
        If -Set is specified, -Values contains the new value(s) for the fields being updated.
    .Parameter Insert
        Specifies a table to insert into. The SQL parameter should contain a hash table or PSObject which holding the data to be inserted.
    .Parameter DateFormat
        Allows the format applied to Dates to be inserted to be changed if a service requires does not follow standard conventions.
    .Parameter GridView
        If specified, sends the output to gridview instead of the PowerShell console.
    .Parameter GroupBy
        If specified, adds a group by clause to a select query; in this case the SELECT clause needs to contain fields suitable for grouping.
    .Parameter Describe
        Returns a description of the specified table - note that some ODBC providers don't support this.
    .Parameter ShowTables
        If specified, returns a list of tables in the current database - note that some ODBC providers don't support this.
    .Parameter Paste
        If specified, takes an SQL statement from the clipboard.
        Line breaks and any text before SELECT , UPDATE or DELETE will be removed
    .Parameter Quiet
        If specified, surpresses printing of the console message saying how many rows were returned
    .Example
    Get-SQL -MsSQLserver -Connection "server=lync3\rtclocal;database=rtcdyn; trusted_connection=true;" -Session Lync
    Creates a new session named "LYNC" to the rtcdyn database on the Rtclocal SQL instance on server Lync
    .Example
    Get-SQL -Session LR -Connection "DSN=LR" -Quiet -SQL $SQL
    Runs the SQL in $SQL - if the Session LR already exists it will be used, otherwise it will be created to the ODBC source "LR"
    Note that a script should always name a its session(s), something else may already have set the defualt session
    .Example
    Get-Sql -showtables *dataitem
    Gives a list of tables on the default connection that end wtih "dataitem"
    .Example
    Get-SQL -Session f1 -Excel -Connection C:\Users\James\OneDrive\Public\F1\f1Results.xlsx -showtables
    Creates a new connection named F1 to the an Excel file, and shows the tables available.
    .Example
    f1 -Insert "[RACES]" @{RaceName = $raceName, RaceDate = $racedate.ToString("yyyy-MM-dd") }
    Uses the automatically created alias "f1" which was created in the previous example to insert a row of data into the "Races" Table
    .Example
    Get-SQL -Session F1 -Table "[races]" -Set "[poleDriver]" -Values $PoleDriver -SQL "WHERE RaceDate = $rd" -Confirm:$false
    Updates the races table in the "F1" session, setting the value in the column "PoleDriver" to the contents of
    the variable $PoleDriver, in those rows where the RaceDate = $RD. This time the session is explictly specified
    (using aliases is OK at the command line but not in scripts especially if the alias is created by a command run in the script)
    Changes normally prompt the user to confirm but here -Confirm:$false prevents it
    .Example
    "CREATE USER 'johndoe' IDENTIFIED BY 'password'" , "GRANT ALL PRIVILEGES ON *.* TO 'johndoe'@'%' WITH grant option" | Get-SQL
    Pipes two commands into the default connection, giving a new mySql user full access to all tables in all databases
    .Example
    Get-Sql -paste -gridview
    Runs the query currently in the windows clipboard against the default existing and outputs to the Gridview
    .Example
    SQL -table catalog_dataitem -select dataStatus -distinct -orderBy dataStatus -gridView
    Builds the query " SELECT DISTINCT dataStatus FROM catalog_dataitem ORDER BY dataStatus",
    runs it against the default existing connection and displays the results in a grid.
    #>

 [CmdletBinding(DefaultParameterSetName='Describe',SupportsShouldProcess=$true,ConfirmImpact="High")]
 Param   ([parameter(Position=0, ValueFromPipeLine=$true)]
           $SQL,
           [parameter(Position=1)][ValidateNotNullOrEmpty()] 
           [string]$Connection  = $global:DefaultDBConnection ,
           [ValidateNotNullOrEmpty()]
           [string]$Session = "Default",
           [parameter(Position=2)]
           [alias('Use')]
           [string]$ChangeDB,
           [alias('Renew')]
           [switch]$ForceNew  , 
           [parameter(ParameterSetName="Paste")]
           [parameter(ParameterSetName="Describe")]
           [parameter(ParameterSetName="Select")]
           [parameter(ParameterSetName="SelectWhere")]
           [alias('g')][switch]$GridView,
           [parameter(ParameterSetName="Describe")]
           [alias('d')][string]$Describe,
           [parameter(ParameterSetName="ShowTables" , Mandatory=$true)]
           [switch]$ShowTables,
           [parameter(ParameterSetName="Paste"      , Mandatory=$true)]
           [switch]$Paste,
           [parameter(ParameterSetName="UpdateWhere", Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
           [parameter(ParameterSetName="DeleteWhere", Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
           [parameter(ParameterSetName="SelectWhere", Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
           [parameter(ParameterSetName="Update"     , Mandatory=$false)]
           [parameter(ParameterSetName="Delete"     , Mandatory=$false)]
           [parameter(ParameterSetName="Select"     , Mandatory=$false)]
           [alias('from','update')][string]$Table,
#region Parameters for queries with a WHERE clause
           [parameter(ParameterSetName="UpdateWhere", Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
           [parameter(ParameterSetName="DeleteWhere", Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
           [parameter(ParameterSetName="SelectWhere", Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
           [string]$Where,
           [parameter(ParameterSetName="UpdateWhere")]
           [parameter(ParameterSetName="DeleteWhere")]
           [parameter(ParameterSetName="SelectWhere")]
           [switch]$GT,
           [parameter(ParameterSetName="UpdateWhere")]
           [parameter(ParameterSetName="DeleteWhere")]
           [parameter(ParameterSetName="SelectWhere")]
           [switch]$GE,
           [parameter(ParameterSetName="UpdateWhere")]
           [parameter(ParameterSetName="DeleteWhere")]
           [parameter(ParameterSetName="SelectWhere")]
           [switch]$EQ,
           [parameter(ParameterSetName="UpdateWhere")]
           [parameter(ParameterSetName="DeleteWhere")]
           [parameter(ParameterSetName="SelectWhere")]
           [switch]$NE,
           [parameter(ParameterSetName="UpdateWhere")]
           [parameter(ParameterSetName="DeleteWhere")]
           [parameter(ParameterSetName="SelectWhere")]
           [switch]$LE,
           [parameter(ParameterSetName="UpdateWhere")]
           [parameter(ParameterSetName="DeleteWhere")]
           [parameter(ParameterSetName="SelectWhere")]
           [switch]$LT,
           [parameter(ParameterSetName="UpdateWhere")]
           [parameter(ParameterSetName="DeleteWhere")]
           [parameter(ParameterSetName="SelectWhere")]
           [switch]$Like,
           [parameter(ParameterSetName="UpdateWhere")]
           [parameter(ParameterSetName="DeleteWhere")]
           [parameter(ParameterSetName="SelectWhere")]
           [switch]$NotLike,
#endregion
            #Parameters for SELECT Queries
           [parameter(ParameterSetName="Select")]
           [parameter(ParameterSetName="SelectWhere")]
           [alias('Property')][string[]]$Select,
           [parameter(ParameterSetName="Select")]
           [parameter(ParameterSetName="SelectWhere")]
           [switch]$Distinct,
           [parameter(ParameterSetName="Select")]
           [parameter(ParameterSetName="SelectWhere")]
           [string[]]$OrderBy,
           [parameter(ParameterSetName="Select")]
           [parameter(ParameterSetName="SelectWhere")]
           [String[]]$GroupBy,
           #Parameters for Delete queries
           [parameter(ParameterSetName="DeleteWhere", Mandatory=$true)]
           [parameter(ParameterSetName="Delete"     , Mandatory=$true)]
           [switch]$Delete,
           #Parameters for Update queries
           [parameter(ParameterSetName="UpdateWhere", Mandatory=$true)]
           [parameter(ParameterSetName="Update"     , Mandatory=$true)]
           [string[]]$Set,
           [parameter(ParameterSetName="UpdateWhere", Mandatory=$true,Position=1)]
           [parameter(ParameterSetName="Update"     , Mandatory=$true,Position=1)]
           [string[]]$Values,
           #Parameters for INSERT Queries
           [parameter(ParameterSetName="Insert"     , Mandatory=$true)]
           [alias('into')][string]$Insert,
           [parameter(ParameterSetName="Insert")]
           [parameter(ParameterSetName="Update")]
           [parameter(ParameterSetName="UpdateWhere")] 
           [parameter(ParameterSetName="DeleteWhere")]
           [parameter(ParameterSetName="SelectWhere")]
           [String]$DateFormat   = "'\''yyyy'-'MM'-'dd HH':'mm':'ss'\''",
           [parameter(ParameterSetName="Paste")]
           [parameter(ParameterSetName="Describe")]
           [parameter(ParameterSetName="Select")]
           [parameter(ParameterSetName="SelectWhere")]
           [switch]$Quiet,
           [switch]$MsSQLserver,
           [switch]$Access,
           [switch]$Excel,
           [switch]$Close)  
 Begin   {#Prepare session if needed and leave it in the global variable DBSessions - a hash table with Name and ODBC connection object
    #If the function was invoked with an Alias of "DB" and there is session named "DB" switch to using that session

    if   (  ("Default" -eq $Session) -and  $Global:DbSessions[$MyInvocation.InvocationName]) {$Session = $MyInvocation.InvocationName}
    #if the session doesn't exist or we're told to force a new session, then create and open a session
    if   (  ($ForceNew)  -or  (  -not      $Global:DbSessions[$session])) {
        #If the -Excel switch was used, then the connection parameter is the path to an Excel file, so check it exists and build the ODBC string
        if  ($Excel)                  {
          if (Test-Path -Path  $Connection)  {
              $Connection  = "Driver={Microsoft Excel Driver (*.xls, *.xlsx, *.xlsm, *.xlsb)};DriverId=790;ReadOnly=0;Dbq=" + 
                             (Resolve-Path -Path $Connection -ErrorAction SilentlyContinue).Path + ";"
              Write-Verbose -Message "Connection String is '$connection'"
          }
          else { Write-Warning -Message "Can't create database connection: could not find $Connection" ; return}  
        }
        if  ($Access)                 {
          if (Test-Path -Path  $Connection)  {
              $Connection  = "Driver={Microsoft Access Driver (*.mdb, *.accdb)};Dbq="+ 
                             (Resolve-Path -Path $Connection -ErrorAction SilentlyContinue).Path + ";"
              Write-Verbose -Message "Connection String is '$connection'"
          }
          else { Write-Warning -Message "Can't create database connection: could not find $Connection" ; return}  
        }
        #Catch the forcing of a new *SQL Server* connection
        if (($ForceNew)      -and       $Global:DbSessions[$session] -and  $Global:DbSessions[$session].GetType().name -eq "SqlConnection" ) {$MsSQLserver = $true}
        if  ($MsSQLserver    -and       $Connection                  -and  $connection -notmatch "=") {
            $Connection = "server=$Connection;trusted_connection=true;timeout=60"
        }
        if (-not $connection)         { Write-Warning -Message "A connection was needed but -Connection was not provided."; break} 
        #Use different types for SQL server and ODBC. They (and the logic) are almost interchangable.
        if  ($MsSQLserver)            { $Global:DbSessions[$Session] = New-Object -TypeName System.Data.SqlClient.SqlConnection -ArgumentList $Connection }
        else                          { $Global:DbSessions[$Session] = New-Object -TypeName System.Data.Odbc.OdbcConnection     -ArgumentList $Connection 
                                        $Global:DbSessions[$Session].ConnectionTimeout = 30
        }
        #Open our connection. NB, if 32 bit office is installed Excel, Access ETC have 32 bit ODBC drivers which need 32 bit Powershell not 64 bit.
        try                           { $Global:DbSessions[$Session].open() }
        catch                         { Write-Warning -Message "Error opening connection to '$Connection'"
                                        $Global:DbSessions[$Session] = $null
                                        break
        }                                    
        #Create an alias which matches the connection name.
        if  ("Default" -eq $Session)  { $Global:DefaultDBConnection = $Connection }
        else                          { New-Alias -Name $Session -Value Get-SQL -Scope Global -Force} 
    } 
    if      ($ChangeDB)       {            $Global:DbSessions[$Session].ChangeDatabase($ChangeDB) } #ODBC Method to change DB - Won't work with every provider
    if   (  ($Paste) -and (Get-Command -Name 'Get-Clipboard' -ErrorAction SilentlyContinue))  {
        #You could use [windows.clipboard]::GetText() - be warned this may not work in the older releases of the standard shell
        #For older versions of PowerShell I have a Get-Clipboard function which wraps this
        $SQL = (Get-Clipboard) -replace "^.*?(?=select|update|delete)","" -replace "[\n|\r]+"," " 
    }
 }
 Process {
    #If $table is specified make sure $SQL isn't empty otherwise we won't get to Select * from $Table; also make sure conditions allow for it to be zero!
    $global:foo = $PSBoundParameters
    if   ($Table -and $null -eq $SQL) { $SQL = " "}
    if   ($SQL.SQL)                   { $SQL = $SQL.SQL}
    if                    ($Describe) { #support -Describe [tablename] to descibe a table
        if ($Global:DbSessions[$Session].driver -match "SQORA" ) { #Oracle is special ...
            Get-SQL -Session $Session -Quiet -SQL  "select COLUMN_NAME, data_type as TYPE_NAME, data_length AS COLUMN_SIZE " + 
                                                    " from user_tab_cols where table_name = '$Describe' order by COLUMN_NAME"
        }
        else  { #Remove any [] around the table name - because that's how .GetSchema() works ...
            $Describe = $Describe -replace "\[(.*)\]",'$1'
            # For some drivers .GetSchema() can get the columns for a single table. But the Excel driver can't, so get all columns and filter.
            if     ($Global:DbSessions[$Session].Driver -match "ACEODBC.DLL" ) {
                    $columns = $Global:DbSessions[$Session].GetSchema("Columns") | Where-Object {$_.TABLE_NAME -eq $Describe } 
            } 
            elseif ($Global:DbSessions[$Session].gettype().name -match "SqlConnection" ) {#SQL server uses slightly differnet syntax from ODBC
                    $columns = $Global:DbSessions[$Session].GetSchema("Columns", @("%","%",$Describe,"%")) 
            }
            else {  $columns = $Global:DbSessions[$Session].GetSchema("Columns", @("","",$Describe)) }
            if ($GridView) {$columns | Out-GridView -Title "Table $Describe"} 
            else           {$columns | Select-Object -Property @{n="COLUMN_NAME";e={if ($_.Column_Name -match "\W") {"[$($_.Column_Name)]"} else {$_.column_Name} }},
                                                               TYPE_NAME, COLUMN_SIZE, IS_NULLABLE 
            } 
        }
    }
    elseif              ($Showtables) {   #ODBC method to get tables won't work with every provider, but nor will executing "show tables". $SQL param becomes a filter
        if   ($Global:DbSessions[$Session].driver -match "SQORA" ) {#Oracle is special ...
              (Get-SQL  -Session $Session -Quiet -SQL  "select OBJECT_NAME from user_objects where object_type IN ('VIEW','TABLE'); ").object_name | 
                 Where-Object {$_ -like "$SQL*"}
        }
        else {$Global:DbSessions[$Session].GetSchema("Tables") | where {$Global:DbSessions[$Session].DataSource -ne "Access" -or $_.TABLE_TYPE -ne "SYSTEM TABLE"} |
            ForEach-Object {
                if     ($_.TABLE_NAME -like "$SQL*" -and $_.TABLE_NAME -match "\W") {"[" + $_.TABLE_NAME + "]"} 
                elseif ($_.TABLE_NAME -like "$SQL*")                                {      $_.TABLE_NAME      }   
            } | Sort-Object
        }
    }
    elseif           ($null -ne $SQL) { #$SQL holds any SQL which we can't (or don't want to( assemble from the cmdline, a whole statement or final clause
        ForEach        ($s  in  $SQL) { #More than one statement/clause can be passed
            if ($Delete -or $Set -and -not $Table) {Write-Warning -Message "You must specifiy a table and where condition to use -Delete or -Set" ; return }
            if               ($Table) { #If $Table was specified, build a Select, Delete or Update query
               #Support -table [tablename] -Where [ColumnName] -eq 99 and similar syntax.
               # -eq -ne and other operators are *switches*. The operand for = (etc.) is in $SQL so only Operator is allowed. Too complex to enforce this in Param() block!
               $opCount        =  (($Like, $EQ, $NE , $LT , $GT , $GE, $LE, $NotLike) -eq $true).Count
               #Can't have multiple operators, and operator requires -Where to be specified and a value in -SQL (-SQL usually implied in cmdline)
               if ((($opCount) -gt 1) -or  (($opCount -eq 1) -and -not $Where ) -or ($Where -and  " " -eq  $s )) {
                    Write-Warning -Message  "You can't specify a where condition like that"
                    return 
               }  
               if  (($opCount) -eq 1) { #If we have an operator, column and value in $s turn $s into the condition (add the column name after)
                     #if the operand for -eq etc is a date format it for SQL
                    if  ($s -is [datetime])  {
                                        $s = $s.tostring($DateFormat)  #Default format has "'" this works for Excel inserts and SQL server.
                        if ($Global:DbSessions[$Session].Driver -eq "ACEODBC.DLL") {       #For Excel where needs # not quotes as date markers
                                        $s = $s -replace "'","#" 
                        }
                    } #if the operand for -eq etc is not a number or isn't wrrapped in quotes. Wrap it in quotes and double up the ' character
                    elseif (($s -notmatch "^\d+\.?\d*$") -and ($s -notmatch "^'.*'$"))   
                                      {$s = "'" + ($s -replace "(?<!')'(?!')","''") +"'"  } 
                    if          ($EQ) {$s =   " = $s "            }
                    if          ($NE) {$s =   " <> $s "            }
                    if          ($GE) {$s =   " >= $s "            }
                    if          ($LE) {$s =   " <= $s "            }
                    if          ($GT) {$s =   " > $s "            }
                    if          ($LT) {$s =   " < $s "            }
                    if       (($Like)  -or ($NotLike) ) {   #for the like operators replace * wildcard with SQL % wildcard
                                       $s = $s -replace "\*","%"   }    
                    if        ($Like) {$s =     " like $s "        }
                    if     ($NotLike) {$s = " not like $s "        }
                    #At the end of this $s holds the condition but not the column name
               }
               if           ($Delete) { #Support Delete queries -Table [tableName] -Delete -where [Column] -eq [Value]
                  #A careless -Delete could wipe out a table - so insist on either -where [columnName] and a condition, or "Where blah blah" in $SQL
                  if ((($Where) -and $s) -or ($s -match "where\s+\w+")) {
                    if ($Where)       {$s = "DELETE FROM $Table WHERE $Where " + $S }  
                    else              {$s = "DELETE FROM $Table "              + $S }   
                  }
                  else {Write-Warning -Message "You must specifiy a where condition to use -Delete"; return }
               } 
               elseif          ($Set) {
                #Support update ... set queries -Table [tableName] -Set [Columns] -Values [values] -Where [Column] -EQ [Value]
                #Don't allow set to modify all the rows (same logic as Delete)
                  if ( (  $Where  -and $s) -or ($s -match "where\s+\w+")) {
                    #We have a list of columns in Set and values for them need the same number of each - then build the set clause, wrapping text values in ''
                    if ($Set.Count  -ne  $Values.Count)          {Write-Warning -Message "Must have the same numbe of columns to set as values to set them to"; return }
                    $setList = ""
                    for  ($i = 0; $i  -lt  $set.count; $i++) { 
                        if  (   $Values[$i] -is [datetime])  {
                                $Vi   = $Values[$i].tostring($DateFormat)     #Default format has "'" this works for Excel inserts and SQL server.
                            #if ($Session.Driver -eq "ACEODBC.DLL") {$Vi = $i[$i]-replace "'","#" } #For Excel where needs # not quotes as date markers
                                $SetList = $SetList + $Set[$i] + "= " +  $Values[$i] +" ,"
                        }
                        # Wrap text in ' and escape ' char
                        elseif ($Values[$i] -notmatch "^[\d\.]*$") {$SetList = $SetList + $Set[$i] + "='" + ($Values[$i] -replace "'","''") +"' ," }
                        else                                       {$SetList = $SetList + $Set[$i] + "= " +  $Values[$i] +" ," } 
                    }
                    #will have an extra "," at the end.
                    $setList = $setList -replace ",$",""
                    if   ($Where)   {$s = "UPDATE $Table SET $setList WHERE $Where " + $s }  
                    else            {$s = "UPDATE $Table SET $setList "              + $s } 
                }  
                  else                {Write-Warning -Message "You must specifiy a where condition to use -Set"   ; return }                    
               }                
               else                   {#If we're not updating or deleting and -Table was passed we must be selecting ....
                    if   (   $Select) {$SelectClause = ($Select -join ", ") + " FROM $Table " }
                    else              {$SelectClause =                      " * FROM $Table " }
                    if   (    $Where) {$SelectClause = $SelectClause   +      "WHERE $Where " } #note we need to have the "what" part of SQL. but SQL could be @("=10",">73") we'll run 2 queries
                    if   ( $Distinct) {$s = "SELECT DISTINCT "         +  $SelectClause + $s  }
                    else              {$s = "SELECT "                  +  $SelectClause + $s  }
                    if   (  $GroupBy) {$s = $s +    " GROUP BY "       + ($GroupBy -join ", ")}
                    if   (  $OrderBy) {$s = $s +    " ORDER BY "       + ($OrderBy -join ", ")}
                }
            }
            elseif          ($Insert) {
            #Support -insert [IntoTableName] @{hashtable of fields and values}
                if     ($s -is [Hashtable]) {$index = $s.keys} 
                elseif ($s -is [psobject] ) {$index = (Get-Member -InputObject $s -MemberType NoteProperty).Name }
                else                        { Write-Warning -Message "Can't build an Insert statement from $s. Pass a hashtable or a PSObject" ; return}
                $fieldsPart     = " " 
                $valuesPart     = " "   
                foreach ($name in $index) {
                    $fieldsPart = $fieldsPart  + $name + " , " 
                    $v          = $s.$name
                    #$DateFormat defaults to the standard date format which SQL dialects support, but it can be overridden for special cases
                    if     ($v -is [datetime] )  {$valuesPart = $valuesPart +         $v.tostring($DateFormat) + " , " }
                    elseif ($v -is [int]    -or 
                            $v -is [float]  -or 
                            $v -is [boolean]  )  {$valuesPart = $valuesPart +         $v.tostring()         +    " , " }
                    elseif ($v -match "^\d+$" )  {$valuesPart = $valuesPart +         $v.tostring()         +    " , " }
                    else                         {$valuesPart = $valuesPart +  "'" + ($v -replace "'","''") +   "' , " }
                }           
                $s  = ("INSERT INTO {0} ({1}) VALUES ({2})" -f $Insert,($fieldsPart -replace ",\s*$",""),($valuesPart -replace ",\s*$","")) 
                $s = $s -replace ",\s*,",", null ," -replace "(?<=[(,])\s*''\s*(?=[),])"," null " -replace ",\s*\)",", null)"
            }
            Write-Verbose -Message $s          
            #Choose different data adapter objects for SQL server or ODBC
            If ($Global:DbSessions[$Session].gettype().name -match "SqlConnection" )  {
               $da = New-Object    -TypeName System.Data.SqlClient.SqlDataAdapter -ArgumentList (
                        New-Object -TypeName System.Data.SqlClient.SqlCommand     -ArgumentList $s,$Global:DbSessions[$Session] ) 
            }
            else                      {
                 $da = New-Object  -TypeName System.Data.Odbc.OdbcDataAdapter     -ArgumentList (
                        New-Object -TypeName System.Data.Odbc.OdbcCommand         -ArgumentList $s,$Global:DbSessions[$Session]) 
             
            }
            $dt           = New-Object -TypeName System.Data.DataTable
            #And finally we get to run the query.
            try                       { if ((-not ($Set -or $Delete -or ($Insert -and $ConfirmPreference -ne "high"))) -or ($PSCmdlet.ShouldProcess("$Session database", $s)) ) {
               $rows  = $da.fill($dt)
               if (-not ($Quiet -or $Delete -or $Set -or $Insert)) {Write-host -Object ("" + [int]$rows + " row(s) returned")}
            }} 
            catch                     { 
               if($SQL)               { #if we get an error and -SQL was passed show the final SQL statement.
                  $e=$Global:error[0]
                  throw ( New-Object -TypeName "System.Management.Automation.ErrorRecord" `
                                     -ArgumentList (($e.exception.message -replace "^(.*])\s*","`$1`n") + "`n `n>>> $SQL `n `n" ), $e.FullyQualifiedErrorId ,"ParserError" ,$e.TargetObject) 
               }
               else                   { throw }
              }
            if   (($GridView) -and (($host.version.major -GE 3)-or ($host.name -match "ISE" )) ) {$dt | Out-GridView -Title $s}  
            else  {$dt}
        }
    }
    #If $SQL, $table, $describe or $showtimes weren't included either we're opening a new connection, or we're checking or closing an existing one.
    elseif              (-not $Close) { $Global:DbSessions[$Session] }
    if                       ($Close) { $Global:DbSessions[$Session].close()
                                        $Global:DbSessions.Remove($Session)
                                        Remove-Item -Path (Join-Path -Path "Alias:\" -ChildPath $Session) -ErrorAction SilentlyContinue
    }
 }
}

Function Hide-GetSQL {
<#
    .Synopsis
        Allows a command line with quote marks to passed into Get-SQL, can be used simply as ¬
    .Example
        ¬ select host,user from mysql.user
        Sends the command "select host,user from mysql.user" to the default ODBC session
     
#>

  Get-Sql -sql ($MyInvocation.line.substring($MyInvocation.OffsetInLine)) | Format-Table -AutoSize  
} 
Set-Alias -Name ¬ -Value Hide-GetSQL