sqmSQLTool · Backup & Restore · 2026
FULL + DIFF + LOG. Selective. AlwaysOn-aware.
The sqmSQLTool backup strategy: daily differentials, selective backup of individual databases, automatic AG detection, and a structured restore in one pass.
Uwe Janke · Senior SQL Server DBA · dtcSoftware
Motivation
Growing databases mean a daily FULL backup fills the entire maintenance window. No room left for integrity checks, index maintenance, or statistics updates.
7 x FULL per week = 7 x the full database size. With DIFF + LOG, Monday through Saturday only backs up the changed pages - typically 5-15% of the database size.
Temporary databases, replication targets, staging DBs, Ola helper tools: these need to be identified and explicitly excluded - not backed up by accident.
In an AG, backup jobs run on every node. Without a preferred-replica check, the backup runs multiple times - or not at all, if the active node changes.
Strategy
FULL once a week (Sunday), DIFF daily (Mon-Sat), LOG hourly. The backup window shrinks, RPO drops to <1 h.
* DIFF without a LOG backup: recovery point = last DIFF, no point-in-time possible.
Architecture
sqmSQLTool supports both approaches - native and via Ola Hallengren. Recommended: Ola for new environments.
Creates SQL Agent jobs that call Invoke-sqmUserDatabaseBackup. No external framework needed.
| BackupType | FULL | DIFF | LOG |
| Path | <BackupDir>\User-Db |
| Exclude | -UseExcludeTable |
| AlwaysOn | -CheckPreferredReplica |
-MailTo, -MailOnSuccess |
Creates jobs built on Ola Hallengren's DatabaseBackup procedure. Industry standard, extensive options.
| Switches | -Full -Diff -Log |
| Path | <BackupDir>\Usr-db |
| Exclude | -UseExcludeTable |
| Quality | -Compress, -Verify, -CheckSum |
| Logging | -LogToTable (msdb) |
New-sqmOlaUsrDbBackupJob detects that and installs the Ola scripts (DatabaseBackup, CommandLog, DatabaseIntegrityCheck, IndexOptimize) automatically on the target instance.Setup
One PowerShell call sets up the entire FULL/DIFF/LOG job structure on an instance.
# Full setup: FULL (Sun 8pm) + DIFF (Mon-Sat 8pm) + LOG (hourly)
New-sqmOlaUsrDbBackupJob -SqlInstance 'SQL01\PROD' `
-BackupDirectory 'D:\SQLBackup' `
-Full -FullScheduleTime '20:00' -FullScheduleDays @('Sunday') `
-Diff -DiffScheduleTime '20:00' -DiffScheduleDays @('Monday','Tuesday','Wednesday','Thursday','Friday','Saturday') `
-Log -LogScheduleIntervalMinutes 60 `
-UseExcludeTable `
-Compress -Verify -CheckSum -LogToTable
SQL Agent jobs created:
| Job name (configurable) | Type | Schedule | Path |
|---|---|---|---|
OlaHH-UserDatabases-FULL | FULL | Sunday 8pm | D:\SQLBackup\Usr-db |
OlaHH-UserDatabases-DIFF | DIFF | Mon-Sat 8pm | D:\SQLBackup\Usr-db |
OlaHH-UserDatabases-LOG | LOG | Hourly | D:\SQLBackup\Usr-db |
New-sqmOlaSysDbBackupJob - they don't support DIFF/LOG.Selective backup
Two approaches - combine as needed.
Directly in the function call - for ad-hoc or one-off backups.
# Back up only two databases
Invoke-sqmUserDatabaseBackup `
-SqlInstance 'SQL01' `
-Database 'Finance', 'Controlling' `
-BackupPath 'D:\SQLBackup\User-Db'
# Back up everything except the exclude table
Invoke-sqmUserDatabaseBackup `
-SqlInstance 'SQL01' `
-All `
-UseExcludeTable
A persistent exclusion list in master. Changes apply immediately to every backup job - no job changes needed.
# Initialize the table (once)
Sync-sqmBackupExcludeTable `
-SqlInstance 'SQL01'
# Exclude a database
INSERT INTO master.dbo.sqm_BackupExclude
(DatabaseName, Reason)
VALUES ('StagingDB', 'No backup - repopulated daily')
# Read access for the backup service account
Set-sqmBackupExcludePermission `
-SqlInstance 'SQL01' `
-LoginName 'DOMAIN\sqlsvc'
Selective backup - detail
The full table definition with an audit trail. Created and maintained automatically by Sync-sqmBackupExcludeTable.
| Column | Type | Meaning |
|---|---|---|
DatabaseName | sysname PK | Name of the database to exclude |
Reason | nvarchar(255) | Justification (e.g. "Staging") |
ExcludedBy | sysname | Who excluded it (SUSER_SNAME()) |
ExcludedAt | datetime2 | When (default: SYSDATETIME()) |
IsActive | bit | 1 = actively excluded, 0 = reactivated |
IsOrphaned | bit | 1 = the database no longer exists on the instance |
Every change to the exclude table is written automatically to the history table via the trg_sqm_BackupExclude_Audit trigger. A complete change history.
Regularly reads the instance's current databases and automatically flags deleted ones as IsOrphaned = 1. No manual cleanup needed.
Sync-sqmBackupExcludeTable as step 1 in the backup job. New-sqmBackupMaintenanceJob does this automatically when -UseExcludeTable is set.AlwaysOn
Backup jobs run on every AG node. Without a check, the backup runs multiple times, or on the wrong node.
| SQL01 (Primary) | Backup runs |
| SQL02 (Secondary) | Backup runs too |
| Result | Double storage use |
Invoke-sqmUserDatabaseBackup `
-SqlInstance 'SQL01' `
-All `
-CheckPreferredReplica `
-UseExcludeTable
Internally checks sys.fn_hadr_backup_is_preferred_replica() per AG database. Non-preferred replicas silently skip the database.
Both nodes have identical backup jobs.
fn_hadr_backup_is_preferred_replica('Finance') returns 1 (preferred) or 0.
SQL02 skips every AG database if SQL01 is preferred.
After failover to SQL02, SQL02 backs up - no job changes needed.
-CheckPreferredReplica accounts for this automatically: on 2025 instances, the secondary can be preferred as the backup node, taking the load off the primary entirely.Quality
Test-sqmBackupIntegrity - RESTORE VERIFYONLY without an actual restore. Catches corrupt backup files before they're needed.
# Check every backup file in a path
Test-sqmBackupIntegrity `
-SqlInstance 'SQL01' `
-BackupPath 'D:\SQLBackup\User-Db'
# Output the file list (header info)
Test-sqmBackupIntegrity `
-SqlInstance 'SQL01' `
-BackupPath 'D:\SQLBackup\User-Db' `
-FileListOnly
RESTORE VERIFYONLY FROM DISK = '...'. Checks checksums, file sizes, and the backup header - no actual restore.-CheckSum)-FileListOnly: inventory of every backed-up databaseRestore
The full restore workflow - from a single backup file to a FULL+DIFF+LOG chain. AlwaysOn-aware.
# Simple restore from one file
Invoke-sqmRestoreDatabase `
-SqlInstance 'SQL01' `
-BackupFile 'D:\SQLBackup\User-Db\Finance_FULL_20260518.bak'
# Restore chain: FULL + DIFF + LOG (point-in-time)
Invoke-sqmRestoreDatabase `
-SqlInstance 'SQL01' `
-BackupFiles 'D:\Backup\Finance_FULL.bak',
'D:\Backup\Finance_DIFF.bak',
'D:\Backup\Finance_LOG_10.trn',
'D:\Backup\Finance_LOG_11.trn' `
-NewDatabaseName 'Finance_Restore_Test' `
-NewDatabaseFilePath 'E:\MSSQL\DATA' `
-NewLogFilePath 'F:\MSSQL\LOG'
-BackupFiles determines the restore sequence: FULL first, then DIFF, then the LOGs in chronological order.Restore - features
-KeepAlwaysOn | Remove the DB from the AG, run the restore, then automatically add it back |
-RejoinAvailabilityGroup | Rejoin the AG only, after a manual restore (WITH NORECOVERY) |
-WithNoRecovery | Restore in NORECOVERY mode - for multi-stage restore chains |
-BackupBeforeRestore | Backs up the target database before it's overwritten - a safety net |
-ForceSingleUser | Forces single-user mode if active connections exist |
Runs automatically after the restore:
Orphaned DB users are automatically linked back to existing logins.
Windows logins that no longer exist in AD are removed from the database.
Security consistency: the database owner is set to sa.
-NoUserExport: turn off user restoration for pure restore tests.
Reference
| Task | Function |
|---|---|
| Set up backup jobs (Ola) | New-sqmOlaUsrDbBackupJob |
| Set up backup jobs (native) | New-sqmBackupMaintenanceJob |
| Back up system databases | New-sqmOlaSysDbBackupJob |
| Ad-hoc user DB backup | Invoke-sqmUserDatabaseBackup |
| Create/sync the exclude table | Sync-sqmBackupExcludeTable |
| Set exclude permission | Set-sqmBackupExcludePermission |
| Check a backup file | Test-sqmBackupIntegrity |
| Restore a database | Invoke-sqmRestoreDatabase |
| Parameter cheat sheet | Meaning |
|---|---|
-All | Every user DB (respects the exclude table) |
-Database | Explicit DB list |
-UseExcludeTable | Honor sqm_BackupExclude |
-CheckPreferredReplica | AG-aware - preferred node only |
-BackupType DIFF | Differential backup (native) |
-Diff | Create the differential job (Ola) |
-BackupFiles | Restore chain (FULL+DIFF+LOG[]) |
-BackupBeforeRestore | Safety net before overwriting |
<BackupDir>\Usr-db (Ola) vs. <BackupDir>\User-Db (native) - keep this consistent on a fresh install.Boundaries
| Prerequisite | Detail |
|---|---|
| SQL Server version | 2016 and later recommended |
| Recovery model | FULL for LOG backups (sqmSQLTool doesn't check this - a DBA task) |
| Ola Hallengren | Must be installed on the instance (the DatabaseBackup SP in msdb or master) |
| SQL Server Agent | Running and reachable |
| Backup path | The SQL Server service account has write access |
| AlwaysOn | AG configuration already set up (e.g. via AlwaysOnSetup) |
| Database Mail | Optional - for mail notifications (-MailTo) |
-- Confirm before a LOG backup:
SELECT name, recovery_model_desc
FROM sys.databases
WHERE recovery_model_desc <> 'FULL'
AND name NOT IN ('tempdb', 'model')
Summary
From zero to a running FULL+DIFF+LOG strategy in four steps.
Set every database that needs backing up to FULL. No LOG backup is possible without Full recovery.
Run Sync-sqmBackupExcludeTable on every instance. Add staging, replication targets, and temporary DBs.
New-sqmOlaUsrDbBackupJob with -Full -Diff -Log -UseExcludeTable -CheckPreferredReplica -Compress -Verify. System databases separately with New-sqmOlaSysDbBackupJob.
Invoke-sqmRestoreDatabase -BackupFiles ... on a test system. Confirm backup integrity with Test-sqmBackupIntegrity.
| Backup window | -70% Mon-Sat, thanks to DIFF instead of FULL |
| RPO | < 1 hour with hourly LOG |
| Control | Selective databases with no job changes |
| AlwaysOn | One job set for every AG node |
| Traceability | Audit trail in sqm_BackupExclude |