Ok, so for the most part this happens automagically and you won't have to mess with it at all. However, for some reason I found that a few recovery points I had manually created with Microsoft Support while troubleshooting an issue were not going away. How do I know? Here's the script for determining if you have expired but not pruned recovery points (source):
#displays all RP for data sources and shows which RP’s would be deleted by the regular pruneshadowcopies.ps1 # Outputs to a logfile: C:\Program Files\Microsoft DPM\DPM\bin\SHOW-PRUNESHADOWCOPIES.LOG
Author : Mike J #Date : 02/24/2009 $version="V1.0"
$date=get-date $logfile="SHOW-PRUNESHADOWCOPIES.LOG.txt"
function GetDistinctDays([Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.OMCommon.ProtectionGroup] $group, [Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.OMCommon.Datasource] \(ds) { if(\)group.ProtectionType -eq [Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.OMCommon.ProtectionType]::DiskToTape) { return 0 } $scheduleList = get-policyschedule -ProtectionGroup \(group -ShortTerm if(\)ds -is [Microsoft.Internal.EnterpriseStorage.Dls.UI.ObjectModel.FileSystem.FsDataSource]) { $jobType = [Microsoft.Internal.EnterpriseStorage.Dls.Intent.JobTypeType]::ShadowCopy } else { \(jobType = \[Microsoft.Internal.EnterpriseStorage.Dls.Intent.JobTypeType\]::FullReplicationForApplication if(\)ds.ProtectionType -eq [Microsoft.Internal.EnterpriseStorage.Dls.Intent.ReplicaProtectionType]::ProtectFromDPM) { return 2 } } write-host "Look for jobType $jobType"
foreach($schedule in $scheduleList) { write-host("schedule jobType {0}" -f \(schedule.JobType) if(\)schedule.JobType -eq \(jobType) { return \[Math\]::Ceiling((\)schedule.WeekDays.Length * $ds.RecoveryRangeinDays) / 7) } }
return 0 }
function IsShadowCopyExternal($id) { $result = $false;
$ctx = New-Object -Typename Microsoft.Internal.EnterpriseStorage.Dls.DB.SqlContext $ctx.Open()
$cmd = $ctx.CreateCommand() \(cmd.CommandText = "select COUNT(\*) from tbl\_RM\_ShadowCopy where shadowcopyid = ‘\)id’" write-host $cmd.CommandText $countObj = $cmd.ExecuteScalar() write-host \(countObj if (\)countObj -eq 0) { $result = $true } $cmd.Dispose() $ctx.Close()
return $result }
function IsShadowCopyInUse($id) { $result = $true;
$ctx = New-Object -Typename Microsoft.Internal.EnterpriseStorage.Dls.DB.SqlContext $ctx.Open()
$cmd = $ctx.CreateCommand() \(cmd.CommandText = "select ArchiveTaskId, RecoveryJobId from tbl\_RM\_ShadowCopy where ShadowCopyId = ‘\)id’" write-host $cmd.CommandText \(reader = \(cmd.ExecuteReader() while(\)reader.Read()) { if (\)reader.IsDBNull(0) -and $reader.IsDBNull(1)) { $result = $false } } $cmd.Dispose() $ctx.Close()
return $result }
"**********************************" > $logfile "Version $version" >> $logfile get-date >> $logfile
$dpmservername = &"hostname"
$dpmsrv = connect-dpmserver $dpmservername
if (!$dpmsrv) { write-host "Unable to connect to $dpmservername" exit 1 }
write-host $dpmservername "Selected DPM server = $DPMservername" >> $logfile $pgList = get-protectiongroup \(dpmservername if (!\)pgList) { write-host "No PGs found" disconnect-dpmserver $dpmservername exit 2 }
write-host("Number of ProtectionGroups = {0}" -f $pgList.Length) $replicaList = @{} $latestScDateList = @{}
foreach($pg in $pgList) { $dslist = get-datasource \(pg if (\)dslist.length -gt 0) { write-host("Number of datasources in this PG = {0}" -f $dslist.length) ("Number of datasources in this PG = {0}" -f $dslist.length) >> \(logfile } Foreach (\)ds in $dslist) { write-host("DS NAME= \(ds") ("DS NAME= \(ds") >>\)logfile } foreach (\)ds in $dslist) { $rplist = get-recoverypoint $ds | where { $_.DataLocation -eq ‘Disk’ } write-host("Number of recovery points for $ds {0}" -f $rplist.length) ("Number of recovery points for $ds {0}" -f \(rplist.length) >>\)logfile $countDistinctDays = GetDistinctDays $pg \(ds write-host("Number of days with fulls = (countDistinctDays") ("Number of days with fulls = \(countDistinctDays") >>\)logfile if(\)countDistinctDays -eq 0) { write-host "D2T PG. No recovery points to delete" "D2T PG. No recovery points to delete" >>\)logfile continue; } \(replicaList\[\)ds.ReplicaPath] = $ds.RecoveryRangeinDays \(latestScDateList\[\)ds.ReplicaPath] = new-object DateTime 0,0 \(lastDayOfRetentionRange = (\[DateTime\]::UtcNow).AddDays(\)ds.RecoveryRangeinDays * -1); write-host("Distinct days to count = {0}. LastDayOfRetentionRange = {1} " -f $countDistinctDays, $lastDayOfRetentionRange) ("Distinct days to count = {0}. LastDayOfRetentionRange = {1} " -f $countDistinctDays, \(lastDayOfRetentionRange) >>\)logfile $distinctDays = 0; $lastDistinctDay = (get-Date).Date $numberOfRecoveryPointsDeleted = 0
if (\(rplist) { foreach (\)rp in (\(rplist | sort-object -property UtcRepresentedPointInTime -descending)) { if (\)rp) { if ($rp.UtcRepresentedPointInTime.Date -lt $lastDistinctDay) { $distinctDays += 1 $lastDistinctDay = \(rp.UtcRepresentedPointInTime.Date } write-host(" (ds") (" \(ds") >>\)logfile write-host(" Recovery Point #\)distinctdays RPtime={0}" -f \(rp.UtcRepresentedPointInTime) (" Recovery Point #\)distinctdays RPtime={0}" -f \(rp.UtcRepresentedPointInTime) >>\)logfile if ((\)distinctDays -gt \(countDistinctDays) -and (\)rp.UtcRepresentedPointInTime -lt \(lastDayOfRetentionRange)) { write-host ("Recovery Point would be deleted ! – RPtime={0}" -f \(rp.UtcRepresentedPointInTime) -foregroundcolor red ("Recovery Point would be deleted ! – RPtime={0} <<<<<<>\)logfile #remove-recoverypoint \(rp -ForceDeletion -confirm:\)true | out-null \(numberOfRecoveryPointsDeleted += 1 } else { write-host " Recovery point not expired yet" " Recovery point not yet expired" >>\)logfile } } else { write-host "Got a NULL rp" "Got a NULL rp" >>\)logfile } }
write-host "Number of RPs that would be deleted = $numberOfRecoveryPointsDeleted" "Number of RPs that would be deleted = \(numberOfRecoveryPointsDeleted" >>\)logfile } } }
disconnect-dpmserver $dpmservername write-host "Exiting from script"
exit
Now, you could run the prune script in the DPB/bin folder but for me that didn't resolve the recovery points that were sitting out there and long past their expiration date. So, here's the script which does that for you (source): ############################################# # Script will delete ALL recovery points from all protection groups over X days old. # Takes server name and days as input arguements: # RemoveRecoveryPoints.ps1 "DPMServername" Days #############################################
param([string] $dpmname, [int32] $days )
function Usage()
{ write-host write-host "Usage::" write-host "RemoveRecoveryPoints.ps1 "DPMServername" Days(In int)" write-host }
if(("-?","-help") -contains $args[0]) { Usage exit 0 }
if(!$dpmname) { $dpmname = read-host "DPMServerName:" }
if(!$days) { $days = read-host "Number of Days" }
$pgList = Get-ProtectionGroup $dpmname
Foreach($pg in $pgList) { $Name = $pg.FriendlyName Write-Host "Getting Data Source list for PG $Name..." $dsList = Get-Datasource $pg
Foreach($ds in $dsList) { $Name = $ds.Name Write-Host "Getting Recovery point list for Data-Source $Name ..." $rpList = Get-RecoveryPoint $ds
Foreach($rp in $rpList) { $date = Get-Date $datediff = $date - $rp.RepresentedPointInTime $rpDays = $datediff.Days Write-Host "Recovery Point is $rpDays days old "
if($rpDays -ge $days) { Write-Host "Removing Recovery Point older than $rpDays" Remove-RecoveryPoint -RecoveryPoint $rp }
}
}
}
Don't worry about running the script and having it remove data you don't want it to. You will be prompted for each and asked if you want to remove them. Expect some errors as the script runs. What I did was run the script, get some errors, run it a second time get some more errors, then on the third run I had no points left to remove.
It's not a perfect solution, but it works.