Cmdlets/Invoke-SqlBulkCopy.ps1

<#
.Synopsis
    Executes a bulk copy between two connections.
 
.Description
    Executes a bulk copy operation between two connections. This is highly
    optimized if the destination has a managed bulkcopy implemenation, otherwise
    it is only generally optimized. For example, SQL Server has a bulk copy
    class (SqlBulkCopy) that is easily implemented and provides an efficient
    means of inserting data into SQL Server.
 
    The default implemenation, if the provider does not provider a managed
    bulk copy mechanism is to prepare the sql insert, and wrap multiple inserts
    into a single transaction (batching). This provides a significant
    performance improvement over looping with Invoke-SqlUpdate.
 
    CONSIDERATIONS
    * You must specify either a SourceConnectionName or DestinationConnectionName,
        whichever one is not specified will use 'default', not specifying either
        will cause an error.
    * If you don’t specify DestinationTable, it will use SourceTable; however
        DestinationTable is required if you use SourceQuery.
    * If you specify ColumnMap and Source Table, then the select against the
        SourceConnection will be limited to the columns you specified in ColumnMap.
 
    Returns number of rows copied.
 
.Parameter SourceConnectionName
    User defined name for connection where data will be queried from.
 
.Parameter DestinationConnectionName
    User defined name for connection where data will be inserted to.
 
.Parameter SourceTable
    The name of the table in the source connection.
 
.Parameter DestinationTable
    The name of the table to write to in the destination connection.
    If not specified, will be taken from SourceTable parameter.
 
.Parameter ColumnMap
    Key is the column name in the source connection.
    Value is the column name in the destination connection.
 
.Parameter BatchSize
    How many inserts are batched together at one time.
 
.Parameter BatchTimeout
    How long, in seconds, that each batch can take.
    Defaults to the command timeout for the source connection.
 
.Parameter Notify
    If present, as each batch completes a progress notification will be
    generated with the total number of rows inserted so far.
 
.Parameter SourceQuery
    The query to determine the source data, instead of specifying a table.
 
.Parameter SourceParameters
    Parameters needed for the source query.
 
#>

Function Invoke-SqlBulkCopy {
    [CmdletBinding()]
    Param(
        [ValidateNotNullOrEmpty()][string]$SourceConnectionName = "default"
        , [ValidateNotNullOrEmpty()][string]$DestinationConnectionName = "default"
        , [Parameter(Mandatory, ParameterSetName="table")][string]$SourceTable
        , [Parameter(Mandatory, ParameterSetName="query")][AllowEmptyString()][string[]]$SourceQuery
        , [Parameter(ParameterSetName="query")][hashtable]$SourceParameters = @{}
        , [Parameter(ParameterSetName="table")]
            [Parameter(Mandatory, ParameterSetName="query")]
            [string]$DestinationTable
        , [hashtable]$ColumnMap = @{}
        , [ValidateRange(1,5000)][int]$BatchSize = 500
        , [int]$BatchTimeout = -1
        , [switch]$Notify
    )
    
    If($SourceConnectionName -eq $DestinationConnectionName) {
        $PSCmdlet.ThrowTerminatingError(
            [System.Management.Automation.ErrorRecord]::new(
                [System.Management.Automation.PSArgumentException]::new(
                    "You cannot use the same connection for both the source and destination!", "SourceConnectionName, DestinationConnectionName"
                ), $null, [System.Management.Automation.ErrorCategory]::InvalidArgument, $null
            )
        )
    }

    If((TestConnectionName -ConnectionName $SourceConnectionName) -and (TestConnectionName -ConnectionName $DestinationConnectionName)) {
        If($BatchTimeout -lt 0) { $BatchTimeout = $script:Connections.$SourceConnectionName.CommandTimeout }
        Try {
            If($PSCmdlet.ParameterSetName -eq "table") {
                If([string]::IsNullOrWhiteSpace($DestinationTable)) { $DestinationTable -eq $SourceTable }
                If($ColumnMap -and $ColumnMap.Count -gt 0) { $SourceQuery = "SELECT {0} FROM $SourceTable" -f $ColumnMap.Keys -join ", " }
                Else { $SourceQuery = "SELECT * FROM $SourceTable" }
            }
            Else { [string]$SourceQuery = $SourceQuery -join [System.Environment]::NewLine }
            
            If(-not $SourceParameters) { $SourceParameters = @{} }
            $srcReader = $script:Connections.$SourceConnectionName.GetReader($SourceQuery, $BatchTimeout, $SourceParameters)
            If($Notify.IsPresent) {
                $script:Connections.$DestinationConnectionName.BulkLoad($srcReader, $DestinationTable, $ColumnMap, $BatchSize, $BatchTimeout, {
                        Param([int]$insertCount)
                        Write-Progress -Activity "SimplySql BulkCopy" -Status $DestinationTable -CurrentOperation "Inserted $insertCount rows."
                    }.GetNewClosure())
                Write-Progress -Activity "SimplySql BulkCopy" -Status "Finished" -Completed
            }
            Else { $script:Connections.$DestinationConnectionName.BulkLoad($srcReader, $DestinationTable, $ColumnMap, $BatchSize, $BatchTimeout, $null) }
        }
        Finally {
            If(Test-Path variable:srcReader) { $srcReader.Dispose() }
        }
    }
    Else { Throw [System.Management.Automation.PSArgumentException]::new("Invalid Connection Name(s).", "SourceConnectionName, DestinationConnectionName") }
}

Export-ModuleMember -Function Invoke-SqlBulkCopy