powershelldba.de
REF: PSDB-SQMDT-2026 SCOPE: SQL Server 2016+ STATUS: Production
Module

Move table data between instances without losing the constraints — or the proof.

sqmDataTransfer is a dbatools-based PowerShell module that copies chosen tables from one SQL Server instance to another: it can script the table's DDL on the target if it doesn't exist yet, disables foreign keys and non-clustered indexes for the bulk copy, re-enables them afterward — guaranteed, even on failure — and reconciles row counts between source and target. Every run writes a full log and a self-contained HTML report.

Why this exists

"Just BCP it over" skips the parts that actually go wrong.

Moving table data between instances by hand means separately handling the target DDL, disabling constraints so the bulk load doesn't choke on FK checks, remembering to re-enable everything afterward even if step 3 fails, and then manually counting rows to prove nothing was lost. sqmDataTransfer runs that whole sequence as one call.

Without itWith sqmDataTransfer
Target table DDL scripted and re-typed by hand, dependencies missedTable, dependent types, sequences, and FK-referenced tables scripted automatically
Bulk copy fails or crawls because FKs/indexes are still activeNon-clustered indexes and foreign keys disabled for the copy, every time
A failed step leaves constraints disabled with no one noticingRe-enable runs in a finally block — guaranteed, even after a failure
"I think it copied everything" — no proofExplicit source-vs-target row-count comparison in the report
Partitioned or CLR-typed tables fail the script with an opaque errorPartitioning stripped with a warning; CLR types flagged for manual assembly deploy
What one call does

Five steps, run in order, logged individually.

Invoke-sqmTableTransfer is the main entry point — it orchestrates the full sequence below for every table you pass in.

  1. Script metadata (optional)Scripts the table DDL — columns, PK, indexes, foreign keys, defaults, checks — from the source and creates it on the target if it doesn't already exist. Existing target tables are never dropped or recreated.
  2. DisableDisables foreign keys and non-clustered indexes on the target table so the bulk load isn't fighting constraint checks or index maintenance.
  3. Copy dataBulk-copies rows from source to target via dbatools' Copy-DbaDbTableData.
  4. Compare row countsCounts rows on both sides and records the comparison in the report.
  5. Re-enableRebuilds the disabled foreign keys and indexes — inside a finally block, so this step runs even if an earlier one fails.

Every step is logged to %LogPath%\sqmDataTransfer_yyyyMMdd_<FunctionName>.log and returned as a structured result object (Table, Step, Status, Message, Timestamp). A self-contained HTML report is always written at the end of the run — every processed table, anything missing or failed, and the row-count comparison — and opens automatically in the browser unless -NoOpen is passed.

Metadata scripting

The part that usually breaks a hand-written CREATE TABLE.

With -ScriptMetadata, the module doesn't just script the table — it walks the real dependency graph so the CREATE actually succeeds on the target.

SchemaThe destination schema is created automatically if it doesn't exist
DependenciesUser-defined types, sequences, and FK-referenced tables the table depends on are scripted automatically too (SMO WithDependencies)
Partitioned tablesStill fully scripted and transferred — but the physical partitioning itself (partition function/scheme, per-partition filegroups) is stripped, since there's no way to know whether an equivalent scheme exists on the target. Lands as a normal table on PRIMARY instead of failing with a missing-partition-scheme error. Reported as a warning.
CLR user-defined typesScripted, but flagged as a warning — the assembly itself has to be deployed on the target manually
Version targetingThe destination's actual SQL Server version is auto-detected and passed to SMO as TargetServerVersion, so scripting from a newer source (e.g. 2022) down to an older target (e.g. 2019) produces syntax the target can run
Reference

Eleven functions — one orchestrator, ten building blocks.

FunctionPurpose
Invoke-sqmTableTransferMain entry point — orchestrates the full five-step sequence
Export-sqmTableSchemaScripts table DDL from a source instance (SMO Scripter)
New-sqmTableFromScriptExecutes scripted DDL batches against a target instance
Copy-sqmTableSchemaConvenience wrapper: export + create in one call
Disable-sqmTableConstraintsDisables FKs / non-clustered indexes on a table
Enable-sqmTableConstraintsRe-enables (rebuilds) previously disabled FKs / indexes — re-detects state, nothing to pass in
Copy-sqmTableDataBulk-copies table data (wraps Copy-DbaDbTableData)
Compare-sqmTableRowCountCompares row counts source vs. target
Export-sqmTransferReportBuilds the HTML summary/row-count report
Show-sqmTableTransferGuiWinForms GUI for the whole workflow
Get-sqmTransferConfig / Set-sqmTransferConfigModule configuration (log path, batch size, etc.)

Clustered indexes are never disabled — doing so blocks table access entirely — only non-clustered indexes are touched. Foreign keys are disabled/enabled individually by name, leaving CHECK/DEFAULT constraints untouched.

Quick start

Install, then one call — or the GUI.

# Installation - auto-detects AllUsers (if Admin) or CurrentUser
Install.cmd

# Transfer two tables, scripting metadata and truncating the target first
Invoke-sqmTableTransfer -Source SQL01 -SourceDatabase Sales -Destination SQL02 -DestinationDatabase Sales -Table 'dbo.Orders', 'dbo.Customers' -ScriptMetadata -Truncate -Confirm:$false

# Or via GUI
Show-sqmTableTransferGui

Configuration is persisted separately from sqmSQLTool (%APPDATA%\SQLDataTransfer\config.json) so both modules can be imported side by side without interfering, but LogPath/OutputPath default to the same location sqmSQLTool uses.

Next steps

Move your first table today.

Built on dbatools — if it's already on the box, sqmDataTransfer is one Import-Module away.