@@ -28,32 +28,25 @@ EXEC dbo.DatabaseRestore
2828 @RunRecovery = 1;
2929*/
3030
31- USE [master]
31+ IF OBJECT_ID (' dbo.sp_DatabaseRestore' ) IS NULL
32+ EXEC (' CREATE PROCEDURE dbo.sp_DatabaseRestore AS RETURN 0;' )
3233GO
3334
34- SET ANSI_NULLS ON
35- GO
36- SET QUOTED_IDENTIFIER ON
37- GO
38-
39- CREATE PROCEDURE [dbo].[DatabaseRestore]
35+ ALTER PROCEDURE [dbo].[sp_DatabaseRestore]
4036 @Database NVARCHAR (128 ), @RestoreDatabaseName NVARCHAR (128 ) = NULL , @BackupPathFull NVARCHAR (MAX ), @BackupPathLog NVARCHAR (MAX ),
4137 @MoveFiles bit = 0 , @MoveDataDrive NVARCHAR (260 ) = NULL , @MoveLogDrive NVARCHAR (260 ) = NULL , @TestRestore bit = 0 , @RunCheckDB bit = 0 ,
4238 @ContinueLogs bit = 0 , @RunRecovery bit = 0
4339AS
44-
4540SET NOCOUNT ON ;
4641
4742DECLARE @cmd NVARCHAR (4000 ), @sql NVARCHAR (MAX ), @LastFullBackup NVARCHAR (500 ), @BackupFile NVARCHAR (500 );
4843DECLARE @FileList TABLE (BackupFile NVARCHAR (255 ));
4944
50- DECLARE @MoveDataLocation AS NVARCHAR (500 ), @MoveDataLocationName AS NVARCHAR (500 ), @MoveLogLocation AS NVARCHAR (500 ), @MoveLogLocationName AS NVARCHAR (500 );
51-
5245IF @RestoreDatabaseName IS NULL
5346 SET @RestoreDatabaseName = @Database;
5447
5548-- get list of files
56- SET @cmd = ' DIR /b '+ @BackupPathFull;
49+ SET @cmd = ' DIR /b " '+ @BackupPathFull + ' "' ;
5750INSERT INTO @FileList (BackupFile)
5851EXEC master .sys .xp_cmdshell @cmd;
5952
@@ -65,7 +58,23 @@ WHERE BackupFile LIKE '%.bak'
6558 AND
6659 BackupFile LIKE ' %'+ @Database+ ' %' ;
6760
68- DECLARE @FileListParameters TABLE
61+ -- Get the SQL Server version number because the columns returned by RESTORE commands vary by version
62+ -- Based on: https://www.brentozar.com/archive/2015/05/sql-server-version-detection/
63+ -- Need to capture BuildVersion because RESTORE HEADERONLY changed with 2014 CU1, not RTM
64+ DECLARE @ProductVersion AS varchar (20 ) = CAST (SERVERPROPERTY (' productversion' ) AS varchar (20 ));
65+ DECLARE @MajorVersion AS smallint = CAST (PARSENAME (@ProductVersion, 4 ) AS smallint );
66+ DECLARE @MinorVersion AS smallint = CAST (PARSENAME (@ProductVersion, 3 ) AS smallint );
67+ DECLARE @BuildVersion AS smallint = CAST (PARSENAME (@ProductVersion, 2 ) AS smallint );
68+
69+ IF @MajorVersion < 10
70+ BEGIN
71+ RAISERROR (' Sorry, DatabaseRestore doesn'' t work on versions of SQL prior to 2008.' , 15 , 1 );
72+ RETURN ;
73+ END
74+
75+ -- Build SQL for RESTORE FILELIST ONLY
76+ IF OBJECT_ID (N ' tempdb..#FileListParameters' ) IS NOT NULL DROP TABLE #FileListParameters;
77+ CREATE TABLE #FileListParameters
6978(
7079 LogicalName NVARCHAR (128 ) NOT NULL
7180 ,PhysicalName NVARCHAR (260 ) NOT NULL
@@ -90,11 +99,67 @@ DECLARE @FileListParameters TABLE
9099 ,TDEThumbprint VARBINARY (32 ) NULL
91100 ,SnapshotUrl NVARCHAR (360 ) NULL
92101);
102+ DECLARE @FileListParamSQL AS nvarchar (4000 );
103+ SET @FileListParamSQL =
104+ ' INSERT INTO #FileListParameters
105+ (LogicalName, PhysicalName, Type, FileGroupName, Size, MaxSize, FileID, CreateLSN, DropLSN
106+ ,UniqueID, ReadOnlyLSN, ReadWriteLSN, BackupSizeInBytes, SourceBlockSize, FileGroupID, LogGroupGUID
107+ ,DifferentialBaseLSN, DifferentialBaseGUID, IsReadOnly, IsPresent, TDEThumbprint'
108+
109+ IF @MajorVersion >= 13
110+ SET @FileListParamSQL + = ' , SnapshotUrl'
111+
112+ SET @FileListParamSQL + = ' )' + CHAR (13 ) + CHAR (10 );
113+ SET @FileListParamSQL + = ' EXEC ('' RESTORE FILELISTONLY FROM DISK='' '' {Path}'' '' '' )' ;
114+
115+ SET @sql = REPLACE (@FileListParamSQL, ' {Path}' , @BackupPathFull + @LastFullBackup);
116+ PRINT @sql;
117+ EXEC (@sql);
118+
119+ -- Build SQL for RESTORE HEADERONLY - this will be used a bit further below
120+ IF OBJECT_ID (N ' tempdb..#Headers' ) IS NOT NULL DROP TABLE #Headers;
121+ CREATE TABLE #Headers (
122+ BackupName VARCHAR (256 ), BackupDescription VARCHAR (256 ), BackupType VARCHAR (256 ), ExpirationDate VARCHAR (256 ),
123+ Compressed VARCHAR (256 ), Position VARCHAR (256 ), DeviceType VARCHAR (256 ), UserName VARCHAR (256 ), ServerName VARCHAR (
124+ 256 ), DatabaseName VARCHAR (256 ), DatabaseVersion VARCHAR (256 ), DatabaseCreationDate VARCHAR (256 ), BackupSize VARCHAR (
125+ 256 ), FirstLSN VARCHAR (256 ), LastLSN VARCHAR (256 ), CheckpointLSN VARCHAR (256 ), DatabaseBackupLSN VARCHAR (256 ),
126+ BackupStartDate VARCHAR (256 ), BackupFinishDate VARCHAR (256 ), SortOrder VARCHAR (256 ), CodePage VARCHAR (256 ),
127+ UnicodeLocaleId VARCHAR (256 ), UnicodeComparisonStyle VARCHAR (256 ), CompatibilityLevel VARCHAR (256 ), SoftwareVendorId
128+ VARCHAR (256 ), SoftwareVersionMajor VARCHAR (256 ), SoftwareVersionMinor VARCHAR (256 ), SoftwareVersionBuild VARCHAR (256 ),
129+ MachineName VARCHAR (256 ), Flags VARCHAR (256 ), BindingID VARCHAR (256 ), RecoveryForkID VARCHAR (256 ), Collation VARCHAR (
130+ 256 ), FamilyGUID VARCHAR (256 ), HasBulkLoggedData VARCHAR (256 ), IsSnapshot VARCHAR (256 ), IsReadOnly VARCHAR (256 ),
131+ IsSingleUser VARCHAR (256 ), HasBackupChecksums VARCHAR (256 ), IsDamaged VARCHAR (256 ), BeginsLogChain VARCHAR (256 ),
132+ HasIncompleteMetaData VARCHAR (256 ), IsForceOffline VARCHAR (256 ), IsCopyOnly VARCHAR (256 ), FirstRecoveryForkID VARCHAR
133+ (256 ), ForkPointLSN VARCHAR (256 ), RecoveryModel VARCHAR (256 ), DifferentialBaseLSN VARCHAR (256 ), DifferentialBaseGUID
134+ VARCHAR (256 ), BackupTypeDescription VARCHAR (256 ), BackupSetGUID VARCHAR (256 ), CompressedBackupSize VARCHAR (256 ),
135+ Containment VARCHAR (256 ), KeyAlgorithm NVARCHAR (32 ), EncryptorThumbprint VARBINARY (20 ), EncryptorType NVARCHAR (32 ),
136+ --
137+ -- Seq added to retain order by
138+ --
139+ Seq INT NOT NULL IDENTITY (1 , 1 )
140+ );
93141
94- INSERT INTO @FileListParameters
95- EXEC (' RESTORE FILELISTONLY FROM DISK='' '+ @BackupPathFull + @LastFullBackup+ ' '' ' );
142+ DECLARE @HeadersSQL AS nvarchar (4000 );
143+ SET @HeadersSQL =
144+ ' INSERT INTO #Headers
145+ (BackupName, BackupDescription, BackupType, ExpirationDate, Compressed, Position, DeviceType, UserName, ServerName
146+ ,DatabaseName, DatabaseVersion, DatabaseCreationDate, BackupSize, FirstLSN, LastLSN, CheckpointLSN, DatabaseBackupLSN
147+ ,BackupStartDate, BackupFinishDate, SortOrder, CodePage, UnicodeLocaleId, UnicodeComparisonStyle, CompatibilityLevel
148+ ,SoftwareVendorId, SoftwareVersionMajor, SoftwareVersionMinor, SoftwareVersionBuild, MachineName, Flags, BindingID
149+ ,RecoveryForkID, Collation, FamilyGUID, HasBulkLoggedData, IsSnapshot, IsReadOnly, IsSingleUser, HasBackupChecksums
150+ ,IsDamaged, BeginsLogChain, HasIncompleteMetaData, IsForceOffline, IsCopyOnly, FirstRecoveryForkID, ForkPointLSN
151+ ,RecoveryModel, DifferentialBaseLSN, DifferentialBaseGUID, BackupTypeDescription, BackupSetGUID, CompressedBackupSize'
152+
153+ IF @MajorVersion >= 11
154+ SET @HeadersSQL + = CHAR (13 ) + CHAR (10 ) + ' ,Containment' ;
96155
97- DECLARE @MoveOption AS NVARCHAR (1000 )= ' ' ;
156+ IF @MajorVersion >= 13 OR (@MajorVersion = 12 AND @BuildVersion >= 2342 )
157+ SET @HeadersSQL + = ' , KeyAlgorithm, EncryptorThumbprint, EncryptorType'
158+
159+ SET @HeadersSQL + = ' )' + CHAR (13 ) + CHAR (10 );
160+ SET @HeadersSQL + = ' EXEC ('' RESTORE HEADERONLY FROM DISK='' '' {Path}'' '' '' )' ;
161+
162+ DECLARE @MoveOption AS NVARCHAR (MAX )= ' ' ;
98163
99164IF @MoveFiles = 1
100165BEGIN
@@ -105,7 +170,7 @@ BEGIN
105170 WHEN Type = ' D' THEN @MoveDataDrive
106171 WHEN Type = ' L' THEN @MoveLogDrive
107172 END + REVERSE (LEFT (REVERSE (PhysicalName), CHARINDEX (' \' , REVERSE (PhysicalName), 1 )- 1 )) + ' '' ' AS logicalcmds
108- FROM @ FileListParameters)
173+ FROM # FileListParameters)
109174 SELECT @MoveOption = @MoveOption + logicalcmds
110175 FROM Files;
111176END ;
@@ -117,41 +182,23 @@ BEGIN
117182 EXECUTE @sql = [dbo].[CommandExecute] @Command = @sql, @CommandType = ' RESTORE DATABASE' , @Mode = 1 , @DatabaseName = @Database, @LogToTable = ' Y' , @Execute = ' Y' ;
118183
119184 -- get the backup completed data so we can apply tlogs from that point forwards
120- DECLARE @Headers TABLE (
121- BackupName VARCHAR (256 ), BackupDescription VARCHAR (256 ), BackupType VARCHAR (256 ), ExpirationDate VARCHAR (256 ),
122- Compressed VARCHAR (256 ), Position VARCHAR (256 ), DeviceType VARCHAR (256 ), UserName VARCHAR (256 ), ServerName VARCHAR (
123- 256 ), DatabaseName VARCHAR (256 ), DatabaseVersion VARCHAR (256 ), DatabaseCreationDate VARCHAR (256 ), BackupSize VARCHAR (
124- 256 ), FirstLSN VARCHAR (256 ), LastLSN VARCHAR (256 ), CheckpointLSN VARCHAR (256 ), DatabaseBackupLSN VARCHAR (256 ),
125- BackupStartDate VARCHAR (256 ), BackupFinishDate VARCHAR (256 ), SortOrder VARCHAR (256 ), CodePage VARCHAR (256 ),
126- UnicodeLocaleId VARCHAR (256 ), UnicodeComparisonStyle VARCHAR (256 ), CompatibilityLevel VARCHAR (256 ), SoftwareVendorId
127- VARCHAR (256 ), SoftwareVersionMajor VARCHAR (256 ), SoftwareVersionMinor VARCHAR (256 ), SoftwareVersionBuild VARCHAR (256 ),
128- MachineName VARCHAR (256 ), Flags VARCHAR (256 ), BindingID VARCHAR (256 ), RecoveryForkID VARCHAR (256 ), Collation VARCHAR (
129- 256 ), FamilyGUID VARCHAR (256 ), HasBulkLoggedData VARCHAR (256 ), IsSnapshot VARCHAR (256 ), IsReadOnly VARCHAR (256 ),
130- IsSingleUser VARCHAR (256 ), HasBackupChecksums VARCHAR (256 ), IsDamaged VARCHAR (256 ), BeginsLogChain VARCHAR (256 ),
131- HasIncompleteMetaData VARCHAR (256 ), IsForceOffline VARCHAR (256 ), IsCopyOnly VARCHAR (256 ), FirstRecoveryForkID VARCHAR
132- (256 ), ForkPointLSN VARCHAR (256 ), RecoveryModel VARCHAR (256 ), DifferentialBaseLSN VARCHAR (256 ), DifferentialBaseGUID
133- VARCHAR (256 ), BackupTypeDescription VARCHAR (256 ), BackupSetGUID VARCHAR (256 ), CompressedBackupSize VARCHAR (256 ),
134- Containment VARCHAR (256 ), KeyAlgorithm NVARCHAR (32 ), EncryptorThumbprint VARBINARY (20 ), EncryptorType NVARCHAR (32 ),
135- --
136- -- Seq added to retain order by
137- --
138- Seq INT NOT NULL IDENTITY (1 , 1 )
139- );
140-
141- INSERT INTO @Headers
142- EXEC (' RESTORE HEADERONLY FROM DISK = '' '+ @BackupPathFull + @LastFullBackup+ ' '' ' );
143-
144- DECLARE @BackupDateTime AS CHAR (15 ), @FullLastLSN BIGINT ;
185+
186+ SET @sql = REPLACE (@HeadersSQL, ' {Path}' , @BackupPathFull + @LastFullBackup);
187+ PRINT @sql;
188+ EXECUTE (@sql);
189+
190+ DECLARE @BackupDateTime AS CHAR (15 ), @FullLastLSN NUMERIC (25 , 0 );
145191
146192 SELECT @BackupDateTime = RIGHT (@LastFullBackup, 19 )
147193
148- SELECT @FullLastLSN = CAST (LastLSN AS BIGINT ) FROM @Headers WHERE BackupType = 1 ;
194+ SELECT @FullLastLSN = CAST (LastLSN AS NUMERIC (25 , 0 )) FROM #Headers WHERE BackupType = 1 ;
195+
149196END ;
150197ELSE
151198BEGIN
152- DECLARE @DatabaseLastLSN BIGINT ;
199+ DECLARE @DatabaseLastLSN NUMERIC ( 25 , 0 ) ;
153200
154- SELECT @DatabaseLastLSN = CAST (f .redo_start_lsn AS BIGINT )
201+ SELECT @DatabaseLastLSN = CAST (f .redo_start_lsn AS NUMERIC ( 25 , 0 ) )
155202 FROM master .sys .databases d
156203 JOIN master .sys .master_files f ON d .database_id = f .database_id
157204 WHERE d .name = @RestoreDatabaseName AND f .file_id = 1
160207-- Clear out table variables for translogs
161208DELETE FROM @FileList;
162209
163- SET @cmd = ' DIR /b '+ @BackupPathLog;
210+ SET @cmd = ' DIR /b " '+ @BackupPathLog + ' "' ;
164211INSERT INTO @FileList (BackupFile)
165212EXEC master .sys .xp_cmdshell @cmd;
166213
@@ -174,23 +221,24 @@ DECLARE BackupFiles CURSOR FOR
174221
175222OPEN BackupFiles;
176223
177- DECLARE @i tinyint = 1 , @LogFirstLSN BIGINT , @LogLastLSN BIGINT ; ;
224+ DECLARE @i tinyint = 1 , @LogFirstLSN NUMERIC ( 25 , 0 ), @LogLastLSN NUMERIC ( 25 , 0 ) ;
178225
179226-- Loop through all the files for the database
180227FETCH NEXT FROM BackupFiles INTO @BackupFile;
181228WHILE @@FETCH_STATUS = 0
182229BEGIN
183230 IF @i = 1
184231 BEGIN
185- INSERT INTO @Headers
186- EXEC (' RESTORE HEADERONLY FROM DISK = '' '+ @BackupPathLog + @BackupFile+ ' '' ' );
232+ SET @sql = REPLACE (@HeadersSQL, ' {Path}' , @BackupPathLog + @BackupFile);
233+ PRINT @sql;
234+ EXECUTE (@sql);
187235
188- SELECT @LogFirstLSN = CAST (FirstLSN AS BIGINT ) , @LogLastLSN = CAST (LastLSN AS BIGINT ) FROM @ Headers WHERE BackupType = 2 ;
236+ SELECT @LogFirstLSN = CAST (FirstLSN AS NUMERIC ( 25 , 0 )) , @LogLastLSN = CAST (LastLSN AS NUMERIC ( 25 , 0 )) FROM # Headers WHERE BackupType = 2 ;
189237
190238 IF (@ContinueLogs = 0 AND @LogFirstLSN <= @FullLastLSN AND @FullLastLSN <= @LogLastLSN) OR (@ContinueLogs = 1 AND @LogFirstLSN <= @DatabaseLastLSN AND @DatabaseLastLSN < @LogLastLSN)
191239 SET @i = 2 ;
192240
193- DELETE FROM @ Headers WHERE BackupType = 2 ;
241+ DELETE FROM # Headers WHERE BackupType = 2 ;
194242 END ;
195243
196244 IF @i = 2
0 commit comments