Skip to content
Open
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 62 additions & 23 deletions sp_DatabaseRestore.sql
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,30 @@ BEGIN
END
END

/* Reject path-shaped parameters that contain shell metacharacters or control chars.
Single quotes are deliberately allowed (legal in Windows paths) and are escaped at concat sites instead.
COLLATE Latin1_General_BIN2 is required so the LIKE '[...]' class catches NCHAR(0) — under default
collations the NUL byte is silently dropped from the pattern and compared values.
@Database is intentionally NOT in this list: it is primarily an identifier (database names can
legally contain '&', ';', etc.) and downstream usage either parameter-binds it (LIKE filters
against @FileList) or single-quote-escapes it before concatenating into a quoted SQL literal. */
Comment on lines +545 to +547
DECLARE @ForbiddenPathPattern NVARCHAR(40) = N'%["&|;^<>' + NCHAR(0) + NCHAR(10) + NCHAR(13) + N']%';
DECLARE @InvalidPathParam sysname = NULL;
IF @BackupPathFull LIKE @ForbiddenPathPattern COLLATE Latin1_General_BIN2 SET @InvalidPathParam = N'@BackupPathFull';
IF @InvalidPathParam IS NULL AND @BackupPathDiff LIKE @ForbiddenPathPattern COLLATE Latin1_General_BIN2 SET @InvalidPathParam = N'@BackupPathDiff';
IF @InvalidPathParam IS NULL AND @BackupPathLog LIKE @ForbiddenPathPattern COLLATE Latin1_General_BIN2 SET @InvalidPathParam = N'@BackupPathLog';
IF @InvalidPathParam IS NULL AND @MoveDataDrive LIKE @ForbiddenPathPattern COLLATE Latin1_General_BIN2 SET @InvalidPathParam = N'@MoveDataDrive';
IF @InvalidPathParam IS NULL AND @MoveLogDrive LIKE @ForbiddenPathPattern COLLATE Latin1_General_BIN2 SET @InvalidPathParam = N'@MoveLogDrive';
IF @InvalidPathParam IS NULL AND @MoveFilestreamDrive LIKE @ForbiddenPathPattern COLLATE Latin1_General_BIN2 SET @InvalidPathParam = N'@MoveFilestreamDrive';
IF @InvalidPathParam IS NULL AND @MoveFullTextCatalogDrive LIKE @ForbiddenPathPattern COLLATE Latin1_General_BIN2 SET @InvalidPathParam = N'@MoveFullTextCatalogDrive';
IF @InvalidPathParam IS NULL AND @StandbyUndoPath LIKE @ForbiddenPathPattern COLLATE Latin1_General_BIN2 SET @InvalidPathParam = N'@StandbyUndoPath';
IF @InvalidPathParam IS NULL AND @FileNamePrefix LIKE @ForbiddenPathPattern COLLATE Latin1_General_BIN2 SET @InvalidPathParam = N'@FileNamePrefix';
IF @InvalidPathParam IS NOT NULL
BEGIN
RAISERROR('Parameter %s contains a forbidden character. Not allowed: " & | ; ^ < > or control characters.', 16, 1, @InvalidPathParam) WITH NOWAIT;
RETURN;
END;

--File Extension cleanup
IF @FileExtensionDiff LIKE '%.%'
BEGIN
Expand Down Expand Up @@ -625,7 +649,7 @@ BEGIN
END
ELSE
BEGIN
SET @cmd = N'DIR /b "' + @CurrentBackupPathFull + N'"';
SET @cmd = N'DIR /b "' + REPLACE(@CurrentBackupPathFull, N'''', N'''''') + N'"';
IF @Debug = 1
BEGIN
IF @cmd IS NULL PRINT '@cmd is NULL for @CurrentBackupPathFull';
Expand Down Expand Up @@ -762,7 +786,7 @@ BEGIN
SET @FileListParamSQL += N')' + NCHAR(13) + NCHAR(10);
SET @FileListParamSQL += N'EXEC (''RESTORE FILELISTONLY FROM DISK=''''{Path}'''''')';

SET @sql = REPLACE(@FileListParamSQL, N'{Path}', @CurrentBackupPathFull + @LastFullBackup);
SET @sql = REPLACE(@FileListParamSQL, N'{Path}', REPLACE(@CurrentBackupPathFull + @LastFullBackup, N'''', REPLICATE(N'''', 4)));

IF @Debug = 1
BEGIN
Expand All @@ -778,7 +802,7 @@ BEGIN
END

--get the backup completed data so we can apply tlogs from that point forwards
SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathFull + @LastFullBackup);
SET @sql = REPLACE(@HeadersSQL, N'{Path}', REPLACE(@CurrentBackupPathFull + @LastFullBackup, N'''', REPLICATE(N'''', 4)));

IF @Debug = 1
BEGIN
Expand Down Expand Up @@ -939,17 +963,17 @@ BEGIN

SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM '
+ STUFF(
(SELECT CHAR( 10 ) + ',DISK=''' + BackupPath + BackupFile + ''''
(SELECT CHAR( 10 ) + ',DISK=''' + REPLACE(BackupPath, '''', '''''') + REPLACE(BackupFile, '''', '''''') + ''''
FROM #SplitFullBackups
ORDER BY BackupFile
FOR XML PATH ('')),
FOR XML PATH (''), TYPE).value('.', 'nvarchar(max)'),
1,
2,
'') + N' WITH NORECOVERY, REPLACE' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10);
END;
ELSE
BEGIN
SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + @CurrentBackupPathFull + @LastFullBackup + N''' WITH NORECOVERY, REPLACE' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10);
SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + REPLACE(@CurrentBackupPathFull, N'''', N'''''') + REPLACE(@LastFullBackup, N'''', N'''''') + N''' WITH NORECOVERY, REPLACE' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10);
END
IF (@StandbyMode = 1)
BEGIN
Expand All @@ -959,11 +983,11 @@ BEGIN
END
ELSE IF (SELECT COUNT(*) FROM #SplitFullBackups) > 0
BEGIN
SET @sql = @sql + ', STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + NCHAR(13) + NCHAR(10);
SET @sql = @sql + ', STANDBY = ''' + REPLACE(@StandbyUndoPath, N'''', N'''''') + REPLACE(@Database, N'''', N'''''') + 'Undo.ldf''' + NCHAR(13) + NCHAR(10);
END
ELSE
BEGIN
SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + @CurrentBackupPathFull + @LastFullBackup + N''' WITH REPLACE' + @BackupParameters + @MoveOption + N' , STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + NCHAR(13) + NCHAR(10);
SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + REPLACE(@CurrentBackupPathFull, N'''', N'''''') + REPLACE(@LastFullBackup, N'''', N'''''') + N''' WITH REPLACE' + @BackupParameters + @MoveOption + N' , STANDBY = ''' + REPLACE(@StandbyUndoPath, N'''', N'''''') + REPLACE(@Database, N'''', N'''''') + 'Undo.ldf''' + NCHAR(13) + NCHAR(10);
END
END;
IF @Debug = 1 OR @Execute = 'N'
Expand Down Expand Up @@ -1050,7 +1074,7 @@ BEGIN
END
ELSE
BEGIN
SET @cmd = N'DIR /b "' + @CurrentBackupPathDiff + N'"';
SET @cmd = N'DIR /b "' + REPLACE(@CurrentBackupPathDiff, N'''', N'''''') + N'"';
IF @Debug = 1
BEGIN
IF @cmd IS NULL PRINT '@cmd is NULL for @CurrentBackupPathDiff';
Expand Down Expand Up @@ -1131,16 +1155,16 @@ BEGIN
IF @Debug = 1 RAISERROR ('Split backups found', 0, 1) WITH NOWAIT;
SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM '
+ STUFF(
(SELECT CHAR( 10 ) + ',DISK=''' + BackupPath + BackupFile + ''''
(SELECT CHAR( 10 ) + ',DISK=''' + REPLACE(BackupPath, '''', '''''') + REPLACE(BackupFile, '''', '''''') + ''''
FROM #SplitDiffBackups
ORDER BY BackupFile
FOR XML PATH ('')),
FOR XML PATH (''), TYPE).value('.', 'nvarchar(max)'),
1,
2,
'' ) + N' WITH NORECOVERY, REPLACE' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10);
END;
ELSE
SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + @CurrentBackupPathDiff + @LastDiffBackup + N''' WITH NORECOVERY' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10);
SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + REPLACE(@CurrentBackupPathDiff, N'''', N'''''') + REPLACE(@LastDiffBackup, N'''', N'''''') + N''' WITH NORECOVERY' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10);

IF (@StandbyMode = 1)
BEGIN
Expand All @@ -1149,9 +1173,9 @@ BEGIN
IF @Execute = 'Y' OR @Debug = 1 RAISERROR('The file path of the undo file for standby mode was not specified. The database will not be restored in standby mode.', 0, 1) WITH NOWAIT;
END
ELSE IF (SELECT COUNT(*) FROM #SplitDiffBackups) > 0
SET @sql = @sql + ', STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + NCHAR(13) + NCHAR(10);
SET @sql = @sql + ', STANDBY = ''' + REPLACE(@StandbyUndoPath, N'''', N'''''') + REPLACE(@Database, N'''', N'''''') + 'Undo.ldf''' + NCHAR(13) + NCHAR(10);
ELSE
SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + @BackupPathDiff + @LastDiffBackup + N''' WITH STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10);
SET @sql = N'RESTORE DATABASE ' + @RestoreDatabaseName + N' FROM DISK = ''' + REPLACE(@BackupPathDiff, N'''', N'''''') + REPLACE(@LastDiffBackup, N'''', N'''''') + N''' WITH STANDBY = ''' + REPLACE(@StandbyUndoPath, N'''', N'''''') + REPLACE(@Database, N'''', N'''''') + 'Undo.ldf''' + @BackupParameters + @MoveOption + NCHAR(13) + NCHAR(10);
END;
IF @Debug = 1 OR @Execute = 'N'
BEGIN
Expand All @@ -1162,7 +1186,7 @@ BEGIN
EXECUTE @sql = [dbo].[CommandExecute] @DatabaseContext=N'master', @Command = @sql, @CommandType = 'RESTORE DATABASE', @Mode = 1, @DatabaseName = @UnquotedRestoreDatabaseName, @LogToTable = 'Y', @Execute = 'Y';

--get the backup completed data so we can apply tlogs from that point forwards
SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathDiff + @LastDiffBackup);
SET @sql = REPLACE(@HeadersSQL, N'{Path}', REPLACE(@CurrentBackupPathDiff + @LastDiffBackup, N'''', REPLICATE(N'''', 4)));

IF @Debug = 1
BEGIN
Expand Down Expand Up @@ -1235,7 +1259,7 @@ BEGIN
END
ELSE
BEGIN
SET @cmd = N'DIR /b "' + @CurrentBackupPathLog + N'"';
SET @cmd = N'DIR /b "' + REPLACE(@CurrentBackupPathLog, N'''', N'''''') + N'"';
IF @Debug = 1
BEGIN
IF @cmd IS NULL PRINT '@cmd is NULL for @CurrentBackupPathLog';
Expand Down Expand Up @@ -1365,7 +1389,7 @@ IF (@StandbyMode = 1)
IF @Execute = 'Y' OR @Debug = 1 RAISERROR('The file path of the undo file for standby mode was not specified. Logs will not be restored in standby mode.', 0, 1) WITH NOWAIT;
END;
ELSE
SET @LogRecoveryOption = N'STANDBY = ''' + @StandbyUndoPath + @Database + 'Undo.ldf''';
SET @LogRecoveryOption = N'STANDBY = ''' + REPLACE(@StandbyUndoPath, N'''', N'''''') + REPLACE(@Database, N'''', N'''''') + 'Undo.ldf''';
END;

IF (@LogRecoveryOption = N'')
Expand Down Expand Up @@ -1450,7 +1474,7 @@ WHERE BackupFile IS NOT NULL;
IF @i = 1

BEGIN
SET @sql = REPLACE(@HeadersSQL, N'{Path}', @CurrentBackupPathLog + @BackupFile);
SET @sql = REPLACE(@HeadersSQL, N'{Path}', REPLACE(@CurrentBackupPathLog + @BackupFile, N'''', REPLICATE(N'''', 4)));

IF @Debug = 1
BEGIN
Expand Down Expand Up @@ -1490,17 +1514,17 @@ WHERE BackupFile IS NOT NULL;
IF @Debug = 1 RAISERROR ('Split backups found', 0, 1) WITH NOWAIT;
SET @sql = N'RESTORE LOG ' + @RestoreDatabaseName + N' FROM '
+ STUFF(
(SELECT CHAR( 10 ) + ',DISK=''' + BackupPath + BackupFile + ''''
(SELECT CHAR( 10 ) + ',DISK=''' + REPLACE(BackupPath, '''', '''''') + REPLACE(BackupFile, '''', '''''') + ''''
FROM #SplitLogBackups
WHERE DenseRank = @LogRestoreRanking
ORDER BY BackupFile
FOR XML PATH ('')),
FOR XML PATH (''), TYPE).value('.', 'nvarchar(max)'),
1,
2,
'' ) + N' WITH ' + @LogRecoveryOption + NCHAR(13) + NCHAR(10);
END;
ELSE
SET @sql = N'RESTORE LOG ' + @RestoreDatabaseName + N' FROM DISK = ''' + @CurrentBackupPathLog + @BackupFile + N''' WITH ' + @LogRecoveryOption + NCHAR(13) + NCHAR(10);
SET @sql = N'RESTORE LOG ' + @RestoreDatabaseName + N' FROM DISK = ''' + REPLACE(@CurrentBackupPathLog, N'''', N'''''') + REPLACE(@BackupFile, N'''', N'''''') + N''' WITH ' + @LogRecoveryOption + NCHAR(13) + NCHAR(10);

IF @Debug = 1 OR @Execute = 'N'
BEGIN
Expand Down Expand Up @@ -1582,7 +1606,7 @@ IF @DatabaseOwner IS NOT NULL
BEGIN
IF EXISTS (SELECT * FROM master.dbo.syslogins WHERE syslogins.loginname = @DatabaseOwner)
BEGIN
SET @sql = N'ALTER AUTHORIZATION ON DATABASE::' + @RestoreDatabaseName + ' TO [' + @DatabaseOwner + ']';
SET @sql = N'ALTER AUTHORIZATION ON DATABASE::' + @RestoreDatabaseName + N' TO ' + QUOTENAME(@DatabaseOwner);

IF @Debug = 1 OR @Execute = 'N'
BEGIN
Expand Down Expand Up @@ -1672,8 +1696,23 @@ END;'

IF @RunStoredProcAfterRestore IS NOT NULL AND LEN(LTRIM(@RunStoredProcAfterRestore)) > 0
BEGIN
DECLARE @RunStoredProcSchema sysname = NULLIF(PARSENAME(@RunStoredProcAfterRestore, 2), N'');
DECLARE @RunStoredProcName sysname = PARSENAME(@RunStoredProcAfterRestore, 1);
IF @RunStoredProcName IS NULL
OR PARSENAME(@RunStoredProcAfterRestore, 3) IS NOT NULL
OR PARSENAME(@RunStoredProcAfterRestore, 4) IS NOT NULL
BEGIN
RAISERROR('@RunStoredProcAfterRestore must be a procedure name or schema.procedure (1- or 2-part name).', 16, 1) WITH NOWAIT;
RETURN;
END;
PRINT 'Attempting to run ' + @RunStoredProcAfterRestore
SET @sql = N'EXEC ' + @RestoreDatabaseName + '.' + @RunStoredProcAfterRestore
/* Always emit a 3-part name (db.schema.proc). For 1-part input the schema slot is left empty
so the name resolves as db..proc — i.e., the default schema in the restored DB. Without the
second dot, [db].[proc] is parsed as schema.object in the *current* DB. */
SET @sql = N'EXEC ' + @RestoreDatabaseName + N'.'
+ ISNULL(QUOTENAME(@RunStoredProcSchema), N'')
+ N'.'
+ QUOTENAME(@RunStoredProcName);
Comment on lines +1712 to +1715

IF @Debug = 1 OR @Execute = 'N'
BEGIN
Expand Down