Technical Article

Script to construct a restore/recovery script

,

Run on a live production system to construct a recovery script from most recent full backup then all subsequent log file backups. Can override backup folder and data/log files WITH MOVE if restoring to a test server with different folder structure. Uses checksum where possible to verify backups. Ignores Symatech or other non SQL backups (VDI Devices) taken between full/transaction log backups. Lists backup files required for the recovery. The entire script, with the exception of the variable declarations and assignments, is actually a single SQL statement.

-- OVERRIDE VALUES FROM EXISTING
DECLARE @ToFileFolder VARCHAR(2000);
DECLARE @ToLogFolder VARCHAR(2000);
DECLARE @BackupDeviceFolder VARCHAR(2000);

SET @ToFileFolder = 'E:\Data\';
SET @ToLogFolder = 'D:\Logs\';
SET @BackupDeviceFolder = 'F:\Backups\';

--------------------------------------------------------------------------------------------------------------
-- Most recent full backup + subsequent log file backups
--------------------------------------------------------------------------------------------------------------
WITH CTE 
(
     database_name
    ,current_compatibility_level
    ,current_is_read_only
    ,current_state_desc
    ,current_recovery_model_desc
    ,has_backup_checksums
    ,[type]
    ,backupmediasetid
    ,backupfinishdate
    ,physical_device_name
)
AS
(
    SELECT 
         bs.database_name
        ,d.[compatibility_level] AS current_compatibility_level
        ,d.[is_read_only] AS current_is_read_only
        ,d.[state_desc] AS current_state_desc
        ,d.[recovery_model_desc] current_recovery_model_desc
        ,bs.has_backup_checksums    
        ,'D' AS [type]
        ,bs.media_set_id AS backupmediasetid
        ,x.backup_finish_date AS backupfinishdate
        ,mf.physical_device_name
    FROM msdb.dbo.backupset bs
    INNER JOIN sys.databases d
        ON bs.database_name = d.name
    INNER JOIN
    (
        SELECT 
             database_name
            ,MAX(backup_finish_date) backup_finish_date    
        FROM msdb.dbo.backupset a
        JOIN msdb.dbo.backupmediafamily b
            ON a.media_set_id = b.media_set_id 
        WHERE a.[type] = 'D'
        AND b.[Device_Type] = 2
        GROUP BY database_name
    ) x    
        ON x.database_name = bs.database_name
        AND x.backup_finish_date = bs.backup_finish_date

    JOIN msdb.dbo.backupmediafamily mf
        ON mf.media_set_id = bs.media_set_id 

    --------------------------
    UNION
    --------------------------
    SELECT 
         bs.database_name
        ,d.[compatibility_level] AS current_compatibility_level
        ,d.[is_read_only] AS current_is_read_only
        ,d.[state_desc] AS current_state_desc
        ,d.[recovery_model_desc] current_recovery_model_desc
        ,bs.has_backup_checksums    
        ,'L' AS [type]
        ,y.media_set_id AS backupmediasetid
        ,y.backup_finish_date as backupfinishdate
        ,mf.physical_device_name
            
    FROM msdb.dbo.backupset bs
    INNER JOIN sys.databases d
        ON bs.database_name = d.name
    INNER JOIN
    (
        SELECT 
             database_name
            ,MAX(backup_finish_date) backup_finish_date    
        FROM msdb.dbo.backupset a
        JOIN msdb.dbo.backupmediafamily b
            ON a.media_set_id = b.media_set_id 
        WHERE a.[type] = 'D'
        AND b.[Device_Type] = 2
        GROUP BY database_name
    ) x    
        ON x.database_name = bs.database_name
        AND x.backup_finish_date = bs.backup_finish_date
    INNER JOIN 
    (
        SELECT 
             database_name
            ,backup_finish_date
            ,a.media_set_id            
        FROM msdb.dbo.backupset a
        JOIN msdb.dbo.backupmediafamily b
            ON a.media_set_id = b.media_set_id 
        WHERE a.[type] = 'L'
        AND b.[Device_Type] = 2
    ) y
        ON y.database_name = bs.database_name
        AND y.backup_finish_date > x.backup_finish_date
    
    JOIN msdb.dbo.backupmediafamily mf
        ON mf.media_set_id = y.media_set_id 
    
)

---------------------------------------------------------------
-- RESULT SET COMES FROM HERE
SELECT
     @@SERVERNAME 
    ,a.Sequence
    ,a.Database_Name
    ,a.BackupDevice
    ,a.backupfinishdate
    ,a.Command
FROM 
(
    --------------------------------------------------------------------
    -- Most recent full backup
    --------------------------------------------------------------------
    SELECT 
        ';RESTORE DATABASE ' + d.[name] + SPACE(1) +
        'FROM DISK = ' + '''' + 
         CASE ISNULL(@BackupDeviceFolder,'Actual')
            WHEN 'Actual' THEN CTE.physical_device_name
            ELSE @BackupDeviceFolder + SUBSTRING(CTE.physical_device_name,LEN(CTE.physical_device_name) - CHARINDEX('\',REVERSE(CTE.physical_device_name),1) + 2,    CHARINDEX('\',REVERSE(CTE.physical_device_name),1) + 1)
         END + '''' + SPACE(1) + 
        'WITH REPLACE,' + 
        CASE CTE.has_backup_checksums WHEN 1 THEN 'CHECKSUM,' ELSE ' ' END + 
        'NORECOVERY,' + SPACE(1) +
        'MOVE ' + '''' + x.LogicalName + '''' + ' TO ' + 
        '''' + 
         CASE ISNULL(@ToFileFolder,'Actual')
            WHEN 'Actual' THEN x.PhysicalName
            ELSE @ToFileFolder + SUBSTRING(x.PhysicalName,LEN(x.PhysicalName) - CHARINDEX('\',REVERSE(x.PhysicalName),1) + 2,    CHARINDEX('\',REVERSE(x.PhysicalName),1) + 1)
         END + '''' + ',' + SPACE(1) +
    
        'MOVE ' + '''' + y.LogicalName + '''' + ' TO ' + 
        '''' + 
         CASE ISNULL(@ToLogFolder,'Actual')
            WHEN 'Actual' THEN y.PhysicalName
            ELSE @ToLogFolder + SUBSTRING(y.PhysicalName,LEN(y.PhysicalName) - CHARINDEX('\',REVERSE(y.PhysicalName),1) + 2,    CHARINDEX('\',REVERSE(y.PhysicalName),1) + 1)
         END + '''' AS Command,
         1 AS Sequence,
         d.name AS database_name,
         CTE.physical_device_name AS BackupDevice,
         CTE.backupfinishdate
    
    FROM sys.databases d
    JOIN
    (
        SELECT 
             DB_NAME(mf.database_id) AS name
            ,mf.Physical_Name AS PhysicalName
            ,mf.Name AS LogicalName
        FROM sys.master_files mf
        WHERE type_desc = 'ROWS'
        AND mf.file_id = 1
    ) x
        ON d.name = x.name
    JOIN
    (
        SELECT 
             DB_NAME(mf.database_id) AS name
            ,mf.Physical_Name PhysicalName
            ,mf.Name AS LogicalName
        FROM sys.master_files mf
        WHERE type_desc = 'LOG'
    
    ) y
        ON d.name = y.name
    
    JOIN CTE 
        ON CTE.database_name = d.name
    
    WHERE CTE.[type] = 'D'
    AND d.name NOT IN ('master','model','msdb')
    
    --------------------------------------------------------------------
    UNION -- Restore Log backups taken since most recent full
    --------------------------------------------------------------------
    SELECT 
        ';RESTORE LOG ' + d.[name] + SPACE(1) +
        'FROM DISK = ' + '''' + --CTE.physical_device_name + '''' + SPACE(1) +
         CASE ISNULL(@BackupDeviceFolder,'Actual')
            WHEN 'Actual' THEN CTE.physical_device_name
            ELSE @BackupDeviceFolder + SUBSTRING(CTE.physical_device_name,LEN(CTE.physical_device_name) - CHARINDEX('\',REVERSE(CTE.physical_device_name),1) + 2,    CHARINDEX('\',REVERSE(CTE.physical_device_name),1) + 1)
         END + '''' +
        ' WITH NORECOVERY' AS Command,
        32769 AS Sequence,
        d.name AS database_name,
        CTE.physical_device_name AS BackupDevice,
        CTE.backupfinishdate
    
    FROM sys.databases d
    
    JOIN CTE
        ON CTE.database_name = d.name
    
    WHERE CTE.[type] = 'L'
    AND d.name NOT IN ('master','model','msdb')
    
    --------------------------------------------------------------------
    UNION -- Restore WITH RECOVERY
    --------------------------------------------------------------------
    SELECT 
        ';RESTORE DATABASE ' + d.[name] + SPACE(1) + 'WITH RECOVERY' AS Command,
        32770 AS Sequence,
        d.name AS database_name,
        '' AS BackupDevice,
        CTE.backupfinishdate
    
    FROM sys.databases d
    
    JOIN CTE
        ON CTE.database_name = d.name
    
    WHERE CTE.[type] = 'D'
    AND d.name NOT IN ('master','model','msdb')
    
    --------------------------------------------------------------------
    UNION -- CHECKDB
    --------------------------------------------------------------------
    SELECT 
        ';DBCC CHECKDB(' + '''' + d.[name] + '''' + ') WITH NO_INFOMSGS IF @@ERROR > 0 PRINT N''CONSISTENCY PROBLEMS IN DATABASE : ' + d.name + ''' ELSE PRINT N''CONSISTENCY GOOD IN DATABASE : ' + d.name + '''' AS Command,
        32771 AS Sequence,
        d.name AS database_name,
        '' AS BackupDevice,
        CTE.backupfinishdate
    
    FROM sys.databases d
    
    JOIN CTE
        ON CTE.database_name = d.name
    
    WHERE CTE.[type] = 'D'
    AND d.name NOT IN ('master','model','msdb')
    
    ---------------------------------------------------------------------------------------------------------------------------------------------------
    UNION -- MOVE Secondary data files
    ---------------------------------------------------------------------------------------------------------------------------------------------------

    SELECT 
        ', MOVE ' + '''' + b.name + '''' + ' TO ' + 
        '''' + 
         CASE ISNULL(@ToFileFolder,'Actual')
            WHEN 'Actual' THEN b.physical_name
            ELSE @ToFileFolder + SUBSTRING(b.Physical_Name,LEN(b.Physical_Name) - CHARINDEX('\',REVERSE(b.Physical_Name),1) + 2,    CHARINDEX('\',REVERSE(b.Physical_Name),1) + 1)
         END + '''',
        b.file_id AS Sequence,
        DB_NAME(b.database_id) AS database_name,
        '' AS BackupDevice,
        CTE.backupfinishdate
    
    FROM sys.master_files b
    INNER JOIN CTE
        ON CTE.database_name = DB_NAME(b.database_id)
    
    WHERE CTE.[type] = 'D'
    AND b.type_desc = 'ROWS'
    AND b.file_id > 2
    
) a

ORDER BY 
    database_name, 
    sequence,
    backupfinishdate

Rate

4.5 (2)

You rated this post out of 5. Change rating

Share

Share

Rate

4.5 (2)

You rated this post out of 5. Change rating