DPM 2010: Reporting Newest and Oldest Recovery Points With PowerShell

A few days back I was asked to generate a report that shows the oldest and newest backups for each protection member in DPM. DPM Management Shell (DPM’s PowerShell snap-in) has all kinds of built-in cmdlets that give PowerShell access to Data Protection Manager information.  To view the cmdlets specific to DPM, you can use the Get-DPMCommand cmdlet.  You will see that there are dozens of DPM cmdlets that can perform a variety of tasks.

To find recovery points for a protection member, there is a cmdlet called Get-RecoveryPoint. Rather than “protection member” this cmdlet uses the term “datasource” as a parameter, and gives you every recovery point for that datasource . Datasource is a specific member of a “Protection Group.” There are get-Datasource and get-ProtectionGroup cmdlets that come in handy here.

  • Protection Group
    • Datasource
      • Recovery Points

Each Protection group has datasources within that I need to retrieve. Here is an example DPM configuration:

  • Important Servers
    • WSUS C Drive
    • WSUS SQL Database
  • Domain Controllers
    • DC1 system state
    • DC1 bare metal recovery
    • DC2 system state
  • File Sharing
    • File Server 1
    • File Server 2

To find every datasource and store them in an array, you can do this:

$dsarray = @(Get-ProtectionGroup -DPMServer <serverName> | foreach {Get-Datasource $_})

From here, you could use the get-RecoveryPoint cmdlet on each datasource to find all the recovery points for each. But, if you only need the latest and oldest recovery points, each datasource has properties that can be referenced. So, rather than looking up every recovery point and iterating through them find the latest and oldest, it is more efficient to reference the datasource’s properties.

We do run into a problem when doing this though. When I find a datasource and call a property, $dsarray.LatestRecoveryPoint for instance, PowerShell returns $null or “01/01/0001” for the date. The problem is, when you call a property on a particular datasource PowerShell goes and finds all (not just the one you asked for) the properties associated with this datasource (there are many properties associated with datasource, try get-datasource | get-member to look at them), and this runs asynchronously. If I write the property to file right when I call it for the first time “01/01/0001” is written. You have to give PowerShell time to find all the properties for your datasources before you proceed to call any of them for use. There is a great article on the code you need for this here:

http://blogs.technet.com/b/dpm/archive/2010/09/11/why-good-scripts-may-start-to-fail-on-you-for-instance-with-timestamps-like-01-01-0001-00-00-00.aspx

If you look at my script (below), I used a similar method in the Function InitializeDatasourceProperties. Here is a screen shot showing a datasource property returning null, and then returning valid values a short moment later.

$ds.LatestRecoveryPoint returns a null value on its first call

Once I give PowerShell time to “initialize” the datasource properties, I can then write the recovery point data to an HTML table using the Out-File cmdlet. I coded for “LatestRecoveryPoints” older than 36 hours to appear in red, so when this script runs I can see every protection member in DPM right away seeing which members haven’t backed up in the last 36 hours or haven’t backed up at all, and can then act accordingly.

Here is the full script: (make sure to change $dpmservers to the name of your DPM Server(s)! – it will work for 1 or more if they are listed properly in the array, and under #Main put your desired file path for the output instead of what is already there if you like (it writes to the C drive by default).


#FindRecoveryPoints.ps1
#This script finds the newest and oldest recovery points from
#your dpm server and writes them to an html table
# Put all your dpm server names in the array. If there is only 1, that is fine.
$dpmservers = @("<your server name here!","Another server name if you have another")


Function InitializeDatasourceProperties ($datasources)
{
$Eventcount = 0
For($i = 0;$i -lt $datasources.count;$i++)
{
[void](Register-ObjectEvent $datasources[$i] -EventName DataSourceChangedEvent -SourceIdentifier "DPMExtractEvent$i" -Action{$Eventcount++})
}
$datasources | select LatestRecoveryPoint > $null
$begin = get-date
While (((Get-Date).subtract($begin).seconds -lt 10) -and ($Eventcount -lt $datasources.count) ) {sleep -Milliseconds 250}
Unregister-Event -SourceIdentifier DPMExtractEvent* -Confirm:$false
}


#Writes name and recovery point info for current iteration of $ds into HTML table. Newest recovery points not in the last 36 hours are red.
#If there are no recovery points(1/1/0001), the table reads "Never" in red.
Function WriteTableRowToFile($ThisDatasource, $dpmserver)
{
$rpLatest = $ThisDatasource.LatestRecoveryPoint
$rpOldest = $ThisDatasource.OldestRecoveryPoint


"<tr><td>" | Out-File $filename -Append -Confirm:$false
$ThisDatasource.ProductionServerName | Out-File $filename -Append -Confirm:$false
"</td><td>" | Out-File $filename -Append -Confirm:$false
$ThisDatasource.Name | Out-File $filename -Append -Confirm:$false
If($rpLatest -lt $date.AddHours(-36)){
If($rpLatest.ToShortDateString() -eq "1/1/0001"){
"</td><td><b><font style=`"color: #FF0000;`">Never</font></b>" | Out-File $filename -Append -Confirm:$false
}
Else{
"</td><td><b><font style=`"color: #FF0000;`">" | Out-File $filename -Append -Confirm:$false
$rpLatest.ToShortDateString() | Out-File $filename -Append -Confirm:$false
"</font></b>" | Out-File $filename -Append -Confirm:$false
}
}
If($rpLatest -ge $date.AddHours(-36)){
"</td><td>" | Out-File $filename -Append -Confirm:$false
$rpLatest.ToShortDateString() | Out-File $filename -Append -Confirm:$false
"</td>" | Out-File $filename -Append -Confirm:$false
}


If($rpOldest.ToShortDateString() -eq "1/1/0001"){
"<td><b><font style=`"color: #FF0000;`">Never</font></b></td><td>" | Out-File $filename -Append -Confirm:$false
}
Else{
"<td>" | Out-File $filename -Append -Confirm:$false
$rpOldest.ToShortDateString()| Out-File $filename -Append -Confirm:$false
"</td><td>" | Out-File $filename -Append -Confirm:$false
}
($rpLatest - $rpOldest).Days | Out-File $filename -Append -Confirm:$false
"</td><td>" | Out-File $filename -Append -Confirm:$false


$dpmServer | out-file $filename -append -confirm:$false
"</td></tr>" | Out-File $filename -Append -Confirm:$false
}


##Main## The date is used to find recovery points that are too old, and to generate a file #name.
$date = get-date
$filedate = get-date -uformat '%m-%d-%Y-%H%M%S'
$filename = "C:\DPMRecoveryPoints"+ $filedate + ".htm"


## HTML table created
"<html><caption><font style=`"color: #FF0000;`"><b>Red</b></font> = not backed up in the last 36 hours, or has <font style=`"color: #FF0000;`">
<b>Never</b></font> been backed up</caption><table border =`"1`" style=`"text-align:center`" cellpadding=`"5`"><th style=`"color:#6698FF`">
<big>DPM Backups</big></th><body><tr><th>Protection Member</th><th>Datasource</th><th>Newest Backup</th><th>Oldest Backup</th><th># of Days</th>
<th>DPM Server</th></tr>" | Out-File $filename -Confirm:$false


Write-Host "Generating Protection Group Report"
#Disconnect-DPMserver = clear cache, this makes sure that selecting LatestRecoveryPoint in the InitializeDataSourceProperties is an event,
#thus confirming that all the recovery points are retrieved before the script moves any further
Disconnect-DPMserver
#Find all datasources within each protection group
Write-Host "Locating Datasources"
foreach ($dpmserver in $dpmservers){
$dsarray = @(Get-ProtectionGroup -DPMServer $dpmserver | foreach {Get-Datasource $_}) | Sort-Object ProtectionGroup, ProductionServerName
Write-Host " Complete" -ForegroundColor Green
Write-Host "Finding Recovery Points"
InitializeDatasourceProperties $dsarray
Write-Host " Complete" -ForegroundColor Green
Write-Host "Writing to File"
For($i = 0;$i -lt $dsarray.count;$i++)
{
WriteTableRowToFile $dsarray[$i] $dpmserver
}
Disconnect-DPMserver
}
Write-Host " Complete" -ForegroundColor Green
Write-Host "The report has been saved to"$filename
"</body></html>" | Out-File $filename -Append -Confirm:$false

Here is what the output looks like:

A portion of the beautiful HTML output
This entry was posted in Data Protection Manager, DPM, HTML reporting, Recovery Points, Scripting, Windows PowerShell and tagged , , , , , , , , . Bookmark the permalink.

10 Responses to DPM 2010: Reporting Newest and Oldest Recovery Points With PowerShell

  1. Steve Payne says:

    Can you do this for multiple DPM servers instead of just one?

    • Steve,
      I have searched for a way to find the names of your servers if you have more than one (with a cmdlet), but could not find anything. Of course, you could always hard code the names of your servers into an array, and then parse through them individually. Ive updated the script so that there is a global variable that takes the names of all dpm servers in an array (if there is 1 it will work fine also), it then parses through each, finding the recovery points for each, then it outputs them into the same html file with a column named “DPM server” containing the server name. If you wanted, you could create an array of file names which are based on the dpm server name and write to different files for different dpm servers. I hope this helps, let me know if there are any other questions.
      Andrew

  2. Brian Merrifield says:

    Great script. One issue I ran into is the script attempts to write to the “U:” drive by default from this line:

    $filename = “U:/DPMReport_RecoveryPoints_”+ $filedate + “.htm”

    I adjusted to just write to my C: drive

  3. Pingback: IT Solutions Technology Blog » Blog Archive » Microsoft Data Protection Manager 2010 - reporting newest and oldest recovery points

  4. Tom says:

    simple question: dont you need to Disconnect-DPMserver at the end of the “foreach ($dpmserver in $dpmservers)” loop ? , otherwise it wont get the Protection Groups for the next server in the array because a connection already exists to a previous server. Please advise… awesome script BTW
    -Tom

    • Tom,
      That sounds right. I didn’t properly think it through – I only have 1 DPM server so when I threw that part in I didn’t think about disconnecting. Ill add that at the end of the loop. Thanks.

  5. Paul Hoh says:

    Hi,

    Is it possible to have this script to output the results into an Excel spreadsheet so that a monthly report can be generated for reporting purpose?

  6. Stacy Machannagari says:

    How to add a column displaying the ChildDatasource next to the Protection member column? Love the script!!! Exactly what was needed and couldnt get from DPM alerts.

  7. david says:

    Hi Andrew,

    Is it possible to filter only certain protection groups recovery point status in reports? if yes please let me know which parameter have to change in this script

Leave a comment