externalLibs/SQLPSX/mySQLLib/mySQLLib.psm1

[void][System.Reflection.Assembly]::LoadWithPartialName("MySql.Data")
# ---------------------------------------------------------------------------
### <Script>
### <Author>
### Mike Shepard
### </Author>
### <Description>
### Defines functions for executing Ado.net queries with mySQL
### </Description>
### <Usage>
### import-module mySQLLib
### </Usage>
### </Script>
# ---------------------------------------------------------------------------


<#
    .SYNOPSIS
        Tests to see if a value is a SQL NULL or not
 
    .DESCRIPTION
        Returns $true if the value is a SQL NULL.
 
    .PARAMETER value
        The value to test
 
     
 
    .EXAMPLE
        PS C:\> Is-NULL $row.columnname
 
     
    .INPUTS
        None.
        You cannot pipe objects to New-Connection
 
    .OUTPUTS
        Boolean
 
#>

function Is-NULL{
  param([Parameter(Position=0, Mandatory=$true)]$value)
  return  [System.DBNull]::Value.Equals($value)
}


<#
    .SYNOPSIS
        Create a SQLConnection object with the given parameters
 
    .DESCRIPTION
        This function creates a SQLConnection object, using the parameters provided to construct the connection string. You may optionally provide the initial database, and SQL credentials (to use instead of NT Authentication).
 
    .PARAMETER Server
        The name of the SQL Server to connect to. To connect to a named instance, enclose the server name in quotes (e.g. "Laptop\SQLExpress")
 
    .PARAMETER Database
        The InitialDatabase for the connection.
     
    .PARAMETER User
        The SQLUser you wish to use for the connection (instead of using NT Authentication)
         
    .PARAMETER Password
        The password for the user specified by the User parameter.
 
    .EXAMPLE
        PS C:\> New-Connection -server MYSERVER -database master
 
    .EXAMPLE
        PS C:\> Get-Something -server MYSERVER -user sa -password sapassword
 
    .INPUTS
        None.
        You cannot pipe objects to New-Connection
 
    .OUTPUTS
        System.Data.SqlClient.SQLConnection
 
#>

function New-MySQLConnection{
param([Parameter(Position=0, Mandatory=$true)][string]$server, 
      [Parameter(Position=1, Mandatory=$false)][string]$database='',
      [string]$user='',
      [string]$password='',
      [int]$port=3306)

    if($database -ne ''){
      $dbclause="Database=$database;"
    }
    $conn=new-object MySql.Data.MySqlClient.MySqlConnection
    
    if ($user -ne ''){
        $conn.ConnectionString="Server=$Server;Port=$port;Database=$DataBase;Uid=$User;Pwd=$Password;allow zero datetime=yes"
    } else {
        $conn.ConnectionString="Server=$server;Port=$port;$dbclause`Integrated Security=True"
    }
    $conn.Open()
    write-debug $conn.ConnectionString
    return $conn
}

function Get-Connection{
param([MySql.Data.MySqlClient.MySqlConnection]$conn,
      [string]$server, 
      [string]$database,
      [string]$user,
      [string]$password)
    if (-not $conn){
        if ($server){
            $conn=New-Connection -server $server -database $database -user $user -password $password 
        } else {
            throw "No connection or connection information supplied"
        }
    }
    return $conn
}

function Put-OutputParameters{
param([Parameter(Position=0, Mandatory=$true)][MySql.Data.MySqlClient.MySqlCommand]$cmd, 
      [Parameter(Position=1, Mandatory=$false)][hashtable]$outparams)
    if ($outparams){
        foreach($outp in $outparams.Keys){
            $paramtype=get-paramtype $outparams[$outp]
            $p=$cmd.Parameters.Add("@$outp",$paramtype)
            $p.Direction=[System.Data.ParameterDirection]::Output
            if ($paramtype -like '*char*'){
               $p.Size=[int]$outparams[$outp].Replace($paramtype.ToString().ToLower(),'').Replace('(','').Replace(')','')
            }
        }
    }
}

function Get-Outputparameters{
param([Parameter(Position=0, Mandatory=$true)][MySql.Data.MySqlClient.MySqlCommand]$cmd,
      [Parameter(Position=1, Mandatory=$true)][hashtable]$outparams)
    foreach($p in $cmd.Parameters){
        if ($p.Direction -eq [System.Data.ParameterDirection]::Output){
          $outparams[$p.ParameterName.Replace("@","")]=$p.Value
        }
    }
}
#>


function Get-ParamType{
param([string]$typename)
    $type=switch -wildcard ($typename.ToLower()) {
        'uniqueidentifier' {[System.Data.SqlDbType]::UniqueIdentifier}
        'int'  {[System.Data.SQLDbType]::Int}
        'datetime'  {[System.Data.SQLDbType]::Datetime}
        'tinyint'  {[System.Data.SQLDbType]::tinyInt}
        'smallint'  {[System.Data.SQLDbType]::smallInt}
        'bigint'  {[System.Data.SQLDbType]::BigInt}
        'bit'  {[System.Data.SQLDbType]::Bit}
        'char*'  {[System.Data.SQLDbType]::char}
        'nchar*'  {[System.Data.SQLDbType]::nchar}
        'date'  {[System.Data.SQLDbType]::date}
        'datetime'  {[System.Data.SQLDbType]::datetime}
        'varchar*' {[System.Data.SqlDbType]::Varchar}
        'nvarchar*' {[System.Data.SqlDbType]::nVarchar}
        default {[System.Data.SqlDbType]::Int}
    }
    return $type
    
}

function Copy-HashTable{
param([hashtable]$hash,
[String[]]$include,
[String[]]$exclude)

    if($include){
       $newhash=@{}
       foreach ($key in $include){
        if ($hash.ContainsKey($key)){
               $newhash.Add($key,$hash[$key]) | Out-Null 
        }
       }
    } else {
       $newhash=$hash.Clone()
       if ($exclude){
           foreach ($key in $exclude){
                if ($newhash.ContainsKey($key)) {
                       $newhash.Remove($key) | Out-Null 
                }
           }
       }
    }
    return $newhash
}

<#
Helper function figure out what kind of returned object to build from the results of a sql call (ds).
Options are:
    1. Dataset (multiple lists of rows)
    2. Datatable (list of datarows)
    3. Nothing (no rows and no output variables
    4. Dataset with output parameter dictionary
    5. Datatable with output parameter dictionary
    6. A dictionary of output parameters
     
 
#>

function Get-CommandResults{
param([Parameter(Position=0, Mandatory=$true)][System.Data.Dataset]$ds
    ,       [Parameter(Position=1, Mandatory=$true)][HashTable]$outparams
    )   

    if ($ds.tables.count -eq 1){
        $retval= $ds.Tables[0]
    }
    elseif ($ds.tables.count -eq 0){
        $retval=$null
    } else {
        [system.Data.DataSet]$retval= $ds 
    }
    
    if ($outparams.Count -gt 0){
        if ($retval){
            return @{Results=$retval; OutputParameters=$outparams}
        } else {
            return $outparams
        }
    } else {
        return $retval
    }

}

<#
    .SYNOPSIS
        Create a sql command object
 
    .DESCRIPTION
        This function uses the information contained in the parameters to create a sql command object. In general, you will want to use the invoke- functions directly,
        but if you need to manipulate a command object in ways that those functions don't allow, you will need this. Also, the invoke-bulkcopy function allows you to pass
        a command object instead of a set of records in order to "stream" the records into the destination in cases where there are a lot of records and you don't want to
        allocate memory to hold the entire result set.
 
    .PARAMETER sql
        The sql to be executed by the command object (although it is not executed by this function).
 
    .PARAMETER connection
        An existing connection to perform the sql statement with.
 
    .PARAMETER parameters
        A hashtable of input parameters to be supplied with the query. See example 2.
         
    .PARAMETER timeout
        The commandtimeout value (in seconds). The command will fail and be rolled back if it does not complete before the timeout occurs.
 
    .PARAMETER Server
        The server to connect to. If both Server and Connection are specified, Server is ignored.
 
    .PARAMETER Database
        The initial database for the connection. If both Database and Connection are specified, Database is ignored.
 
    .PARAMETER User
        The sql user to use for the connection. If both User and Connection are specified, User is ignored.
 
    .PARAMETER Password
        The password for the sql user named by the User parameter.
 
    .PARAMETER Transaction
        A transaction to execute the sql statement in.
 
    .EXAMPLE
        PS C:\> $cmd=new-sqlcommand "ALTER DATABASE AdventureWorks Modify Name = Northwind" -server MyServer
        PS C:\> $cmd.ExecuteNonQuery()
 
 
    .EXAMPLE
        PS C:\> $cmd=new-sqlcommand -server MyServer -sql "Select * from MyTable"
        PS C:\> invoke-sqlbulkcopy -records $cmd -server MyOtherServer -table CopyOfMyTable
 
    .INPUTS
        None.
        You cannot pipe objects to new-sqlcommand
 
    .OUTPUTS
        System.Data.SqlClient.SqlCommand
 
#>

function New-MySQLCommand{
param([Parameter(Position=0, Mandatory=$true)][Alias('storedProcName')][string]$sql,
      [Parameter(ParameterSetName="SuppliedConnection",Position=1, Mandatory=$false)][ MySql.Data.MySqlClient.MySQLConnection]$connection,
      [Parameter(Position=2, Mandatory=$false)][hashtable]$parameters=@{},
      [Parameter(Position=3, Mandatory=$false)][int]$timeout=30,
      [Parameter(ParameterSetName="AdHocConnection",Position=4, Mandatory=$false)][string]$server,
      [Parameter(ParameterSetName="AdHocConnection",Position=5, Mandatory=$false)][string]$database,
      [Parameter(ParameterSetName="AdHocConnection",Position=6, Mandatory=$false)][string]$user,
      [Parameter(Position=7, Mandatory=$false)][string]$password,
      [Parameter(Position=8, Mandatory=$false)][MySql.Data.MySqlClient.MySqlTransaction]$transaction=$null
      ,[Parameter(Position=9, Mandatory=$false)][hashtable]$outparameters=@{}
     )
   
    $dbconn=Get-Connection -conn $connection -server $server -database $database -user $user -password $password
    $close=($dbconn.State -eq [System.Data.ConnectionState]'Closed')
    if ($close) {
        $dbconn.Open()
    }    
    $cmd=new-object  MySql.Data.MySqlClient.MySqlCommand($sql,$dbconn)
    $cmd.CommandTimeout=$timeout
    foreach($p in $parameters.Keys){
        $parm=$cmd.Parameters.AddWithValue("@$p",$parameters[$p])
        if (Is-NULL $parameters[$p]){
           $parm.Value=[DBNull]::Value
        }
    }
    put-outputparameters $cmd $outparameters

    if ($transaction -is [MySql.Data.MySqlClient.MySqlTransaction]){
       $cmd.Transaction = $transaction
    }
    return $cmd


}



<#
    .SYNOPSIS
        Execute a sql statement, ignoring the result set. Returns the number of rows modified by the statement (or -1 if it was not a DML staement)
 
    .DESCRIPTION
        This function executes a sql statement, using the parameters provided and returns the number of rows modified by the statement. You may optionally
        provide a connection or sufficient information to create a connection, as well as input parameters, command timeout value, and a transaction to join.
 
    .PARAMETER sql
        The SQL Statement
 
    .PARAMETER connection
        An existing connection to perform the sql statement with.
 
    .PARAMETER parameters
        A hashtable of input parameters to be supplied with the query. See example 2.
         
    .PARAMETER timeout
        The commandtimeout value (in seconds). The command will fail and be rolled back if it does not complete before the timeout occurs.
 
    .PARAMETER Server
        The server to connect to. If both Server and Connection are specified, Server is ignored.
 
    .PARAMETER Database
        The initial database for the connection. If both Database and Connection are specified, Database is ignored.
 
    .PARAMETER User
        The sql user to use for the connection. If both User and Connection are specified, User is ignored.
 
    .PARAMETER Password
        The password for the sql user named by the User parameter.
 
    .PARAMETER Transaction
        A transaction to execute the sql statement in.
 
    .EXAMPLE
        PS C:\> invoke-sql "ALTER DATABASE AdventureWorks Modify Name = Northwind" -server MyServer
 
 
    .EXAMPLE
        PS C:\> $con=New-Connection MyServer
        PS C:\> invoke-sql "Update Table1 set Col1=null where TableID=@ID" -parameters @{ID=5}
 
    .INPUTS
        None.
        You cannot pipe objects to invoke-sql
 
    .OUTPUTS
        Integer
 
#>

function Invoke-MySql{
param([Parameter(Position=0, Mandatory=$true)][string]$sql,
      [Parameter(ParameterSetName="SuppliedConnection",Position=1, Mandatory=$false)][MySql.Data.MySqlClient.MySqlConnection]$connection,
      [Parameter(Position=2, Mandatory=$false)][hashtable]$parameters=@{},
      [Parameter(Position=3, Mandatory=$false)][hashtable]$outparameters=@{},
      [Parameter(Position=4, Mandatory=$false)][int]$timeout=30,
      [Parameter(ParameterSetName="AdHocConnection",Position=5, Mandatory=$false)][string]$server,
      [Parameter(ParameterSetName="AdHocConnection",Position=6, Mandatory=$false)][string]$database,
      [Parameter(ParameterSetName="AdHocConnection",Position=7, Mandatory=$false)][string]$user,
      [Parameter(ParameterSetName="AdHocConnection",Position=8, Mandatory=$false)][string]$password,
      [Parameter(Position=9, Mandatory=$false)][System.Data.SqlClient.SqlTransaction]$transaction=$null)
    

       $cmd=new-mysqlcommand @PSBoundParameters

       #if it was an ad hoc connection, close it
       if ($server){
          $cmd.connection.close()
       }    

       return $cmd.ExecuteNonQuery()
    
}
<#
    .SYNOPSIS
        Execute a sql statement, returning the results of the query.
 
    .DESCRIPTION
        This function executes a sql statement, using the parameters provided (both input and output) and returns the results of the query. You may optionally
        provide a connection or sufficient information to create a connection, as well as input and output parameters, command timeout value, and a transaction to join.
 
    .PARAMETER sql
        The SQL Statement
 
    .PARAMETER connection
        An existing connection to perform the sql statement with.
 
    .PARAMETER parameters
        A hashtable of input parameters to be supplied with the query. See example 2.
 
    .PARAMETER outparameters
        A hashtable of input parameters to be supplied with the query. Entries in the hashtable should have names that match the parameter names, and string values that are the type of the parameters. See example 3.
         
    .PARAMETER timeout
        The commandtimeout value (in seconds). The command will fail and be rolled back if it does not complete before the timeout occurs.
 
    .PARAMETER Server
        The server to connect to. If both Server and Connection are specified, Server is ignored.
 
    .PARAMETER Database
        The initial database for the connection. If both Database and Connection are specified, Database is ignored.
 
    .PARAMETER User
        The sql user to use for the connection. If both User and Connection are specified, User is ignored.
 
    .PARAMETER Password
        The password for the sql user named by the User parameter.
 
    .PARAMETER Transaction
        A transaction to execute the sql statement in.
    .EXAMPLE
        This is an example of a query that returns a single result.
        PS C:\> $c=New-Connection '.\sqlexpress'
        PS C:\> $res=invoke-query 'select * from master.dbo.sysdatabases' -conn $c
        PS C:\> $res
   .EXAMPLE
        This is an example of a query that returns 2 distinct result sets.
        PS C:\> $c=New-Connection '.\sqlexpress'
        PS C:\> $res=invoke-query 'select * from master.dbo.sysdatabases; select * from master.dbo.sysservers' -conn $c
        PS C:\> $res.Tables[1]
    .EXAMPLE
        This is an example of a query that returns a single result and uses a parameter. It also generates its own (ad hoc) connection.
        PS C:\> invoke-query 'select * from master.dbo.sysdatabases where name=@dbname' -param @{dbname='master'} -server '.\sqlexpress' -database 'master'
 
     .INPUTS
        None.
        You cannot pipe objects to invoke-query
 
   .OUTPUTS
        Several possibilities (depending on the structure of the query and the presence of output variables)
        1. A list of rows
        2. A dataset (for multi-result set queries)
        3. An object that contains a dictionary of ouptut parameters and their values and either 1 or 2 (for queries that contain output parameters)
#>

function Invoke-MySQLQuery{
param( [Parameter(Position=0, Mandatory=$true)][string]$sql,
       [Parameter(ParameterSetName="SuppliedConnection", Position=1, Mandatory=$false)][MySql.Data.MySqlClient.MySqlConnection]$connection,
       [Parameter(Position=2, Mandatory=$false)][hashtable]$parameters=@{},
       [Parameter(Position=3, Mandatory=$false)][hashtable]$outparameters=@{},
       [Parameter(Position=4, Mandatory=$false)][int]$timeout=30,
       [Parameter(ParameterSetName="AdHocConnection",Position=5, Mandatory=$false)][string]$server,
       [Parameter(ParameterSetName="AdHocConnection",Position=6, Mandatory=$false)][string]$database,
       [Parameter(ParameterSetName="AdHocConnection",Position=7, Mandatory=$false)][string]$user,
       [Parameter(ParameterSetName="AdHocConnection",Position=8, Mandatory=$false)][string]$password,
       [Parameter(Position=9, Mandatory=$false)][System.Data.SqlClient.SqlTransaction]$transaction=$null,
       [Parameter(Position=10, Mandatory=$false)] [ValidateSet("DataSet", "DataTable", "DataRow", "Dynamic")] [string]$AsResult="Dynamic"
       )
    
    $connectionparameters=copy-hashtable $PSBoundParameters -exclude AsResult
    $cmd=new-mysqlcommand @connectionparameters
    $ds=New-Object system.Data.DataSet
    $da=New-Object MySql.Data.MySqlClient.MySqlDataAdapter($cmd)
    $da.fill($ds) | Out-Null
    
    #if it was an ad hoc connection, close it
    if ($server){
       $cmd.connection.close()
    }
    get-outputparameters $cmd $outparameters
    switch ($AsResult)
    {
        'DataSet'   { $result = $ds }
        'DataTable' { $result = $ds.Tables }
        'DataRow'   { $result = $ds.Tables[0] }
        'Dynamic'   { $result = get-commandresults $ds $outparameters  } 
    }
    return $result
}



<#
    .SYNOPSIS
        Execute a stored procedure, returning the results of the query.
 
    .DESCRIPTION
        This function executes a stored procedure, using the parameters provided (both input and output) and returns the results of the query. You may optionally
        provide a connection or sufficient information to create a connection, as well as input and output parameters, command timeout value, and a transaction to join.
 
    .PARAMETER sql
        The SQL Statement
 
    .PARAMETER connection
        An existing connection to perform the sql statement with.
 
    .PARAMETER parameters
        A hashtable of input parameters to be supplied with the query. See example 2.
 
    .PARAMETER outparameters
        A hashtable of input parameters to be supplied with the query. Entries in the hashtable should have names that match the parameter names, and string values that are the type of the parameters.
        Note: not all types are accounted for by the code. int, uniqueidentifier, varchar(n), and char(n) should all work, though.
         
    .PARAMETER timeout
        The commandtimeout value (in seconds). The command will fail and be rolled back if it does not complete before the timeout occurs.
 
    .PARAMETER Server
        The server to connect to. If both Server and Connection are specified, Server is ignored.
 
    .PARAMETER Database
        The initial database for the connection. If both Database and Connection are specified, Database is ignored.
 
    .PARAMETER User
        The sql user to use for the connection. If both User and Connection are specified, User is ignored.
 
    .PARAMETER Password
        The password for the sql user named by the User parameter.
 
    .PARAMETER Transaction
        A transaction to execute the sql statement in.
    .EXAMPLE
        #Calling a simple stored procedure with no parameters
        PS C:\> $c=New-Connection -server '.\sqlexpress'
        PS C:\> invoke-storedprocedure 'sp_who2' -conn $c
    .EXAMPLE
        #Calling a stored procedure that has an output parameter and multiple result sets
        PS C:\> $c=New-Connection '.\sqlexpress'
        PS C:\> $res=invoke-storedprocedure -storedProcName 'AdventureWorks2008.dbo.stp_test' -outparameters @{LogID='int'} -conne $c
        PS C:\> $res.Results.Tables[1]
        PS C:\> $res.OutputParameters
         
        For reference, here's the stored procedure:
        CREATE procedure [dbo].[stp_test]
            @LogID int output
        as
            set @LogID=5
            select * from master.dbo.sysdatabases
            select * from master.dbo.sysservers
    .EXAMPLE
        #Calling a stored procedure that has an input parameter
        PS C:\> invoke-storedprocedure 'sp_who2' -conn $c -parameters @{loginame='sa'}
    .INPUTS
        None.
        You cannot pipe objects to invoke-storedprocedure
 
    .OUTPUTS
        Several possibilities (depending on the structure of the query and the presence of output variables)
        1. A list of rows
        2. A dataset (for multi-result set queries)
        3. An object that contains a hashtables of ouptut parameters and their values and either 1 or 2 (for queries that contain output parameters)
#>

function Invoke-MySQLStoredProcedure{
param([Parameter(Position=0, Mandatory=$true)][string]$storedProcName,
      [Parameter(ParameterSetName="SuppliedConnection",Position=1, Mandatory=$false)][MySql.Data.MySqlClient.MySqlConnection]$connection,
      [Parameter(Position=2, Mandatory=$false)][hashtable] $parameters=@{},
      [Parameter(Position=3, Mandatory=$false)][hashtable]$outparameters=@{},
      [Parameter(Position=4, Mandatory=$false)][int]$timeout=30,
      [Parameter(ParameterSetName="AdHocConnection",Position=5, Mandatory=$false)][string]$server,
      [Parameter(ParameterSetName="AdHocConnection",Position=6, Mandatory=$false)][string]$database,
      [Parameter(ParameterSetName="AdHocConnection",Position=7, Mandatory=$false)][string]$user,
      [Parameter(ParameterSetName="AdHocConnection",Position=8, Mandatory=$false)][string]$password,
      [Parameter(Position=9, Mandatory=$false)][System.Data.SqlClient.SqlTransaction]$transaction=$null) 

    $cmd=new-MySqlCommand @PSBoundParameters
    $cmd.CommandType=[System.Data.CommandType]::StoredProcedure  
    $ds=New-Object system.Data.DataSet
    $da=New-Object MySql.Data.MySqlClient.MySqlDataAdapter($cmd)
    $da.fill($ds) | out-null

    get-outputparameters $cmd $outparameters

    #if it was an ad hoc connection, close it
    if ($server){
       $cmd.connection.close()
    }    
    
    return (get-commandresults $ds $outparameters )
}




export-modulemember New-MySQLConnection
export-modulemember new-MySqlCommand
export-modulemember invoke-MySql
export-modulemember invoke-MySqlquery
export-modulemember invoke-MySqlStoredProcedure