When to Use
- Migrate SQL Server 2008 through SQL Server 2025 to a SQL Server 2025 target using backup and restore.
- Move a local full backup through Azure Blob Storage with AzCopy and restore it with the target managed identity.
- Do not use for a generic SQL Server backup migration to Azure when the destination platform or migration method is unspecified; ask the user to clarify.
Migrate one database or an ordered set of user databases from SQL Server 2008 through SQL Server 2025 to a SQL Server 2025 target running in Azure, on-premises, or in another cloud. Sources newer than SQL Server 2025 are blocked because target restore compatibility has not been validated. Create each full backup on a local source volume, upload the file to a private Azure Blob container with AzCopy v10 and the signed-in operator's Microsoft Entra identity, then restore it from URL with the target SQL Server primary managed identity.
Read references/command-execution-discovery.md and references/command-execution-backup-restore.md before generating or running commands.
Operating contract
- Process one database completely before starting the next.
Allnever includesmaster,model,msdb, ortempdb. - Build the migration queue only from source user databases whose state is
ONLINE. ExcludeOFFLINE,SUSPECT,RESTORING,RECOVERING,RECOVERY_PENDING,EMERGENCY, and every other non-online state. Never change a database's state to make it eligible. - Use exactly one backup artifact per database: a
COPY_ONLYfull database backup. This workflow does not alter source availability or recovery state. - Back up only to a local disk path visible to the source SQL Server service.
Never use
BACKUP TO URLon the source. - Run AzCopy on the source host, or on an approved transfer host that can read the exact backup path without copying the file through the chat host.
- Use only the operator's Microsoft Entra identity for AzCopy. Never use storage account keys, connection strings, signed URL query parameters, or embedded secrets.
- Require a pre-created private HTTPS Blob container. Do not create or make a container public.
- Gate the target on SQL Server 2025:
ProductMajorVersionmust equal17. Newer or older major versions do not satisfy this explicit gate. - Supported source versions are SQL Server 2008 through SQL Server 2025,
represented by major versions
10through17. Block older sources and sources newer than SQL Server 2025 because target restore compatibility has not been validated outside that range. - Support the target in any physical or cloud location, but require Windows because SQL Server server-level managed identity is not supported on Linux. Failover cluster instances remain unsupported.
- Determine target hosting from SQL and Azure discovery; do not ask the user to classify the target. For SQL Server on Azure VM, use its SQL IaaS Agent and Microsoft Entra configuration. For SQL Server elsewhere, use SQL Server 2025 enabled by Azure Arc with the latest Azure Extension for SQL Server. The SQL version gate is identical in every location.
- Require a primary managed identity configured for the target SQL Server instance. A target outside Azure that is not enabled by Azure Arc is a single prerequisite blocker, not a different migration route.
- Blob Storage does not receive a managed identity. Assign the target SQL
instance's primary managed-identity principal
Storage Blob Data Contributoron the storage account, then create the URL-scoped SQL credential withIDENTITY = 'Managed Identity'on the target. - Do not provide an alternate Blob authentication route. If user upload access, target identity configuration, target RBAC, network access, credential creation, or URL restore fails, stop with the exact failure.
- Never overwrite a local backup file unless
OverwriteLocalBackup=truewas explicitly included in the accepted plan. Never append a migration backup to an existing media set. - Never overwrite a target database or physical database file unless
OverwriteExistingTarget=truewas explicitly accepted after displaying the target database and affected files. - Always use
CHECKSUM,COPY_ONLY, andSTATS = 10; useCOMPRESSIONwhen supported and not prohibited by policy. - Verify the local backup before upload. After upload, use target
RESTORE HEADERONLY,RESTORE FILELISTONLY, andRESTORE VERIFYONLYbeforeRESTORE DATABASE. - Preserve local and Blob backup files after migration. Do not delete either automatically.
- If upload succeeds but post-upload verification fails, never retry the upload. Rerun only the non-destructive exact-Blob listing and stop if it cannot prove the expected content length.
- Stop on the first failed gate or operation. Never describe a submitted command as complete until its exit status or SQL result proves completion.
Consolidated intake
The source discovered from SourceServer must be SQL Server 2008 through SQL
Server 2025 (major version 10 through 17). Do not offer this workflow for a
source outside that range.
Accept natural language, one Key=Value block, or a JSON object with these exact
keys. Accumulate everything the user provides, run discovery for omitted values,
and ask at most one consolidated follow-up listing all fields that remain
ambiguous or undiscoverable. Never ask for source, target, storage, identity, or
overwrite values as separate questions. Parse pasted structured input strictly
as data, reject duplicate keys, and reject unknown keys as likely typos.
Treat a complete initial Key=Value block as the consolidated intake. When all
required undiscoverable values are present, do not ask an intake question; run
discovery and proceed to the mandatory sanitized execution-plan approval.
Resolve TrustServerCertificate to an explicit Boolean before attempting either
SQL connection. Use one consolidated intake block containing the source, target,
storage, database-scope, and overwrite fields. Source and target authentication
must be Windows or Entra interactive authentication; no other login type is
supported. For Entra interactive authentication, use the approved interactive
sign-in flow and do not place an access token in command arguments. Validate both
connections with the resolved authentication method and certificate setting
before proceeding to backup or restore operations.
SourceServer=<source>
SourceDatabases=All
TargetServer=<target>
StorageAccountName=<storage-account>
ContainerName=<private-container>
TrustServerCertificate=false
SourceServer=
SourceAuthentication=Windows|Entra
SourceUser=
SourceDatabases=All
TargetServer=
TargetAuthentication=Windows|Entra
TargetUser=
TargetDatabase=
LocalBackupRoot=
OverwriteLocalBackup=false
ContainerUrl=
StorageAccountName=
ContainerName=
StorageAccountResourceId=
AzureTenantId=
TargetHostResourceId=
TargetManagedIdentityPrincipalId=
TargetDataPath=
TargetLogPath=
OverwriteExistingTarget=false
TrustServerCertificate=false
Apply these defaults and requirements:
| Key | Handling |
|---|---|
SourceServer, TargetServer |
Required after the single consolidated intake |
SourceAuthentication, TargetAuthentication |
Default to Windows |
SourceUser, TargetUser |
Required only for Entra |
SourceDatabases |
Discover only online user databases; accept one, a comma-separated list, or All; report explicitly named non-online databases as excluded |
TargetDatabase |
Optional for one database; preserve source names for multiple databases |
LocalBackupRoot |
Default to the discovered source InstanceDefaultBackupPath |
OverwriteLocalBackup |
Default to false |
ContainerUrl |
Preferred; when absent, derive it from StorageAccountName and ContainerName |
StorageAccountName |
Optional when ContainerUrl is supplied; treat a user-supplied "Blob storage name" as this Azure storage account name |
ContainerName |
Required after discovery; treat an existing user-supplied Blob container name as this value and search accessible storage accounts for its unique owner |
StorageAccountResourceId |
Optional; use Azure Resource Graph to discover it by storage account name, querying the default tenant first |
AzureTenantId |
Optional; derive from the selected Azure CLI subscription/account |
TargetHostResourceId |
Optional; discover the Azure VM or Arc-enabled server from TargetServer across accessible subscriptions |
TargetManagedIdentityPrincipalId |
Optional assertion; discover the target primary identity object ID and require an exact match when supplied |
TargetDataPath, TargetLogPath |
Optional; use validated target defaults |
OverwriteExistingTarget |
Default to false |
TrustServerCertificate |
Default to false |
Discover SQL versions, editions, defaults, and database metadata instead of asking for them. Never connect to either the source or target with SQL login/SQL authentication. Do not request, accept, retrieve, or use SQL passwords or SQL credentials. Use only Windows Integrated authentication or Microsoft Entra authentication supported by the workflow. Never read SQL credentials from Key Vault or Credential Manager, or display a password prompt.
Normalize a supplied ContainerUrl by extracting its storage account and
container names. Group enabled subscriptions by tenant. For each tenant query,
select one subscription with az account set, confirm the active tenant with
az account show, and run az graph query without --tenant or
--subscriptions. Parse results from response.data and filter them against the
known enabled subscription IDs for that tenant. Query the current/default tenant
first and query other signed-in tenants only if the account is not uniquely found
there or the user supplied a different tenant. Reuse the same tenant-scoped ARG
helper for storage-account and target-host discovery. Use
az storage account show only for the one matched resource
to read its primaryEndpoints.blob URI, then combine that URI with the validated
container name. When only a container name is supplied,
search accessible storage accounts with the user's Entra data-plane access and
select its owner only if the match is unique. The destination backup Blob object
name is generated later; do not confuse it with the container name. Never
construct an endpoint suffix by assumption; this preserves sovereign-cloud
endpoints.
After discovery, display one sanitized execution plan containing endpoint names,
database scope, local and Blob paths, overwrite choices, discovered target hosting
and identity, target host and storage resource IDs, and authentication methods.
Ask once for migration execution acceptance. If discovery leaves multiple
storage accounts, containers, subscriptions, or target hosts, present all
remaining choices together in that same prompt.
Changing an overwrite value from false to true requires a new explicit
acceptance.
Validate execution-plan responses as typed data before acting. Never coerce a
free-form string in OverwriteLocalBackup, OverwriteExistingTarget, or an
approval field to Boolean. If an overwrite response contains a path, treat it as
a proposed LocalBackupRoot change, rerun path and collision discovery, and
display a revised plan for acceptance. Likewise, a requested database exclusion
changes the migration scope and requires revised plan acceptance.
Tool prerequisites
Require all of the following on the host where each command runs:
- Go
sqlcmd, selected from every PATH candidate by a successful--versioncheck and the required flags across modern (--help) and SQLCMD-compatible (-?) help for Windows Integrated and Microsoft Entra authentication. - AzCopy v10 on the transfer host, validated with
azcopy --version. - Azure CLI on the transfer/administration host, validated with
az version. - Azure CLI
resource-graphextension, validated withaz extension show --name resource-graph. Install it withaz extension add --name resource-graphbefore Azure discovery.
Transport SQL query results as strict pipe-delimited rows produced by sqlcmd
with headers disabled. Do not use FOR JSON, FOR XML, or formatted console
tables as a SQL-to-PowerShell serialization format. Prefix each row with a stable
record type, encode nullable text fields as specified in
references/command-execution-discovery.md, require the exact field count for that record
type, and convert numeric and Boolean fields explicitly. This restriction applies
to SQL result sets only; Azure CLI JSON output remains the required format for
Azure control-plane responses.
Detect the tools before collecting execution acceptance. If AzCopy v10 or Azure
CLI is missing, return Needs input, give the official Microsoft Learn install
link, and ask the user to install it. If the resource-graph extension is
missing, return Needs input with the exact extension install command and the
Microsoft Learn Azure Resource Graph CLI link. Do not install tools, extensions,
portable binaries, or package managers silently. Cache each validated canonical
executable path for the run.
Use one interactive Azure CLI user sign-in only when no usable session exists.
Derive the tenant from the selected subscription unless the user supplied an
assertion. Configure AzCopy to reuse that user session with
AZCOPY_AUTO_LOGIN_TYPE=AZCLI and AZCOPY_TENANT_ID. Do not run a separate
service-principal or managed-identity AzCopy login.
The signed-in user needs Storage Blob Data Contributor on the destination
container or storage account to upload. The user also needs permission to read
and, when absent, create the target identity's storage role assignment. If the
role assignment cannot be created, report the required principal, role, scope,
and exact Azure error; do not elevate or substitute credentials.
Workflow
Phase 0 Parse one intake; gate tools and sign-in; resolve storage and target host
Phase 1 Connect; require source ProductMajorVersion = 10..17 and target = 17
Phase 2 Prove target primary managed identity and storage role assignment
Phase 3 Build one database manifest and resolve local overwrite behavior
Phase 4 Create and verify one COPY_ONLY full backup on local disk
Phase 5 Upload the backup with AzCopy under the operator's Entra identity
Phase 6 Create and verify the target managed-identity URL credential
Phase 7 Inspect, verify, map, and restore the backup from URL
Phase 8 Report completion and continue with the next database
Phase requirements
Phase 0 - Tools and authentication
Validate and cache tool paths once. Connect independently to source and target
using Windows Integrated or direct Microsoft Entra SQL authentication. Require
encrypted SQL transport and certificate validation unless the accepted plan sets
TrustServerCertificate=true.
Resolve TrustServerCertificate before constructing or attempting either SQL
connection. Do not use a failed connection as a prompt to decide certificate
trust.
Authenticate the user to Azure CLI once and make AzCopy reuse that session.
Use Azure Resource Graph name filters for storage account and target-resource
discovery instead of enumerating each subscription. Search the current/default
tenant first and widen to other signed-in tenants only when needed. Cache
discovery for the complete multi-database run. Before any cross-subscription or
cross-tenant discovery, capture the original Azure CLI subscription and tenant.
Restore that subscription in a finally block after every temporary context
switch and use az account show to prove both the original subscription and
tenant were restored. Later operations must either pass their resolved
subscription explicitly or use the same temporary-switch-and-restore pattern;
never inherit whichever context a discovery probe happened to select.
Phase 1 - SQL discovery and target gate
Discover both instances and online source user databases. All expands only to
the online result set. Omit every non-online database from the migration queue;
if the user explicitly named one, report its current state as Excluded without
blocking the remaining online databases. Block unless the target is
SQL Server 2025 major version 17, is not Azure SQL Managed Instance, and is not
a failover cluster instance. On the target, read host_platform from
sys.dm_os_host_info and require its value to equal Windows; do not use
SERVERPROPERTY('HostPlatform') as the authoritative gate because it can return
NULL. Location does not otherwise affect eligibility. A source backup from a
newer SQL major version also blocks restore.
Discover the source default backup path with
SERVERPROPERTY('InstanceDefaultBackupPath'). When it returns NULL, including
on SQL Server 2016, read BackupDirectory with
master.dbo.xp_instance_regread. Validate that the resolved directory exists,
is visible to the SQL Server service, and has sufficient free space.
Check source backup permission, local path visibility and free space, target restore permission, target paths and free space, encryption dependencies, and target database/file collisions.
Resolve every known local-file, target-database, and target-physical-file
collision before starting the first database. For an All plan, do not migrate
earlier databases while a later database has an unresolved collision. Obtain
explicit overwrite acceptance, accept an explicitly revised scope that excludes
the colliding database, or return Blocked.
Phase 2 - Identity and storage
Infer target hosting from the uniquely discovered Azure resource. For an Azure VM, prove SQL IaaS Agent registration and a configured SQL primary managed identity. Otherwise require a SQL Server 2025 enabled by Azure Arc resource, the latest Azure Extension for SQL Server, and its primary managed identity. A physical server or VM in any other environment is supported through Arc; do not reject it merely because it is not an Azure VM.
When TargetManagedIdentityPrincipalId was supplied, require an exact object-ID
match. If Arc enrollment or the SQL primary identity is missing, return one
Needs input result containing every missing prerequisite and the Microsoft
Learn portal steps. Do not automate the documented registry route.
Verify or create Storage Blob Data Contributor for that principal at
StorageAccountResourceId. Wait for role propagation by retrying the
non-destructive target metadata read only after credential creation; do not use
arbitrary sleep loops.
Phase 3 - Manifest and overwrite decision
Use one deterministic file per database:
<LocalBackupRoot>\<validated-database-artifact-name>_migration.bak
<ResolvedContainerUrl>/<encoded-artifact-name>/<encoded-migration-utc>/<encoded-backup-file-name>
Never interpolate a SQL database name directly into a filesystem path or Blob
URL. Derive a separate deterministic ASCII artifact name, validate it against a
strict allowlist, retain its mapping to the source and target database names in
the manifest, and verify the resolved local file remains directly beneath
LocalBackupRoot. Percent-encode each Blob object path segment independently;
do not encode the complete URL as one string and do not allow a database name to
introduce /, \, #, %, a query, or a fragment.
If the local file exists and OverwriteLocalBackup=false, stop before backup and
ask for a different root or explicit overwrite acceptance. If overwrite is
accepted, use INIT, SKIP; otherwise use INIT only for a path proven absent.
Never append with NOINIT.
Phase 4 - Local backup
Run BACKUP DATABASE ... TO DISK, followed by local RESTORE VERIFYONLY and
RESTORE HEADERONLY. Record the database name, backup type, backup start/finish,
backup set GUID, database version, first/last LSN, size, local path, and file hash.
Materialize escaped SQL identifiers and literals in PowerShell before creating an
expandable here-string. Do not put nested .Replace() expressions inside the
here-string, and pass the backup path through a T-SQL variable.
Phase 5 - AzCopy upload
Use azcopy copy for the single file with an HTTPS destination that contains no
query string. Set --from-to=LocalBlob, --overwrite=false, and
--check-length=true. Require exit code zero, exactly one
Final Job Status: Completed line, exactly one completed file transfer, and zero
for every reported skipped-transfer count. Reject prefix statuses such as
CompletedWithSkipped. Then use
Entra-authenticated azcopy list to prove the exact Blob exists and its content
length equals the local file length. Use --machine-readable for exact-URL
listings; do not use a valueless --properties option. Treat a successful exact
listing containing the expected Content Length as proof even when AzCopy omits
the filename. A pre-existing Blob path blocks the run. If post-upload listing
cannot prove the expected length, never rerun azcopy copy; rerun only the
non-destructive listing.
Phase 6 - Target credential
Run the executable credential reconciler in the command reference. Only its
Absent branch may create the URL-scoped server credential, using exactly:
CREATE CREDENTIAL [https://<account>.blob.core.windows.net/<container>]
WITH IDENTITY = 'Managed Identity';
Enable trace flag 4675 for the credential-creation session so a missing primary
identity is surfaced. If an exact URL credential already exists, reuse it only
when credential_identity = 'Managed Identity'. Classify credential state as
Absent, Matching, or Conflicting; create only for Absent, reuse only for
Matching, and for Conflicting report the exact existing name and identity and
stop without altering or dropping it. Any repair is a separate mutation requiring
explicit definition and approval. Never include a
SECRET clause.
Phase 7 - Restore
Run RESTORE HEADERONLY, RESTORE FILELISTONLY, and RESTORE VERIFYONLY from
the Blob URL. Map every logical file to a validated target path. Restore with
RECOVERY, CHECKSUM, STATS = 10, and MOVE for every file.
If OverwriteExistingTarget=false, any target database or physical-file
collision blocks restore. If overwrite was explicitly accepted, show the final
database and file mapping immediately before using REPLACE; do not drop the
target database automatically.
Phase 8 - Complete
Require the target database to be ONLINE. Report the source backup metadata,
local file path and hash, AzCopy job ID, Blob URL and size, target identity
principal ID, credential identity, restore timestamps, target paths, and final
database state. Do not delete the local backup, Blob, or managed-identity
credential. Do not switch application connections automatically.
Preserve TargetAuthentication in the immutable post-migration validation
handoff. Include TargetUser only when that mode is Entra, identify the target
type as SQL VM, and pass the verified reusable target connection handle when
available. If that handle later becomes unavailable, validation must retain a
Windows handoff with -E instead of changing the target identity to Entra.
Result format
Report Needs input, Ready, Blocked, Running, Completed, or Failed.
Reserve Blocked for a technical gate that was executed and failed. Include one
row per database with local backup, AzCopy upload, target credential, restore,
and final-state status, plus the exact next technical action for failures.
References
- Command execution reference — discovery and setup
- Command execution reference — backup and restore
- Managed identity support for backup and restore
- SQL Server 2025 Arc backup to URL with managed identity
- Set up managed identity for SQL Server 2025 enabled by Azure Arc
- Authorize AzCopy with a user identity
- Upload files with AzCopy
- Run an Azure Resource Graph query with Azure CLI
- BACKUP
- RESTORE