コード例 #1
0
        /// <summary>
        /// This method is called to clear the contents of an <see cref="SPBackupCatalog"/> to remove
        /// all backup sets it contains. This operation ensures that an spbrtoc.xml file remains with
        /// any remnants of restore and non-backup operations, but backup set references (and folders
        /// containing the sets) are deleted.
        /// <para>"Removal" of a backup set involves two different operations. The first operation is
        /// the actual updating of the table of contents file to reflect that the backup set is no
        /// longer present. The second part of the removal is the actual deletion of the backup
        /// subfolders housing the backup data being trimmed out.</para>
        /// </summary>
        /// <seealso cref="SPBackupCatalog"/>
        /// <seealso cref="SPCmdletRemoveSPBackupCatalog"/>
        /// <param name="workingCatalog">An <see cref="SPBackupCatalog"/> object that represents the
        /// backup set to be cleared.</param>
        /// <remarks>If an exception occurs, it is assumed that the calling method will trap it and
        /// handle it accordingly (including a possible re-throw).</remarks>
        public static void EmptyBackupCatalog(SPBackupCatalog workingCatalog)
        {
            // Use the XML for the backup catalog's TOC to grab all of the full backup SPHistoryObject
            // nodes and reverse sort them by directory number.
            XElement tocFile            = workingCatalog.TableOfContents;
            String   backupRoot         = workingCatalog.CatalogPath;
            var      fullHistoryObjects = tocFile.Elements().Where(ho =>
                                                                   (ho.Element("SPBackupMethod").Value.Trim().ToUpper() == "FULL") &&
                                                                   ho.Element("SPConfigurationOnly").Value.Trim().ToUpper() == "FALSE" &&
                                                                   ho.Element("SPIsBackup").Value.Trim().ToUpper() == "TRUE").OrderByDescending(fho =>
                                                                                                                                                Convert.ToInt32(fho.Element("SPDirectoryNumber").Value));

            // We need each of the folders we'll be deleting as part of the operation. This has to be
            // dumped to a list because subsequent deferred evaluation (when deleting folders) wouldn't
            // work properly following PHASE 1 (below).
            var backupSetsToDelete = tocFile.Elements().Where(ho =>
                                                              (ho.Element("SPIsBackup").Value.Trim().ToUpper() == "TRUE")).Select(dho =>
                                                                                                                                  new { ID = dho.Element("SPId").Value, Folder = Path.Combine(backupRoot, dho.Element("SPDirectoryName").Value) }).ToList();

            // CLEANUP PHASE 1: clean-up the SPHistoryObject elements in the backup TOC file and save off the file
            tocFile.Elements().Where(ho =>
                                     (ho.Element("SPIsBackup").Value.Trim().ToUpper() == "TRUE")).Remove();
            workingCatalog.TableOfContents = tocFile;
            workingCatalog.SaveTableOfContents();

            // CLEANUP PHASE 2: Iterate through the backup folders to delete and clear them out.
            foreach (var backupSet in backupSetsToDelete)
            {
                Directory.Delete(backupSet.Folder, true);
            }
        }
コード例 #2
0
        /// <summary>
        /// This method is called when the backup set pointers contained within <paramref name="workingCatalog"/>
        /// need to be validated and corrected to make them consistent with the current path held by
        /// the SPBackupCatalog. This type of correction becomes necessary when a backup catalog that
        /// was created at one path (for example, "\\BackupServer\Backups") is moved to another path
        /// (for example, "\\BackupServer\Restores") and referenced.
        /// <para>In this scenario, folder pointers that are internal to the backup catalog will still
        /// point to "\\BackupServer\Backups." Use of this method will correct those pointers to reference
        /// the path currently specified by the <paramref name="newCatalogPath"/>.</para>
        /// </summary>
        /// <seealso cref="SPBackupCatalog"/>
        /// <seealso cref="SPCmdletSetSPBackupCatalog"/>
        /// <param name="workingCatalog">An <see cref="SPBackupCatalog"/> object that represents the
        /// backup set to be parsed and path-corrected.</param>
        /// <param name="newCatalogPath">A <c>String</c> containing the new root path that should be used
        /// for modifying the backup directory paths in the <paramref name="workingCatalog"/>.</param>
        /// <remarks>If an exception occurs, it is assumed that the calling method will trap it and
        /// handle it accordingly (including a possible re-throw).</remarks>
        public static void UpdateBackupCatalogPaths(SPBackupCatalog workingCatalog, String newCatalogPath)
        {
            // We need the XML structure for the table of contents file.
            XElement tocFile = workingCatalog.TableOfContents;

            // Perform the modifications
            XElement workingToc = UpdateBackupCatalogPaths(tocFile, newCatalogPath);

            // Assign the updated TOC structure back to the catalog and save it off.
            workingCatalog.TableOfContents = workingToc;
            workingCatalog.SaveTableOfContents();
        }
コード例 #3
0
        /// <summary>
        /// This method is called to trim the contents of an <see cref="SPBackupCatalog"/> to retain
        /// as many backups as possible that allow the backup catalog to remain below the retention
        /// size specified by the <paramref name="retainSize"/> parameter. Older full backups (and any
        /// differential backups that depend on those full backups) are removed from the backup set
        /// from oldest to newest, and the overall size of the backup set is checked with each removal.
        /// If the backup catalog is too big after a removal or set of removals, the process is repeated.
        /// <para>"Removal" of a backup set involves two different operations. The first operation is
        /// the actual updating of the table of contents file to reflect that the backup set is no
        /// longer present. The second part of the removal is the actual deletion of the backup
        /// subfolders housing the backup data being trimmed out. As indicated, this process is
        /// carried out recursively until the size constraints are met.</para>
        /// </summary>
        /// <seealso cref="SPBackupCatalog"/>
        /// <seealso cref="SPCmdletRemoveSPBackupCatalog"/>
        /// <param name="workingCatalog">An <see cref="SPBackupCatalog"/> object that represents the
        /// backup set to be trimmed.</param>
        /// <param name="retainSize">An <c>Int64</c> that contains the total maximum size of all backups
        /// to retain in the <paramref name="workingCatalog"/>.</param>
        /// <param name="ignoreErrors">TRUE if backup set errors should be ignored for purposes of
        /// determining what to delete, FALSE if backup sets with errors should not count against the
        /// number of backups retained.</param>
        /// <remarks>If an exception occurs, it is assumed that the calling method will trap it and
        /// handle it accordingly (including a possible re-throw).</remarks>
        public static void TrimBackupCatalogSize(SPBackupCatalog workingCatalog, Int64 retainSize, Boolean ignoreErrors)
        {
            // Use the XML for the backup catalog's TOC to grab all of the full backup SPHistoryObject
            // nodes and reverse sort them by directory number.
            var backupRoot = workingCatalog.CatalogPath;
            var tocFile    = workingCatalog.TableOfContents;
            IOrderedEnumerable <XElement> fullHistoryObjects;

            // If we aren't ignoring errors, then we'll need to limit (potentially) the fullHistoryObjects
            // needed to just those backup sets that don't contain errors.
            if (ignoreErrors)
            {
                // All SPHistoryObjects are valid
                fullHistoryObjects = tocFile.Elements().Where(ho =>
                                                              (ho.Element("SPBackupMethod").Value.Trim().ToUpper() == "FULL") &&
                                                              ho.Element("SPConfigurationOnly").Value.Trim().ToUpper() == "FALSE" &&
                                                              ho.Element("SPIsBackup").Value.Trim().ToUpper() == "TRUE").OrderByDescending(fho =>
                                                                                                                                           Convert.ToInt32(fho.Element("SPDirectoryNumber").Value));
            }
            else
            {
                // Only SPHistoryObjects with a zero error count are valid
                fullHistoryObjects = tocFile.Elements().Where(ho =>
                                                              (ho.Element("SPBackupMethod").Value.Trim().ToUpper() == "FULL") &&
                                                              ho.Element("SPConfigurationOnly").Value.Trim().ToUpper() == "FALSE" &&
                                                              ho.Element("SPIsBackup").Value.Trim().ToUpper() == "TRUE" &&
                                                              Convert.ToInt32(ho.Element("SPErrorCount").Value) == 0).OrderByDescending(fho =>
                                                                                                                                        Convert.ToInt32(fho.Element("SPDirectoryNumber").Value));
            }

            // We need to enter into a repeating delete cycle until one of two conditions are met:
            // 1. We have only one full backup set (plus any number of differentials) left
            // 2. The backup catalog is equal to our below the retention size supplied
            while (fullHistoryObjects.Count() > 1 && workingCatalog.CatalogSize > retainSize)
            {
                // Get the ID of the next-to-the-last full backup set; i.e., the last full backup
                // set that will remain in the catalog after the delete. Everything below it in the
                // directory number sequence will be wiped.
                var lastDirNumber = Convert.ToInt32(fullHistoryObjects.ElementAt(fullHistoryObjects.Count() - 2).Element("SPDirectoryNumber").Value);

                // We need each of the folders we'll be deleting as part of the operation. This has to be
                // dumped to a list because subsequent deferred evaluation (when deleting folders) wouldn't
                // work properly following PHASE 1 (below).
                var backupSetsToDelete = tocFile.Elements().Where(ho =>
                                                                  (ho.Element("SPIsBackup").Value.Trim().ToUpper() == "TRUE" &&
                                                                   Convert.ToInt32(ho.Element("SPDirectoryNumber").Value) < lastDirNumber)).Select(dho =>
                                                                                                                                                   new { ID = dho.Element("SPId").Value, Folder = Path.Combine(backupRoot, dho.Element("SPDirectoryName").Value) }).ToList();

                // CLEANUP PHASE 1: clean-up the SPHistoryObject elements in the backup TOC file and save off the file
                tocFile.Elements().Where(ho =>
                                         (ho.Element("SPIsBackup").Value.Trim().ToUpper() == "TRUE" &&
                                          Convert.ToInt32(ho.Element("SPDirectoryNumber").Value) < lastDirNumber)).Remove();
                workingCatalog.TableOfContents = tocFile;
                workingCatalog.SaveTableOfContents();

                // CLEANUP PHASE 2: Iterate through the backup folders to delete and clear them out.
                foreach (var backupSet in backupSetsToDelete)
                {
                    Directory.Delete(backupSet.Folder, true);
                }

                // Refresh the working catalog and re-query for the needed objects.
                workingCatalog.Refresh();
                if (ignoreErrors)
                {
                    // All SPHistoryObjects are valid
                    fullHistoryObjects = tocFile.Elements().Where(ho =>
                                                                  (ho.Element("SPBackupMethod").Value.Trim().ToUpper() == "FULL") &&
                                                                  ho.Element("SPConfigurationOnly").Value.Trim().ToUpper() == "FALSE" &&
                                                                  ho.Element("SPIsBackup").Value.Trim().ToUpper() == "TRUE").OrderByDescending(fho =>
                                                                                                                                               Convert.ToInt32(fho.Element("SPDirectoryNumber").Value));
                }
                else
                {
                    // Only SPHistoryObjects with a zero error count are valid
                    fullHistoryObjects = tocFile.Elements().Where(ho =>
                                                                  (ho.Element("SPBackupMethod").Value.Trim().ToUpper() == "FULL") &&
                                                                  ho.Element("SPConfigurationOnly").Value.Trim().ToUpper() == "FALSE" &&
                                                                  ho.Element("SPIsBackup").Value.Trim().ToUpper() == "TRUE" &&
                                                                  Convert.ToInt32(ho.Element("SPErrorCount").Value) == 0).OrderByDescending(fho =>
                                                                                                                                            Convert.ToInt32(fho.Element("SPDirectoryNumber").Value));
                }
            }
        }
コード例 #4
0
        /// <summary>
        /// This method is called to trim the contents of an <see cref="SPBackupCatalog"/> to retain
        /// only a certain number of full backups. The method is responsible for ensuring that only
        /// the most recent number of full backups specified are retained. Older full backups (and any
        /// differential backups that depend on those full backups) are removed from the backup set.
        /// <para>"Removal" of a backup set involves two different operations. The first operation is
        /// the actual updating of the table of contents file to reflect that the backup set is no
        /// longer present. The second part of the removal is the actual deletion of the backup
        /// subfolders housing the backup data being trimmed out.</para>
        /// </summary>
        /// <seealso cref="SPBackupCatalog"/>
        /// <seealso cref="SPCmdletRemoveSPBackupCatalog"/>
        /// <param name="workingCatalog">An <see cref="SPBackupCatalog"/> object that represents the
        /// backup set to be trimmed.</param>
        /// <param name="retainCount">An <c>Int32</c> that contains the total number of full backups
        /// to retain in the <paramref name="workingCatalog"/>.</param>
        /// <param name="ignoreErrors">TRUE if backup set errors should be ignored for purposes of
        /// determining what to delete, FALSE if backup sets with errors should not count against the
        /// number of backups retained.</param>
        /// <remarks>If an exception occurs, it is assumed that the calling method will trap it and
        /// handle it accordingly (including a possible re-throw).</remarks>
        public static void TrimBackupCatalogCount(SPBackupCatalog workingCatalog, Int32 retainCount, Boolean ignoreErrors)
        {
            // Use the XML for the backup catalog's TOC to grab all of the full backup SPHistoryObject
            // nodes and reverse sort them by directory number.
            var tocFile = workingCatalog.TableOfContents;
            IOrderedEnumerable <XElement> fullHistoryObjects;

            // If we aren't ignoring errors, then we'll need to limit (potentially) the fullHistoryObjects
            // needed to just those backup sets that don't contain errors.
            if (ignoreErrors)
            {
                // All SPHistoryObjects are valid
                fullHistoryObjects = tocFile.Elements().Where(ho =>
                                                              (ho.Element("SPBackupMethod").Value.Trim().ToUpper() == "FULL") &&
                                                              ho.Element("SPConfigurationOnly").Value.Trim().ToUpper() == "FALSE" &&
                                                              ho.Element("SPIsBackup").Value.Trim().ToUpper() == "TRUE").OrderByDescending(fho =>
                                                                                                                                           Convert.ToInt32(fho.Element("SPDirectoryNumber").Value));
            }
            else
            {
                // Only SPHistoryObjects with a zero error count are valid
                fullHistoryObjects = tocFile.Elements().Where(ho =>
                                                              (ho.Element("SPBackupMethod").Value.Trim().ToUpper() == "FULL") &&
                                                              ho.Element("SPConfigurationOnly").Value.Trim().ToUpper() == "FALSE" &&
                                                              ho.Element("SPIsBackup").Value.Trim().ToUpper() == "TRUE" &&
                                                              Convert.ToInt32(ho.Element("SPErrorCount").Value) == 0).OrderByDescending(fho =>
                                                                                                                                        Convert.ToInt32(fho.Element("SPDirectoryNumber").Value));
            }

            // If we've got more full history objects than our count, then it's time to do some trimming.
            if (fullHistoryObjects.Count() > retainCount)
            {
                // We now have all of the SPHistoryObject nodes that correspond to full backups, and they
                // are sorted from most recent to oldest. We want to keep the most recent ones (number
                // specified by retainCount) and dump everything else.
                //
                // Get the directory number of the last full backup we want to keep; anything below that
                // will be deleted. We'll also get a quick reference to the backup root folder.
                Int32  lastDirNumber = Convert.ToInt32(fullHistoryObjects.ElementAt(retainCount - 1).Element("SPDirectoryNumber").Value);
                String backupRoot    = workingCatalog.CatalogPath;

                // We need each of the folders we'll be deleting as part of the operation. This has to be
                // dumped to a list because subsequent deferred evaluation (when deleting folders) wouldn't
                // work properly following PHASE 1 (below).
                var backupSetsToDelete = tocFile.Elements().Where(ho =>
                                                                  (ho.Element("SPIsBackup").Value.Trim().ToUpper() == "TRUE" &&
                                                                   Convert.ToInt32(ho.Element("SPDirectoryNumber").Value) < lastDirNumber)).Select(dho =>
                                                                                                                                                   new { ID = dho.Element("SPId").Value, Folder = Path.Combine(backupRoot, dho.Element("SPDirectoryName").Value) }).ToList();

                // CLEANUP PHASE 1: clean-up the SPHistoryObject elements in the backup TOC file and save off the file
                tocFile.Elements().Where(ho =>
                                         (ho.Element("SPIsBackup").Value.Trim().ToUpper() == "TRUE" &&
                                          Convert.ToInt32(ho.Element("SPDirectoryNumber").Value) < lastDirNumber)).Remove();
                workingCatalog.TableOfContents = tocFile;
                workingCatalog.SaveTableOfContents();

                // CLEANUP PHASE 2: Iterate through the backup folders to delete and clear them out.
                foreach (var backupSet in backupSetsToDelete)
                {
                    Directory.Delete(backupSet.Folder, true);
                }
            }
        }
コード例 #5
0
        /// <summary>
        /// This method is used to examine an <see cref="SPBackupCatalog"/> and build a String that can
        /// be used as the body of an e-mail to communicate the status of the last backup run.
        /// </summary>
        /// <param name="workingCatalog">An <see cref="SPBackupCatalog"/> that will be analyzed for
        /// purposes of building the status message.</param>
        /// <returns>A string containing HTML that can be used as the body of an e-mail message.</returns>
        public static String BuildEmailStatusBody(SPBackupCatalog workingCatalog)
        {
            // Build a list for argument insertion into the template we'll eventually pull from the
            // resources area.
            var mailArgs = new List <Object>
            {
                DateTime.Now.ToShortTimeString(),
                DateTime.Now.ToLongDateString(),
                workingCatalog.LastBackupPath,
                workingCatalog.LastBackupTopComponent,
                workingCatalog.LastBackupMethod,
                workingCatalog.LastBackupRequestor
            };

            try
            {
                mailArgs.Add(Globals.IntelligentDateTimeFormat(workingCatalog.LastBackupStart));
            }
            catch (FormatException)
            {
                mailArgs.Add(null);
            }

            // The first two arguments are the time {0} and date {1}

            // The next block is for general backup information: location {2}, top component {3},
            // backup method {4}, and requestor {5}

            // The final block is for start date/time {6}, finish date/time {7}, duration {8},
            // backup set size {9}, error count {10}, and warning count {11}

            try
            {
                mailArgs.Add(Globals.IntelligentDateTimeFormat(workingCatalog.LastBackupFinish));
            }
            catch (FormatException)
            {
                mailArgs.Add(null);
            }

            try
            {
                mailArgs.Add(Globals.IntelligentTimeFormat(workingCatalog.LastBackupDuration));
            }
            catch (FormatException)
            {
                mailArgs.Add(null);
            }

            mailArgs.Add(Globals.IntelligentBytesFormat(workingCatalog.LastBackupSize));
            mailArgs.Add(Globals.IntelligentIntegerFormat(workingCatalog.LastBackupErrorCount));
            mailArgs.Add(Globals.IntelligentIntegerFormat(workingCatalog.LastBackupWarningCount));

            // Execute a replacement against the template using the argument list we built up and return
            // it for e-mail purposes.
            var bodyTemplate = res.Send_SPBackupStatus_Status_Template;
            var paramVals    = mailArgs.ToArray();
            var mailBody     = String.Format(bodyTemplate, paramVals);

            return(mailBody);
        }
コード例 #6
0
        /// <summary>
        /// This method is called to carry out an export of backup sets contained in a backup catalog.
        /// In essence, this method does all of the heavy lifting for the Export-SPBackupCatalog cmdlet
        /// housed in the <see cref="SPCmdletExportSPBackupCatalog"/> class. All parameters for this
        /// method map in some way to acceptable parameters for the cmdlet.
        /// </summary>
        /// <seealso cref="SPCmdletExportSPBackupCatalog"/>
        public static void ExportBackupCatalog(SPBackupCatalog workingCatalog, String exportPath, String exportName,
                                               BackupSetExportMode exportMode, Int32 exportCount, Boolean ignoreErrors, Boolean useNoCompression,
                                               Boolean updateCatalogPath)
        {
            // Use the XML for the backup catalog's TOC to grab all of the full backup SPHistoryObject
            // nodes and reverse sort them by directory number.
            XElement tocFile = workingCatalog.TableOfContents;
            IOrderedEnumerable <XElement> fullHistoryObjects;

            // If we aren't ignoring errors, then we'll need to limit (potentially) the fullHistoryObjects
            // needed to just those backup sets that don't contain errors.
            if (ignoreErrors)
            {
                // All SPHistoryObjects are valid
                fullHistoryObjects = tocFile.Elements().Where(ho =>
                                                              (ho.Element("SPBackupMethod").Value.Trim().ToUpper() == "FULL") &&
                                                              ho.Element("SPConfigurationOnly").Value.Trim().ToUpper() == "FALSE" &&
                                                              ho.Element("SPIsBackup").Value.Trim().ToUpper() == "TRUE").OrderByDescending(fho =>
                                                                                                                                           Convert.ToInt32(fho.Element("SPDirectoryNumber").Value));
            }
            else
            {
                // Only SPHistoryObjects with a zero error count are valid
                fullHistoryObjects = tocFile.Elements().Where(ho =>
                                                              (ho.Element("SPBackupMethod").Value.Trim().ToUpper() == "FULL") &&
                                                              ho.Element("SPConfigurationOnly").Value.Trim().ToUpper() == "FALSE" &&
                                                              ho.Element("SPIsBackup").Value.Trim().ToUpper() == "TRUE" &&
                                                              Convert.ToInt32(ho.Element("SPErrorCount").Value) == 0).OrderByDescending(fho =>
                                                                                                                                        Convert.ToInt32(fho.Element("SPDirectoryNumber").Value));
            }

            // We've got the appropriate backup sets selected; now we may need to narrow them down based
            // on the type export action/mode specified.
            var lastKeepIndex = 0;
            var lastDirNumber = -1;

            switch (exportMode)
            {
            case BackupSetExportMode.ExportAll:
                // Everything is being exported, so our keep index is out of bounds
                lastKeepIndex = -1;
                break;

            case BackupSetExportMode.IncludeMode:
                // We're explicitly including backup sets, so we need to determine the last directory
                // for export by going to the end and count up the list to find the last one to keep.
                lastKeepIndex = fullHistoryObjects.Count() - exportCount - 1;
                break;

            case BackupSetExportMode.ExcludeMode:
                // Get the directory number of the last full backup we want to export; anything below that
                // will be ignored during export.
                lastKeepIndex = exportCount - 1;
                break;
            }

            // Determine what we're going to actually export based on keep indexes and directory numbers.
            // If the index is less than zero
            if (lastKeepIndex >= (fullHistoryObjects.Count() - 1))
            {
                // We aren't actually exporting any objects, so select a directory number that we know
                // none of the backup sets will be below.
                lastDirNumber = Int32.MinValue;
            }
            else if (lastKeepIndex >= 0)
            {
                // We're exporting some and not exporting others. Locate the element at the appropriate
                // index and figure out what its directory number is. Everything under that number will
                // be what we export.
                lastDirNumber = Convert.ToInt32(fullHistoryObjects.ElementAt(lastKeepIndex).Element("SPDirectoryNumber").Value);
            }
            else
            {
                // We have an index below zero. This means that *everything* is going to be exported.
                lastDirNumber = Int32.MaxValue;
            }

            // We've got the directory number of the backup set which marks the start of everything we
            // *don't* want to export. We now need to select all backup sets have a directory number that is
            // *less* than the last directory number and build folder paths.
            //
            // NOTE: Only full and differential content backups are included for export. Config-only backups
            // are excluded. The ignoreErrors switch is also obeyed to determine if backups with errors are
            // included in the export.
            IEnumerable <XElement> exportSelections = tocFile.Elements().Where(ho =>
                                                                               (ho.Element("SPIsBackup").Value.Trim().ToUpper() == "TRUE" &&
                                                                                ho.Element("SPConfigurationOnly").Value.Trim().ToUpper() == "FALSE" &&
                                                                                Convert.ToInt32(ho.Element("SPDirectoryNumber").Value) < lastDirNumber));

            // If we aren't ignoring errors, then we have some clean-up to do. Differential backups themselves
            // are easy to drop (no dependencies), but we'll need some special logic to clean up full backups
            // because of the fact that differentials may be tied to them.
            var additionalDirectoryExclusions = new List <Int32>();

            if (!ignoreErrors)
            {
                // Work our way "backward" through the collection. Oldest sets are at the end of the collection,
                // so if we encounter a full backup with errors we'll need to delete the differentials that are
                // tied to it.
                Boolean isBadFullBackup = false;
                for (Int32 backupIndex = exportSelections.Count() - 1; backupIndex >= 0; backupIndex--)
                {
                    // Grab a reference to the current backup and determine if it is full or differential.
                    XElement currentBackup = exportSelections.ElementAt(backupIndex);
                    if (currentBackup.Element("SPBackupMethod").Value.Trim().ToUpper() == "FULL")
                    {
                        // This could be a lone full backup, or we may be at the start of a sequence that includes
                        // any number of differential backups. Determine if the current backup has errors.
                        if (Convert.ToInt32(currentBackup.Element("SPErrorCount").Value) > 0)
                        {
                            // There are one or more errors in the full backup. It will not only need to be excluded,
                            // but we'll also have to flip the "bad backup" flag so subsequent differentials are
                            // added to the exclusions list.
                            additionalDirectoryExclusions.Add(Convert.ToInt32(currentBackup.Element("SPDirectoryNumber").Value));
                            isBadFullBackup = true;
                        }
                        else
                        {
                            // No errors in the full backup set; we're good to go for this backup and any
                            // differentials that are tied to it.
                            isBadFullBackup = false;
                        }
                    }
                    else
                    {
                        // We're working with a differential backup. If it has an error, or if the full backup that
                        // it is tied to have an issue, we need to flag it for deletion.
                        if (isBadFullBackup || Convert.ToInt32(currentBackup.Element("SPErrorCount").Value) > 0)
                        {
                            // Add the current differential to the exclusion list.
                            additionalDirectoryExclusions.Add(Convert.ToInt32(currentBackup.Element("SPDirectoryNumber").Value));
                        }
                    }
                }
            }

            // If we've actually got one or more backup sets to export, we can continue with file operations.
            if (exportSelections.Count() > 0)
            {
                // We've got a selection of backup sets for export. We need to transform them into a list
                // that we can use for the actual export operations.
                //
                // We'll be cross-checking to make sure that the directory number of each backup isn't present in
                // the exclude list before selecting it for backup.
                String backupRoot         = workingCatalog.CatalogPath;
                var    backupSetsToExport = exportSelections.Where(bse =>
                                                                   additionalDirectoryExclusions.IndexOf(Convert.ToInt32(bse.Element("SPDirectoryNumber").Value)) == -1).Select(se =>
                                                                                                                                                                                new { RelativeFolder = se.Element("SPDirectoryName").Value, SourceFolder = Path.Combine(backupRoot, se.Element("SPDirectoryName").Value) }).ToList();

                // Build-out the new table-of-contents file for the export we're about to perform (and again,
                // observe exclusions that have been added to the appropriate list).
                XElement newTocFile = XElement.Parse(Globals.BACKUP_CATALOG_BASE_XML);
                foreach (XElement currentHistoryObject in exportSelections.Where(bse =>
                                                                                 additionalDirectoryExclusions.IndexOf(Convert.ToInt32(bse.Element("SPDirectoryNumber").Value)) == -1))
                {
                    newTocFile.Add(currentHistoryObject);
                }

                // If we need to do path corrections, now is the time.
                if (updateCatalogPath)
                {
                    // We're going to update the catalog path, so we need to determine what the new path will be.
                    // The actual path depends on whether or not the ExportName is part of the path.
                    String newCatalogPath;
                    if (useNoCompression)
                    {
                        newCatalogPath = Path.Combine(exportPath, exportName);
                    }
                    else
                    {
                        newCatalogPath = exportPath;
                    }

                    // Execute the update.
                    newTocFile = UpdateBackupCatalogPaths(newTocFile, newCatalogPath);
                }

                // How we proceed at this point depends on whether or not compression is enabled.
                if (useNoCompression)
                {
                    // We're not using compression, so we need to create a folder structure at the export path.
                    // Build the full path and use it for the creation.
                    var exportFullPath = Path.Combine(exportPath, exportName);
                    Directory.CreateDirectory(exportFullPath);

                    // Write out the table of contents to export area.
                    var tocFilePath = Path.Combine(exportFullPath, Globals.TOC_FILENAME);
                    newTocFile.Save(tocFilePath);

                    // Iterate through each of the backup sets to export and copy them to the export area.
                    foreach (var exportBackupSet in backupSetsToExport)
                    {
                        // Create the destination folder.
                        var destinationFolder = Path.Combine(exportFullPath, exportBackupSet.RelativeFolder);
                        Directory.CreateDirectory(destinationFolder);

                        // We now need to recursively copy the contents of the source folder (and its subdirectories
                        // if any exist) into the destination folder.
                        RecursiveCopy(exportBackupSet.SourceFolder, destinationFolder);
                    }
                }
                else
                {
                    // We're using compression, so the export name supplied will be used as the name of the
                    // zip file we create. Make sure the export name actually ends in .zip
                    if (!exportName.EndsWith(Globals.EXPORT_ARCHIVE_EXTENSION, StringComparison.InvariantCultureIgnoreCase))
                    {
                        exportName += Globals.EXPORT_ARCHIVE_EXTENSION;
                    }

                    // Build the full path to the zip file we want to create; if it already exists, delete it so
                    // we can write out the new one.
                    var archiveFullPath = Path.Combine(exportPath, exportName);
                    if (File.Exists(archiveFullPath))
                    {
                        File.Delete(archiveFullPath);
                    }

                    // Prep the zip archive that we'll be writing to
                    using (var catalogArchive = new ZipFile(archiveFullPath))
                    {
                        // Begin by writing out the table of contents to the root of the archive.
                        catalogArchive.AddEntry(Globals.TOC_FILENAME, newTocFile.ToString());

                        // Iterate through each of the backup sets to process them into the archive.
                        foreach (var exportBackupSet in backupSetsToExport)
                        {
                            catalogArchive.AddDirectory(exportBackupSet.SourceFolder, exportBackupSet.RelativeFolder);
                        }

                        // We need to save the archive, but we should make a couple of changes to ensure proper
                        // .zip processing. The archive that we're creating is likely very large relative to the
                        // average .zip file. For this reason, we'll adjust the save options and buffers based
                        // on a sample from the Ionic Zip Library "BufferSize" property description.
                        catalogArchive.UseZip64WhenSaving = Zip64Option.Always;
                        catalogArchive.BufferSize         = 65536 * 8; // 512k buffer (8k buffer is default)
                        catalogArchive.Save();
                    }
                }
            }
        }