Tuesday, December 16, 2008

Faster DFS recovery application

In trying to set up DFS replication, we had a number of files that were not present in both the primary DFS partner and the destination partner. In this case, DFS will move all of the files "missing" from the primary partner out of the tree and into a separate pre-existing path on each destination volume. Microsoft will provide you with a recovery script that calls xcopy to, based on the generated PreExistingManifest.xml file, move the files back into their original locations.

The problem we had was that shelling out to xcopy when you have millions of relatively small files was going to take, well, months to complete. I built the following .NET (3.5, C#) console application which proved to do this at hundreds of times the rate of the Microsoft script. The only issue is that it does not replicate permissions; since we did not need that for our recovery, it fit the bill.

Please use at your own risk. I make no warranties. I recommend specifying an alternate recovery path when calling the application so you can validate output first.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace DFSRecovery
{
/// <summary>
/// Handles copying DFS files back into the original folder structure.
/// </summary>
class Program
{
/// <summary>
/// The main application loop.
/// </summary>
/// <param name="args">The args. See usage text.</param>
static void Main(string[] args)
{
if (args.Length < 3)
{
Console.WriteLine("\nDFSRecovery version " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString() + " [Arthur Penn, http://devarthur.blogspot.com]");
Console.WriteLine("Usage: DFSRecovery.exe \"\\\\path\\to\\PreExistingManifest.xml\" \"\\\\path\\to\\pre-existing\\folder\" \"\\\\path\\to\\output\\folder\" [print only=true|false]");
Environment.Exit(1);
}

// Load the PreExistingManifest.xml document and select the values we need
var doc = XDocument.Load(args[0]);
string preExistingFolder = args[1];
string outputFolder = args[2];
bool printOnly = false;
if (args.Length > 3)
{
printOnly = bool.Parse(args[3]);
}

int rc = 0;

var actions = from n in doc.Descendants("Resource")
select new {
FileOrFolder = ((string)n.Descendants("Attributes").First()),
Source = Path.Combine(preExistingFolder, (string)n.Descendants("NewName").First()),
Destination = Path.Combine(outputFolder, ((string)n.Descendants("Path").First()).Substring(7,
((string)n.Descendants("Path").First()).Length - 7))
};

foreach (var item in actions)
{
try
{
if (File.Exists(item.Source))
{
if (File.Exists(item.Destination))
{
if (printOnly)
{
Console.WriteLine("Target file exists: \"" + item.Destination + "\"");
}
}
else
{
CopyFile(item.Source, item.Destination, printOnly);
}
}
else
{
// It's a directory
CopyDirectory(item.Source, item.Destination, printOnly);
//break;
}
}
catch (Exception x)
{
rc = 1;
Console.WriteLine("Exception copying \"" + item.Source + "\" to \"" + item.Destination +
"\": " + x.ToString());
}
}

Environment.Exit(rc);
}

/// <summary>
/// Ensures the directory is present.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="isDirectory">if set to <c>true</c> [is directory].</param>
/// <param name="printOnly">if set to <c>true</c> [print only].</param>
static void EnsureDirectory(string path, bool isDirectory, bool printOnly)
{
string targetFolder = (isDirectory ? path : path.Substring(0, path.LastIndexOf("\\")));
if (Directory.Exists(targetFolder))
{
if (printOnly)
{
Console.WriteLine("Target folder exists: \"" + targetFolder + "\"");
}
}
else
{
if (printOnly)
{
Console.WriteLine("Creating target folder: \"" + targetFolder + "\"");
}
else
{
Directory.CreateDirectory(targetFolder);
}
}
}

/// <summary>
/// Copies the directory.
/// </summary>
/// <param name="sourcePath">The source path.</param>
/// <param name="destinationPath">The destination path.</param>
/// <param name="printOnly">if set to <c>true</c> [print only].</param>
static void CopyDirectory(string sourcePath, string destinationPath, bool printOnly)
{
EnsureDirectory(destinationPath, true, printOnly);
foreach (string file in Directory.GetFiles(sourcePath))
{
//#if DEBUG
// Console.Write("From CopyDirectory: ");
//#endif
string fileName = file.Substring(file.LastIndexOf("\\") + 1);
CopyFile(file, Path.Combine(destinationPath, fileName), printOnly);
}

// Recursively process directories
foreach (string directory in Directory.GetDirectories(sourcePath))
{
string sourceSubDirectory = directory.Substring(directory.LastIndexOf("\\") + 1);
string destinationSubDirectory = Path.Combine(destinationPath, sourceSubDirectory);

CopyDirectory(directory, destinationSubDirectory, printOnly);
}
}

/// <summary>
/// Copies the file.
/// </summary>
/// <param name="sourcePath">The source path.</param>
/// <param name="destinationPath">The destination path.</param>
/// <param name="printOnly">if set to <c>true</c> [print only].</param>
static void CopyFile(string sourcePath, string destinationPath, bool printOnly)
{
if (printOnly)
{
Console.WriteLine("Copying \"" + sourcePath + "\" to \"" + destinationPath + "\"");
}
else
{
EnsureDirectory(destinationPath, false, printOnly);
File.Copy(sourcePath, destinationPath);
}
}
}
}

No comments:

Post a Comment