ourSSD 2 - Are My Databases Being Backed Up?
Saturday, January 30, 2010 at 2:44PM Checking Backups
In ourSSD part one we covered checking that our SQL nodes were up, now we’ll move on to checking that our backups are working. I used to do this with a painful DTS and then SSIS package which would go to each SQL Instance and pull in all the backup information. Initial setup was extremely time consuming and the maintenance of the package with adding and removing SQL Instances got to be a serious pain.
Powershell to the rescue once more. A single command will give us the backup information for all the user databases on a SQL Instance (this does not provide information on the system databases) (using the -force switch we can pull back all databases, including system).
dir SQLSERVER:\SQL\MYSQLSERVER\SQLINSTANCE\Databases | SELECT Name, LastBackupDate, LastDifferentialBackupDate
Name LastBackupDate LastDifferentialBackupDate
--------------------------- -------------------- ---------------------------------
AdventureWorks 1/31/2010 1:40:12PM 1/1/0001 12:00:00AM
AdventureWorks2008 1/20/2010 10:23:03AM 1/31/2010 1:40:04PM
AdventureWorksDW2008 1/1/0001 12:00:00AM 1/1/0001 12:00:00AM
A couple of things to note here, instead of using a null value when there’s no record, a date of 1/1/0001 12:00:00AM is used. This date value is not usable within SQL so when we bring in the data we need to make it a date that we can actually work with. We’ll do that as a part of the larger Powershell script. If you look at the records returned above you’ll see that the AdventureWorks database was backed up 1/31/2010 1:40:12PM, and has no differential backup. The AdventureWorks2008 database has a full backup from 1/20/2010 and a differential 1/31/2010. The AdventureWorksDW2008 database has never been backed up.
Setting Up The SQL
Now that we have the very basics to pull the information that we want let’s get creative. To start things off we’ll create a table to hold a list of the SQL Instances that we are going to want to monitor. We’ll include a couple of configuration columns that we will be using within our monitoring routines.
CREATE TABLE SQLMonitor.dbo.SQLInstances
(
ServerName VARCHAR(20)
, Instance VARCHAR(30)
, CheckBackup BIT DEFAULT 1
, CheckBackupDate DATETIME
, CheckDBSpace BIT
, CheckDBSpaceDate DATETIME
, CheckDiskSpace BIT
, CheckDiskSpaceDate DATETIME
)
Insert a couple of rows for SQL Instances that we’ll be checking the backup stats on
INSERT INTO SQLMonitor.dbo.SQLInstances (ServerName, Instance, CheckBackup, CheckDBSpace, CheckDiskSpace)
VALUES ('PROD1', NULL, 1,1,1)
, ('PROD2', 'ODS', 1,1,1)
Finally, for the DB side of things, add a table to hold the results of the database backup checks.
Note: we are going to use a computed column to store the difference between the last backup and the check time as well as store the last full and last differential backup times, with the lastbackupdate being the most recent of those two
CREATE TABLE SQLMonitor.dbo.DatabaseBackups
(
ServerName VARCHAR(50)
, DatabaseName VARCHAR(128)
, ExtendedProperties NVARCHAR(MAX)
, LastFullBackupDate DATETIME
, LastDifferentialBackupDate DATETIME
, LastBackupDate DATETIME
, RecordDate DATETIME DEFAULT GETDATE()
, BackupDelta AS (DATEDIFF(DAY,[LastBackupDate],[RecordDate]))
)
Going Through The Powershell
Now we just need the powershell script to go and pull the data in and load the table.
We use Invoke-SqlCmd to go and get our recordset from the SQLInstances table and throw that into the $DSServername dataset.
$DSServernames= Invoke-SqlCmd -ServerInstance ".\YUKON" -Database "SQLMonitor" -Query "select ServerName,isnull(Instance, 'NULL') as Instance from SQLInstances where CheckBackup = 1"
We need to work with the dataset a little here to ensure that we get the correct path to the databases. The dir command follows the path SQLSERVER:\SQL\<servername>\<instance>. When you aren’t querying a named instance then you use Default in place of the instance name. For the two records that we added to the table we would need to query paths SQLSERVER:\SQL\PROD1\Default and SQLSERVER:\SQL\PROD2\ODS.
To get the correct path we use a string match and set a variable for the location depending on whether it’s a named or default instance
foreach ($Servername in $DSServernames)
{
#for the query we use \Default if not a named instance but want to keep the original SQL instance info for table insertion
IF($Servername.Instance -eq "NULL")
{
$location = $Servername.ServerName+"\Default"
$srv = $Servername.ServerName
}
else
{
$location = $Servername.ServerName+"\"+$Servername.Instance
$srv = $location
}
The $location variable now contains the our SQL path and we just plug that into the dir command and build ourselves a new dataset from there.
$backupstatus = dir -force SQLSERVER:\SQL\$location\Databases | SELECT Name, ExtendedProperties, LastBackupDate, LastDifferentialBackupDate
Note: I’m also pull in ExtendedProperties here. This is useful for me as in my production environment I don’t backup replicated data, this saves me a great deal of space (all procs/views that use that data are housed in other databases). My backup procedures check for a rep_copy value to know whether to perform that backup or not. I don’t want my report to include a list of databases that I don’t expect to be backed up so I’ll pull in this information and evaluate it in my queries later.
We want to be sure that we are getting the information for all the servers that we query. To do this we update the SQLInstances table with the current datetime once we’ve pulled the dataset. We can check this within our reports to be sure that there were no issues with a datapull and thus potentially miss information.
#if we were able to build a recordset update the timestamp in the config table to show we captured data
if($?)
{
Invoke-SQLCmd -ServerInstance ".\YUKON" -Database "SQLMonitor" -Query "UPDATE SQLInstances SET CheckBackupDate = getdate() WHERE ServerName = '$updsrv' AND (Instance = '$updinst' OR Instance IS NULL)"
For each record in the dataset we assign a variable
foreach ($BackupRecord in $backupstatus)
{
$db = $BackupRecord.Name
$exp = $BackupRecord.ExtendedProperties
$lbu = $BackupRecord.LastBackupDate
$ldbu = $BackupRecord.LastDifferentialBackupDate
And we run a fix on the odd datetime value so that we can get the data into SQL
#In the event that there has never been a backup or differential backup for a database we need to set a real datetime value
#there is a default value of 01/01/0001 00:00:00 which is not a real datetime
IF ($lbu -eq "01/01/0001 00:00:00") { $lbu = "01/01/1900 00:00:00"}
IF ($ldbu -eq "01/01/0001 00:00:00") { $ldbu = "01/01/1900 00:00:00"}
Then we assign the $lb variable with the most recent backup datetime, be it full or differential
#if the last full backup is newer than the last differential use that, otherwise use the differential date
IF ($lbu -gt $ldbu)
{ $lb = $lbu }
Else
{ $lb = $ldbu }
Finally we insert the data into the table
#insert it into the table
Invoke-SQLCmd -ServerInstance ".\YUKON" -Database "SQLMonitor" -Query "INSERT INTO dbo.DatabaseBackups (ServerName, DatabaseName, ExtendedProperties, LastFullBackupDate, LastDifferentialBackupDate, LastBackupDate) VALUES ('$srv', '$db', '$exp', '$lbu', '$ldbu', '$lb')"
The Full Powershell Script
#CHECKING BACKUPS
$ErrorActionPreference = "SilentlyContinue"
Add-PSSnapin SqlServerCmdletSnapin100
#Clear out the old records
Invoke-SQLCmd -ServerInstance ".\YUKON" -Database "SQLMonitor" -Query "TRUNCATE TABLE dbo.DatabaseBackups"
#Get a list of SQL Instances from the table and dump them into a dataset that we can work with
$DSServernames= Invoke-SqlCmd -ServerInstance ".\YUKON" -Database "SQLMonitor" -Query "select ServerName,isnull(Instance, 'NULL') as Instance from SQLInstances where CheckBackup = 1"
#Now let's work with each record returned and check things out
foreach ($Servername in $DSServernames)
{
#for the query we use \Default if not a named instance but want to keep the original SQL instance info for table insertion
IF($Servername.Instance -eq "NULL")
{
$location = $Servername.ServerName+"\Default"
$srv = $Servername.ServerName
}
else
{
$location = $Servername.ServerName+"\"+$Servername.Instance
$srv = $location
}
$updinst = $Servername.Instance
$updsrv = $Servername.ServerName
#this is where we actually get the data (using the -force switch to also include system databases)
$backupstatus = dir -force SQLSERVER:\SQL\$location\Databases | SELECT Name, ExtendedProperties, LastBackupDate, LastDifferentialBackupDate
#if we were able to build a recordset update the timestamp in the config table to show we captured data
if($?)
{
Write-Host "$updsrv $updinst"
Invoke-SQLCmd -ServerInstance ".\YUKON" -Database "SQLMonitor" -Query "UPDATE SQLInstances SET CheckBackupDate = getdate() WHERE ServerName = '$updsrv' AND (Instance = '$updinst' OR Instance IS NULL)"
foreach ($BackupRecord in $backupstatus)
{
$db = $BackupRecord.Name
$exp = $BackupRecord.ExtendedProperties
$lbu = $BackupRecord.LastBackupDate
$ldbu = $BackupRecord.LastDifferentialBackupDate
#In the event that there has never been a backup or differential backup for a database we need to set a real datetime value
#there is a default value of 01/01/0001 00:00:00 which is not a real datetime
IF ($lbu -eq "01/01/0001 00:00:00") { $lbu = "01/01/1900 00:00:00"}
IF ($ldbu -eq "01/01/0001 00:00:00") { $ldbu = "01/01/1900 00:00:00"}
#if the last full backup is newer than the last differential use that, otherwise use the differential date
IF ($lbu -gt $ldbu)
{ $lb = $lbu }
else
{ $lb = $ldbu }
#uncomment the next line to view info on screen
#Write-Host "$srv, $db, $exp, $lbu, $ldbu, $lb"
#insert it into the table
Invoke-SQLCmd -ServerInstance ".\YUKON" -Database "SQLMonitor" -Query "INSERT INTO dbo.DatabaseBackups (ServerName, DatabaseName, ExtendedProperties, LastFullBackupDate, LastDifferentialBackupDate, LastBackupDate) VALUES ('$srv', '$db', '$exp', '$lbu', '$ldbu', '$lb')"
}
}
}
Now if you query the DatabaseBackups table you’ll see a list of SQL Instances, databases, their last full and differential dates along with when that info was last checked, the most recent backup (be it full or differential) and the delta (in days) between that last backup and when we performed the check.
In the SQLInstances table you will see an updated CheckBackupDate for any server marked that was successfully checked.

