sqlpackage

Status: Reviewed Stack: PowerShell → SQL / stored procs → Blazor Depends on: SQL-CLI-01 helpful for post-deploy verify; SQL-CLI-03 for Backup-SqlDatabase alternatives Goal: Use sqlpackage from the CLI for extract/publish/script/export/import of DACPAC/BACPAC in ways that fit PowerShell deploy pipelines feeding SQL → Blazor.


1. When to use sqlpackage vs SSMS Deploy vs Backup/Restore vs bcp vs manual scripts

Rick’s loop: AD/Windows/PowerShell → SQL stored procs → Blazor Server. sqlpackage is the DacFx CLI: schema-as-artifact (.dacpac) and portability (.bacpac). It is not a backup tool and not a bulk loader.

Need sqlpackage SSMS Deploy / DAC UI Backup / Restore (SQL-CLI-03) bcp (SQL-CLI-02) Manual .sql / sqlcmd (SQL-CLI-01)
Incremental schema deploy from SSDT / SQL project ✅ Publish ✅ Same engine, GUI Possible; no model diff
Review plan before apply (CI gate) ✅ Script / DeployReport Preview in UI N/A N/A Diff by eye
Capture prod schema → lab ✅ Extract → Publish Extract Data-tier App Full restore (heavy) Script objects by hand
Move schema + data to another server/cloud ✅ Export / Import (BACPAC) Export Data-tier App ✅ Native BAK (same edition family) Partial Painful
Point-in-time / disaster recovery ✅ Prefer
Multi-GB inventory CSV → staging
Drop/recreate empty lab DB from model ✅ Publish + CreateNewDatabase Yes Restore over DROP/CREATE scripts
Detect drift vs last registered DAC ✅ DriftReport Limited Custom
Agent / CI exit codes $LASTEXITCODE Manual Cmdlet exceptions -b

Rule of thumb

DACPAC vs BACPAC (memorize this)

Artifact Default contents Primary actions Use for
.dacpac Schema only (data optional via Extract props) Extract, Publish, Script, DeployReport Deploy / promote schema
.bacpac Schema + data Export, Import Portability / copy to empty DB

Files are compressed, not encrypted. Treat both as sensitive.


2. Install on Windows (2026)

Official Learn (sql-server-ver17 / June 2026): latest build 170.4.83.3 (3 Jun 2026). Prefer standalone sqlpackage over the copy bundled with SSMS/VS — release cadence is faster and pipelines stay predictable.

Recommended: .NET global tool

Requires .NET SDK 8+ (Learn recommends the .NET 10 SqlPackage line).

dotnet --list-sdks
dotnet tool install -g microsoft.sqlpackage
# pin when you care:
# dotnet tool install -g microsoft.sqlpackage --version 170.4.83.3
dotnet tool update -g microsoft.sqlpackage
sqlpackage /Version

If NuGet can’t find the package: ensure nuget.org is a source (dotnet nuget list source). Newer SDK without matching runtime: add --allow-roll-forward.

winget (MSI path)

winget search SqlPackage
winget install --id Microsoft.SqlPackage --exact
# or pin:
# winget install --id Microsoft.SqlPackage --exact --version 170.4.83.3
# silent (Intune / SCCM style):
# winget install --id Microsoft.SqlPackage --exact --silent --accept-package-agreements --accept-source-agreements

MSI (.NET Framework) / ZIP (.NET 10 self-contained)

Method Notes Typical path / use
dotnet tool Best for CI + jump boxes; easy update On PATH as sqlpackage
winget → MSI Machine-wide; good for shared DBA hosts C:\Program Files\Microsoft SQL Server\170\DAC\bin\SqlPackage.exe
DacFramework.msi Evergreen: https://aka.ms/dacfx-msi Same ...\170\DAC\bin
Windows .NET 10 ZIP No SDK; unpack anywhere Evergreen: https://aka.ms/sqlpackage-windows
# Verify
sqlpackage /Version
# or full path after MSI:
& "C:\Program Files\Microsoft SQL Server\170\DAC\bin\SqlPackage.exe" /Version
Get-Command sqlpackage -ErrorAction SilentlyContinue | Format-List Source, Version

Pipeline tip: install at job start with dotnet tool install -g microsoft.sqlpackage rather than depending on image-bundled copies (ubuntu-latest no longer guarantees it).


3. Actions — what matters for Rick

Action Artifact What it does When Rick uses it
Extract .dacpac Reverse-engineer connected DB schema (data off by default) Snapshot prod/lab schema; feed SQL projects; baseline before change
Publish .dacpac → DB Incremental desired-state deploy; creates DB if missing + permissions Deploy procs/tables that Blazor calls; promote lab → UAT
Script .dacpac + target → .sql T-SQL incremental script without applying Code review / change ticket / “what will run?”
DeployReport .xml Machine-readable plan of Publish changes CI artifact; parse for Drop/Alter warnings
DriftReport .xml Diff vs last registered DAC on that DB Catch hotfixes applied outside pipeline (RegisterDataTierApplication)
Export .bacpac Schema + data from live DB Copy small/medium DB to another environment (not DR)
Import .bacpacnew DB Create DB and load schema+data Spin lab from bacpac; Azure SQL portability

Ops notes


4. Auth & connection

SqlPackage uses Microsoft.Data.SqlClient auth. Prefer /SourceConnectionString + /TargetConnectionString (or short forms /scs / /tcs) so Encrypt/Trust settings live in one place.

Mode How Notes
Windows Integrated Omit user/password; run as domain account Default on-prem AD. Best for Rick’s jump box → SQL.
SQL auth User ID= / Password= in connection string, or /tu /tp Prefer secret store; avoid plaintext in scripts committed to git.
Entra password Authentication=Active Directory Password; Interactive/automation with UPN + password.
Entra integrated Authentication=Active Directory Integrated; Domain-joined + Entra sync scenarios.
Managed identity Authentication=Active Directory Managed Identity; Preferred in Azure Pipelines / MI-enabled hosts. Optional User Id=<client-id> for user-assigned.
Service principal Authentication=Active Directory Service Principal; User Id=<appId>; Password=<secret> or /at:<token> Token path: you own refresh; SP-in-string lets SqlPackage refresh.
Universal / MFA /UniversalAuthentication:True (+ /TenantId for guests) Interactive; poor fit for unattended CI.

TLS (do this explicitly)

Setting Guidance
Encrypt=True / /tec:Mandatory Default expectation for Azure SQL and hardened on-prem.
TrustServerCertificate=False Prod / Azure.
TrustServerCertificate=True / /ttsc:True Lab self-signed only.
HostNameInCertificate= When you connect by short name / alias under Force Encryption.
Entra timeouts /tt:30 or higher recommended.
# Windows Integrated — on-prem
$tcs = "Server=SQLPROD01;Database=AppDb;Integrated Security=True;Encrypt=True;TrustServerCertificate=False"

# SQL auth
$tcs = "Server=SQLPROD01;Database=AppDb;User ID=deploy;Password=$env:SQL_DEPLOY_PWD;Encrypt=True;TrustServerCertificate=False"

# Azure SQL + managed identity
$tcs = "Server=tcp:myapp.database.windows.net,1433;Initial Catalog=AppDb;Authentication=Active Directory Managed Identity;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30"

Cross-link: for ad-hoc verify after publish, use sqlcmd or Invoke-Sqlcmd (SQL-CLI-03) — same Encrypt/Trust discipline.


5. Practical properties & flags cheat sheet (ops-focused)

Syntax: SqlPackage {parameters} {properties } {SQLCMD variables}

Core parameters

Flag Short Purpose
/Action: /a: Extract, Publish, Script, Export, Import, DeployReport, DriftReport
/SourceFile: /sf: Input .dacpac (Publish/Script) or.bacpac` (Import)
/TargetFile: /tf: Output .dacpac / .bacpac / report path as applicable
/SourceConnectionString: /scs: Live source DB
/TargetConnectionString: /tcs: Live target DB
/SourceServerName: /SourceDatabaseName: /ssn /sdn Alternate to full SCS (plus user/pass as needed)
/TargetServerName: /TargetDatabaseName: /tsn /tdn Alternate to full TCS
/AccessToken: /at: Entra bearer token (omit User Id/Password in CS)
/Profile: /pr: Publish profile (.xml) with properties/variables
/DiagnosticsFile: /df: Verbose log file for failures
/Quiet: /q: Less console noise (CI)
/OverwriteFiles: /of: Allow overwrite of outputs
/DeployScriptPath: /dsp: Script output path (Script action or alongside Publish)
/DeployReportPath: /drp: DeployReport XML path (DeployReport action or alongside Publish)
/OutputPath: /op: Generic output path (Script / DeployReport / DriftReport)
/Variables: /v:Name=Value SQLCMD variables for pre/post scripts in dacpac
/Version Print build number

Publish properties you will actually touch

Property Default Ops meaning
/p:BlockOnPossibleDataLoss True Keep True in shared envs. Blocks type changes / drops that might lose data (even if table empty). Set False only with eyes open.
/p:DropObjectsNotInSource False True = make target match dacpac (drops extras). Dangerous without exclusions.
/p:DoNotDropObjectTypes Semicolon list preserved when DropObjectsNotInSource=True (e.g. Users;Logins;Permissions;RoleMembership)
/p:ExcludeObjectTypes Ignore types entirely during compare/deploy. Quote in PowerShell ("Users;Logins") — ; is a PS statement separator.
/p:CreateNewDatabase False Drop + recreate target (lab reset). Not for prod.
/p:BackupDatabaseBeforeChanges False On-prem only; not Azure SQL. Prefer real backup jobs (SQL-CLI-03).
/p:GenerateSmartDefaults False Helps NOT NULL column adds on tables with data.
/p:ScriptDatabaseOptions True Often exclude DatabaseOptions if env-specific.
/p:VerifyDeployment True Leave on; disable only when debugging.
/p:AllowIncompatiblePlatform False Rare; Azure ↔ on-prem edge cases.
/p:CommandTimeout 60 Raise for big publishes.
/p:RegisterDataTierApplication False Set True if you plan to use DriftReport later.
/p:IgnorePermissions / IgnoreRoleMembership / IgnoreUserSettingsObjects varies Common when security is managed outside the dacpac.

Extract data knobs (prefer BACPAC for full data moves): /p:ExtractAllTableData=True or repeated /p:TableData=schema.table.

Export size: /p:TempDirectoryForTableData=D:\sqlpkg-temp when TEMP is small.


6. PowerShell orchestration patterns

sqlpackage is an exe → use $LASTEXITCODE (same discipline as sqlcmd / bcp). Do not assume throw-on-failure.

function Invoke-SqlPackage {
    param([Parameter(Mandatory)][string[]]$ArgumentList)
    & sqlpackage @ArgumentList
    if ($LASTEXITCODE -ne 0) {
        throw "sqlpackage failed with exit code $LASTEXITCODE"
    }
}

CI-friendly publish

  1. Install tool (dotnet tool install -g microsoft.sqlpackage).
  2. Script or DeployReport → publish artifact for review.
  3. Publish with identical /p: set + /df:sqlpackage.log + /q:True.
  4. Verify with sqlcmd/Invoke-Sqlcmd (row counts, proc smoke tests).
  5. Fail the job on non-zero exit.

Extract prod → publish lab

prod (Extract) → AppDb.dacpac → lab (Publish, CreateNewDatabase or empty DB)
                 └─ Script against lab for human review (optional gate)

Exclude users/logins/permissions when lab auth ≠ prod.

Script for review before apply

Invoke-SqlPackage @(
  '/Action:Script'
  "/SourceFile:$dacpac"
  "/TargetConnectionString:$labCs"
  "/DeployScriptPath:$outSql"   # /dsp — also accepts /OutputPath
  '/p:BlockOnPossibleDataLoss=True'
)
# Apply later:
# sqlcmd -S ... -d ... -b -i $outSql   # SQL-CLI-01

Publish can emit the same artifacts without applying-only mode via /dsp: (script) and /drp: (report) alongside /Action:Publish.

Exit codes: treat anything non-zero as failure. Capture /DiagnosticsFile on failure paths in CI.


7. Gotchas

Gotcha Reality
Data loss blocks BlockOnPossibleDataLoss=True fires on possible loss (column type change, drop column, etc.) even on empty tables. Don’t casually set False in shared UAT.
Users / logins / permissions Contained users ship in dacpac/bacpac; passwords are replaced with random values — reset after Import/Publish. Prefer excluding Users/Logins/Permissions/RoleMembership and managing security separately (AD groups).
DropObjectsNotInSource Will drop hotfix objects not in the model. Pair with DoNotDropObjectTypes or keep False and delete deliberately.
PowerShell ; /p:ExcludeObjectTypes=Users;Logins breaks in PS. Use quotes: "/p:ExcludeObjectTypes=Users;Logins".
Temporal tables History table / period changes can force table rebuilds; expect longer publishes and data-motion checks.
Always Encrypted Keys/attestations needed (EnclaveAttestationUrl, AKV auth props). Extract/Publish without key access fails or strips.
Azure SQL firewall Client IP (or agent outbound IPs) must be allowed; MI/SP still need network path.
BACPAC ≠ backup No differential, no log chain, weaker consistency story. Use Backup-SqlDatabase for DR.
DACPAC ≠ bulk load Not a substitute for bcp inventory CSVs.
Large Export Slow / temp-disk hungry; quiesce writers; consider size limits (~200 GB guidance).
SSMS-bundled sqlpackage Stale. Pin standalone 170.x in pipelines.
Import destination Designed for new database — don’t expect merge semantics into busy prod.
Telemetry Opt out: DACFX_TELEMETRY_OPTOUT=1 if policy requires.

8. Copy-paste recipes

1) Extract DACPAC (schema only) from prod

$src = "Server=SQLPROD01;Database=AppDb;Integrated Security=True;Encrypt=True;TrustServerCertificate=False"
sqlpackage /Action:Extract `
  /SourceConnectionString:$src `
  /TargetFile:"D:\dac\AppDb.dacpac" `
  /p:VerifyExtraction=True `
  /OverwriteFiles:True
if ($LASTEXITCODE -ne 0) { throw "Extract failed: $LASTEXITCODE" }

2) Publish DACPAC to lab (safe defaults)

$tgt = "Server=SQLLAB01;Database=AppDb;Integrated Security=True;Encrypt=True;TrustServerCertificate=True"
sqlpackage /Action:Publish `
  /SourceFile:"D:\dac\AppDb.dacpac" `
  /TargetConnectionString:$tgt `
  /p:BlockOnPossibleDataLoss=True `
  /p:DropObjectsNotInSource=False `
  "/p:ExcludeObjectTypes=Users;Logins;Permissions;RoleMembership;ExtendedProperties" `
  /DiagnosticsFile:"D:\dac\publish-lab.log" `
  /Quiet:True
if ($LASTEXITCODE -ne 0) { throw "Publish failed: $LASTEXITCODE" }

3) Generate deploy script for review (no apply)

sqlpackage /Action:Script `
  /SourceFile:"D:\dac\AppDb.dacpac" `
  /TargetConnectionString:$tgt `
  /DeployScriptPath:"D:\dac\AppDb-upgrade.sql" `
  /p:BlockOnPossibleDataLoss=True `
  "/p:ExcludeObjectTypes=Users;Logins;Permissions;RoleMembership"
if ($LASTEXITCODE -ne 0) { throw "Script failed: $LASTEXITCODE" }
# Review AppDb-upgrade.sql, then apply via SQL-CLI-01 sqlcmd -b -i ...

4) DeployReport (CI XML artifact)

sqlpackage /Action:DeployReport `
  /SourceFile:"D:\dac\AppDb.dacpac" `
  /TargetConnectionString:$tgt `
  /DeployReportPath:"D:\dac\AppDb-deploy.xml" `
  /p:BlockOnPossibleDataLoss=True
if ($LASTEXITCODE -ne 0) { throw "DeployReport failed: $LASTEXITCODE" }

5) Export / Import BACPAC (schema + data)

# Export (quiesce writes or use a copy)
sqlpackage /Action:Export `
  /SourceConnectionString:$src `
  /TargetFile:"D:\dac\AppDb.bacpac" `
  /OverwriteFiles:True `
  /p:TempDirectoryForTableData:"D:\sqlpkg-temp"
if ($LASTEXITCODE -ne 0) { throw "Export failed: $LASTEXITCODE" }

# Import into NEW database name on lab
$newCs = "Server=SQLLAB01;Database=AppDb_Copy;Integrated Security=True;Encrypt=True;TrustServerCertificate=True"
sqlpackage /Action:Import `
  /SourceFile:"D:\dac\AppDb.bacpac" `
  /TargetConnectionString:$newCs
if ($LASTEXITCODE -ne 0) { throw "Import failed: $LASTEXITCODE" }
# Reset contained-user passwords after import

6) PowerShell wrapper with $LASTEXITCODE + diagnostics

function Invoke-SqlPackage {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][ValidateSet('Extract','Publish','Script','Export','Import','DeployReport','DriftReport')]
        [string]$Action,
        [Parameter(Mandatory)][hashtable]$Params,
        [string]$DiagnosticsFile
    )
    $args = @("/Action:$Action")
    foreach ($k in $Params.Keys) { $args += "/${k}:$($Params[$k])" }
    if ($DiagnosticsFile) { $args += "/DiagnosticsFile:$DiagnosticsFile" }

    Write-Verbose ($args -join ' ')
    & sqlpackage @args
    $code = $LASTEXITCODE
    if ($code -ne 0) {
        throw "sqlpackage $Action failed with exit code $code$(if ($DiagnosticsFile) { "; see $DiagnosticsFile" })"
    }
}

Invoke-SqlPackage -Action Publish -DiagnosticsFile 'D:\dac\pub.log' -Params @{
    SourceFile = 'D:\dac\AppDb.dacpac'
    TargetConnectionString = $tgt
    'p:BlockOnPossibleDataLoss' = 'True'
    Quiet = 'True'
}

7) Azure SQL + Entra (managed identity)

$tcs = "Server=tcp:myapp.database.windows.net,1433;Initial Catalog=AppDb;Authentication=Active Directory Managed Identity;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30"
sqlpackage /Action:Publish `
  /SourceFile:"D:\dac\AppDb.dacpac" `
  /TargetConnectionString:$tcs `
  /p:BlockOnPossibleDataLoss=True `
  "/p:ExcludeObjectTypes=Users;Logins;Permissions;RoleMembership"
if ($LASTEXITCODE -ne 0) { throw "Azure publish failed: $LASTEXITCODE" }

8) Azure SQL + access token (service principal)

# Requires Az.Accounts; audience for Azure SQL
Connect-AzAccount -ServicePrincipal -Tenant $TenantId -Credential $SpCredential | Out-Null
$token = (Get-AzAccessToken -ResourceUrl "https://database.windows.net/").Token
$tcs = "Server=tcp:myapp.database.windows.net,1433;Initial Catalog=AppDb;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30"
sqlpackage /Action:Publish `
  /SourceFile:"D:\dac\AppDb.dacpac" `
  /TargetConnectionString:$tcs `
  /AccessToken:$token `
  /p:BlockOnPossibleDataLoss=True
if ($LASTEXITCODE -ne 0) { throw "Token publish failed: $LASTEXITCODE" }

Quick reference — Rick’s defaults

Situation Command shape
Promote schema Publish + BlockOnPossibleDataLoss=True + exclude Users/Logins/Permissions
Change ticket Script / DeployReport → review → Publish or sqlcmd -b -i
Refresh lab data+schema ExportImport to new DB name (or Publish+CreateNewDatabase for schema-only reset)
DR / PITO Not sqlpackageSQL-CLI-03 Backup/Restore
Inventory CSV Not sqlpackageSQL-CLI-02
Post-deploy smoke SQL-CLI-01 / Invoke-Sqlcmd

Docs (Learn, sql-server-ver17): SqlPackage overview · Download/install · Publish · Extract · Export · Release notes 170.4.83.3