Click here to Skip to main content
Email Password   helpLost your password?

Introduction

I was near the end of a drawn out development project for adding users to an Active Directory server when I was informed I would also need to set up their Thunderbird client settings. This required me to create a copy of a directory, sub-directories, and files over at the user's "My Documents" folder.

I tried using Microsoft's MSDN Online to search for a way to copy directories with their subfolders and files, when I determined that the Directory.Copy() method did not exist natively. I did not find anything remotely close.

I then searched the internet for other user's code examples, hoping someone had found a solution to this seemingly simple problem. And I did come across a few; still, the code I did find was inelegant and very much a "quick fix".

So I ended up writing my own.

NEW! Version 3.0 Description

Written October 18th, 2006

Well, after much more feedback and a lot of research, I give you Version 3 of the xDirectory Copier. This version is built on an almost completely different method: It's event-driven.

Yes, that's right, the xDirectory object now copies files and subfolders on a separate thread, and responds with events. This version will work both with Console as well as GUI (such as Windows Forms-Based) programs. It is UI-Thread Friendly, so it will allow you to update your controls on the events. It can give you a progress report, and you can cancel the copy thread at any time. Also, you can set multiple file filters to copy multiple file types (see below).

You'll definately want to download the provided code and look through it yourself. There's enough changes in the code that posting it here is probably just wasting your time. I will however post some highlights.

So, to use this new class, you would do this:

xDirectory Copier = new xDirectory();
Copier.ItemIndexed += new ItemIndexedEventHandler(Copier_ItemIndexed);
Copier.IndexComplete += new IndexCompleteEventHandler(Copier_IndexComplete);
Copier.ItemCopied += new ItemCopiedEventHandler(Copier_ItemCopied);
Copier.CopyComplete += new CopyCompleteEventHandler(Copier_CopyComplete);
Copier.CopyError += new CopyErrorEventHandler(Copier_CopyError);

Then, there are two ways to use the code once initialized. You can do as below:

Copier.Source = new DirectoryInfo(sSource);
Copier.Destination = new DirectoryInfo(sDestination);
Copier.Overwrite = true;
Copier.FolderFilter = "*";
Copier.FileFilters.Add("*.jpg");
Copier.FileFilters.Add("*.bmp");
Copier.FileFilters.Add("*.png");
Copier.StartCopy();

Or, you can simply call the overloaded StartCopy Method:

List<string> FileFilters = new List<string>();
FileFilters.Add("*.jpg");
Copier.StartCopy(sSource, sDestination, FileFilters, null, true);

If you place the Initialization part in a Form_Load method, and then put Usage part in a Button_Click event, it will run asynchronously and will allow you to update your controls (such as a progress bar) through the event methods.

One of the best parts of this new class is that if there's an error in copying a file, instead of cancelling the entire process, it will simply send a 'CopyError' Event! So you can list the files that had errors in copying but continue to copy the rest of the files successfully!

And here's the method that does all the work. It's a bit more complex-looking than version 2:

/// <summary>
/// The Main Work Method of the xDirectory Class: Handled in a Separate Thread.
/// </summary>
/// <param name="StateInfo">Undefined</param>
private void DoWork(object StateInfo)
{
    _CopierStatus = xDirectoryStatus.Started;

    int iterator = 0;
    List<DirectoryInfo> FolderSourceList = new List<DirectoryInfo>();
    List<FileInfo> FileSourceList = new List<FileInfo>();
    DirectoryInfo FolderPath;
    FileInfo FilePath;

    try
    {
        // Part 1: Indexing
        ///////////////////////////////////////////////////////
        
        _CopierStatus = xDirectoryStatus.Indexing;
        
        FolderSourceList.Add(_Source);

        while (iterator < FolderSourceList.Count)
        {
            if (_CancelCopy) return;

            foreach (DirectoryInfo di in 
                     FolderSourceList[iterator].GetDirectories(_FolderFilter))
            {
                if (_CancelCopy) return;

                FolderSourceList.Add(di);

                OnItemIndexed(new ItemIndexedEventArgs(
                    di.FullName,
                    0,
                    FolderSourceList.Count,
                    true));
            }

            foreach (string FileFilter in _FileFilters)
            {
                foreach (FileInfo fi in 
                         FolderSourceList[iterator].GetFiles(FileFilter))
                {
                    if (_CancelCopy) return;

                    FileSourceList.Add(fi);

                    OnItemIndexed(new ItemIndexedEventArgs(
                        fi.FullName,
                        fi.Length,
                        FileSourceList.Count,
                        false));
                }
            }

            iterator++;
        }

        OnIndexComplete(new IndexCompleteEventArgs(
            FolderSourceList.Count, 
            FileSourceList.Count));



        // Part 2: Destination Folder Creation
        ///////////////////////////////////////////////////////

        _CopierStatus = xDirectoryStatus.CopyingFolders;

        for (iterator = 0; iterator < FolderSourceList.Count; iterator++)
        {
            if (_CancelCopy) return;

            
            if (FolderSourceList[iterator].Exists)
            {
                FolderPath = new DirectoryInfo(
                    _Destination.FullName +
                    Path.DirectorySeparatorChar +
                    FolderSourceList[iterator].FullName.Remove(0, 
                                                 _Source.FullName.Length));

                try
                {
                    // Prevent IOException
                    if (!FolderPath.Exists) FolderPath.Create(); 

                    OnItemCopied(new ItemCopiedEventArgs(
                            FolderSourceList[iterator].FullName,
                            FolderPath.FullName,
                            0,
                            iterator,
                            FolderSourceList.Count,
                            true));
                }
                catch (Exception iError)
                {
                    OnCopyError(new CopyErrorEventArgs(
                            FolderSourceList[iterator].FullName,
                            FolderPath.FullName,
                            iError));
                }
            }
            
        }



        // Part 3: Source to Destination File Copy
        ///////////////////////////////////////////////////////

        _CopierStatus = xDirectoryStatus.CopyingFiles;

        for (iterator = 0; iterator < FileSourceList.Count; iterator++)
        {
                if (_CancelCopy) return;

                if (FileSourceList[iterator].Exists)
                {
                    FilePath = new FileInfo(
                        _Destination.FullName +
                        Path.DirectorySeparatorChar +
                        FileSourceList[iterator].FullName.Remove(0, 
                                             _Source.FullName.Length + 1));

                    try
                    {
                        if (_Overwrite)
                            FileSourceList[iterator].CopyTo(FilePath.FullName,
                                                            true); 
                        else
                        {
                            if (!FilePath.Exists)
                                FileSourceList[iterator].CopyTo(
                                                    FilePath.FullName, true);
                        }

                        OnItemCopied(new ItemCopiedEventArgs(
                                FileSourceList[iterator].FullName,
                                FilePath.FullName,
                                FileSourceList[iterator].Length,
                                iterator,
                                FileSourceList.Count,
                                false));

                    }
                    catch (Exception iError)
                    {
                        OnCopyError(new CopyErrorEventArgs(
                                FileSourceList[iterator].FullName,
                                FilePath.FullName,
                                iError));
                    }
                }
            
        }

    }
    catch
    { throw; }
    finally
    {
        _CopierStatus = xDirectoryStatus.Stopped;
        OnCopyComplete(new CopyCompleteEventArgs(_CancelCopy));
    }
}

Old Version 2.0 Description

Written May 18th, 2006

Updated July 25th, 2006 - Fixed a major (but minute) error, added a ... + @"\" + ... to the string sFolderPath = ... and string sFilePath = ... declarations. Should fix any "files with foldername copied" errors.

After so much feedback, I decided I would revisit the Copy code. It's been quite a while since I posted the original, and I've learned a great deal more in the intervening time with regards to programming, and so I hope to bring this method to a new level.

The original version was written in .NET 1.1, and since then, I've gotten used to .NET 2.0, so this is what the second version is written in. Everyone cheer for Generic Collections!

Also, if you notice in the source code, the xDirectory.Copy method is now iterative instead of recursive. The iterative version of this code performs better than the recursive (for obvious reasons), and actually uses less overall memory.

Some key points were brought up, both in my work and in the comments; hopefully, all these have been rectified to everyone's satisfaction!

Here's the xDirectory.Copy() method in all its glory. (There are overloads in the source project.)

/// <summary>

/// xDirectory.Copy() - Copy a Source Directory
/// and it's SubDirectories/Files
/// </summary>
/// <param name="diSource">The Source Directory</param>
/// <param name="diDestination">The Destination Directory</param>
/// <param name="FileFilter">The File Filter
/// (Standard Windows Filter Parameter, Wildcards: "*" and "?")</param>

/// <param name="DirectoryFilter">The Directory Filter
/// (Standard Windows Filter Parameter, Wildcards: "*" and "?")</param>
/// <param name="Overwrite">Whether or not to Overwrite
/// a Destination File if it Exists.</param>
/// <param name="FolderLimit">Iteration Limit - Total Number
/// of Folders/SubFolders to Copy</param>
public static void Copy(DirectoryInfo diSource, 
       DirectoryInfo diDestination, 
       string FileFilter, string DirectoryFilter, 
       bool Overwrite, int FolderLimit)
{
    int iterator = 0;
    List<DirectoryInfo> diSourceList = 
            new List<DirectoryInfo>();
    List<FileInfo> fiSourceList = 
            new List<FileInfo>();

    try
    {
        ///// Error Checking /////
        if (diSource == null) 
            throw new ArgumentException("Source Directory: NULL");
        if (diDestination == null) 
            throw new ArgumentException("Destination Directory: NULL");
        if (!diSource.Exists) 
            throw new IOException("Source Directory: Does Not Exist");
        if (!(FolderLimit > 0)) 
            throw new ArgumentException("Folder Limit: Less Than 1");
        if (DirectoryFilter == null || DirectoryFilter == string.Empty)
            DirectoryFilter = "*";
        if (FileFilter == null || FileFilter == string.Empty)
            FileFilter = "*";

        ///// Add Source Directory to List /////
        diSourceList.Add(diSource);

        ///// First Section: Get Folder/File Listing /////
        while (iterator < diSourceList.Count && iterator < FolderLimit)
        {
            foreach (DirectoryInfo di in 
              diSourceList[iterator].GetDirectories(DirectoryFilter))
                diSourceList.Add(di);

            foreach (FileInfo fi in 
                                diSourceList[iterator].GetFiles(FileFilter))
                fiSourceList.Add(fi);

            iterator++;
        }

        ///// Second Section: Create Folders from Listing /////
        foreach (DirectoryInfo di in diSourceList)
        {
            if (di.Exists)
            {
                string sFolderPath = diDestination.FullName + @"\" +
                       di.FullName.Remove(0, diSource.FullName.Length);
                
                ///// Prevent Silly IOException /////
                if (!Directory.Exists(sFolderPath))
                    Directory.CreateDirectory(sFolderPath);
            }
        }

        ///// Third Section: Copy Files from Listing /////
        foreach (FileInfo fi in fiSourceList)
        {
            if (fi.Exists)
            {
                string sFilePath = diDestination.FullName + @"\" +
                       fi.FullName.Remove(0, diSource.FullName.Length);
                
                //// Better Overwrite Test W/O IOException from CopyTo() ////
                if (Overwrite)
                    fi.CopyTo(sFilePath, true);
                else
                {
                    ///// Prevent Silly IOException /////
                    if (!File.Exists(sFilePath))
                        fi.CopyTo(sFilePath, true);
                }
            }
        }
    }
    catch
    { throw; }
}

Hopefully, you'll be able to put the new code to work easier than before! I've left both versions on here just in case .NET 2.0 is out of your reach; though, if pressed, you can use this code in .NET 1.1 with just a little tweaking. Essentially, change the generic List<DirectoryInfo> to an ArrayList, and do some type-casting to get it to work.

Enjoy!

Version History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
QuestionFolder filter.....errrrmmm?
riscy
21:40 12 Feb '10  
It may be silly query

(a) I'm copying the folder xx to yy, to backup the content folder and files.
(b) Inside the folder xx is a subfolder "History" which I don't want to copy, (it take too long)
(c) I set filter xCopier.FolderFilter = "History" within the c# code, I preassumed it prevents backing up the folder called "History"
(d) Since this folder xx has no sub-folder "History", it pop up an IO Directionary exception window. errmmm.....what have I done wrong?
(e) How to use xCopier.FolderFilter which allow copy all content with xx, except the folder History
(f) and how to prevent exception error if the folder is not there.

Thank, it is great solution.
>It's not so much what you have to learn if you accept weird theories, it's what you have to unlearn. (Isaac Asimov)
>Life's journey is not to arrive at the grave safely in a well preserved body,but rather to skid in sideways, totally worn out, shouting "...holy sh*t...what a ride! [Riscy]

GeneralError Message: System.IO.PathTooLongException (Path and/or Filename is too long)
pebo66450
21:39 7 Dec '09  
Hello John,

at first: thank you for your helpfull Code Thumbs Up And sorry for my bad english Wink
When i try copy Folders, i get sometimes the following error message (it is in german. If you dont understand the text, i will change my system and send you the english message):

===================================================================================
System.IO.PathTooLongException wurde nicht behandelt.
Message="Der angegebene Pfad und/oder Dateiname ist zu lang. Der vollständig qualifizierte Dateiname muss kürzer als 260 Zeichen und der Pfadname kürzer als 248 Zeichen sein."
Source="mscorlib"
StackTrace:
bei System.IO.Path.SafeSetStackPointerValue(Char* buffer, Int32 index, Char value)
bei System.IO.Path.NormalizePathFast(String path, Boolean fullCheck)
bei System.IO.Path.NormalizePath(String path, Boolean fullCheck)
bei System.IO.Path.GetFullPathInternal(String path)
bei System.Security.Util.StringExpressionSet.CanonicalizePath(String path, Boolean needFullPath)
bei System.Security.Util.StringExpressionSet.CreateListFromExpressions(String[] str, Boolean needFullPath)
bei System.Security.Permissions.FileIOPermission.AddPathList(FileIOPermissionAccess access, AccessControlActions control, String[] pathListOrig, Boolean checkForDuplicates, Boolean needFullPath, Boolean copyPathList)
bei System.Security.Permissions.FileIOPermission..ctor(FileIOPermissionAccess access, String path)
bei System.IO.FileSystemInfo.get_FullName()
bei System.IO.xDirectory.DoWork(Object StateInfo)
bei System.Threading._ThreadPoolWaitCallback.WaitCallback_Context(Object state)
bei System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
bei System.Threading._ThreadPoolWaitCallback.PerformWaitCallbackInternal(_ThreadPoolWaitCallback tpWaitCallBack)
bei System.Threading._ThreadPoolWaitCallback.PerformWaitCallback(Object state)
InnerException:
===================================================================================

Can you (or someone others) tell me how i can change your sample code (form1.cs) or must be change your xDirectory.dll ?

Friendly greetings from germany
Peter
GeneralRe: Error Message: System.IO.PathTooLongException (Path and/or Filename is too long)
John Storer II
2:26 8 Dec '09  
Hi Peter:

You have a file or directory path that is longer than the OS specified limits (260 for full path (C:\this\path\is\too\long) and 248 for filename length (C:\this\filenameistol.ong)). Try running it on a directory that has shorter path names and see if it works.

I do not know if there is a way around the issue in code, since it is an OS imposed limit.

Earth.USA.Indiana.People["Storer"]["John"][2].SendGlobalMessage("Hi!");

GeneralRe: Error Message: System.IO.PathTooLongException (Path and/or Filename is too long)
pebo66450
3:11 8 Dec '09  
Hi John,

thank's for your fast answer Thumbs Up
With directorys that have shorter Pathnames, your code works fantastic.

Thanks again
Peter
Generalsome bug in version 3
shenjian250
8:56 8 Nov '09  
in TestxDirectory.sln:
when Source Directory is a removeable disk,it will be lots of errors.
in error listbox show me,the destination directory will miss the first letter.
source:I:(I:\abcdefg,I:\abcdedf\1.txt)
Destination:c:\abc
error:can not find the path "c:\abc\bcdefg\1.txt.........b~lalala
....................................here missing "a"!
please help me.pardon me bad English.
QuestionQuestion about me converting your utility to VB.Net
Jim Tyler
7:45 14 Oct '09  
Hello John. I hope you don't mind if I want to convert your utility to VB.Net for my own usage. The only reason is because we're a VB.net shop and I wanted to add the ability to optionally follow-up with a delete after a successful copy.

The only problem I was having is how to convert the event code. Below is the IndexCompleteEventHandler as an example. Your C# code is fine. But when I try to convert to VB.Net I receive a syntax error on the 'Handler' declaration indicating that I should use the RaiseEvent statement and that it cannot be called directly.

Do you know what I need to do?

C#:

/// <summary>
/// The 'Index Complete' Event
/// </summary>
public event IndexCompleteEventHandler IndexComplete;

/// <summary>
/// The 'On Index Complete' Event Handler: Called when Indexing of the Source Folder is Complete.
/// </summary>
/// <param name="e">The 'Index Complete' Event Arguments.</param>
protected virtual void OnIndexComplete(IndexCompleteEventArgs e)
{
IndexCompleteEventHandler Handler = IndexComplete;
if (Handler != null)
{
foreach (IndexCompleteEventHandler Caster in Handler.GetInvocationList())
{
ISynchronizeInvoke SyncInvoke = Caster.Target as ISynchronizeInvoke;
try
{
if (SyncInvoke != null && SyncInvoke.InvokeRequired)
SyncInvoke.Invoke(Handler, new object[] { this, e });
else
Caster(this, e);
}
catch
{ }
}
}
}


VB.NET:

''' <summary>
''' The 'Index Complete' Event
''' </summary>
Public Event IndexComplete As IndexCompleteEventHandler

''' <summary>
''' The 'On Index Complete' Event Handler: Called when Indexing of the Source Folder is Complete.
''' </summary>
''' <param name="e">The 'Index Complete' Event Arguments.</param>
Protected Overridable Sub OnIndexComplete(ByVal e As IndexCompleteEventArgs)
Dim Handler As IndexCompleteEventHandler = IndexComplete
If Handler IsNot Nothing Then
For Each Caster As IndexCompleteEventHandler In Handler.GetInvocationList()
Dim SyncInvoke As ISynchronizeInvoke = TryCast(Caster.Target, ISynchronizeInvoke)
Try
If SyncInvoke IsNot Nothing AndAlso SyncInvoke.InvokeRequired Then
SyncInvoke.Invoke(Handler, New Object() {Me, e})
Else
Caster(Me, e)
End If
Catch
End Try
Next
End If
End Sub
AnswerRe: Question about me converting your utility to VB.Net
Jim Tyler
8:08 14 Oct '09  
Whoops! I didn't see the other request for the same thing further down the list.

Oh well.
GeneralRe: Question about me converting your utility to VB.Net
John Storer II
8:14 14 Oct '09  
No worries.

I don't dabble in VB (I personally thing the language is counter-intuitive, but that's my own personal opinion) so I probably wouldn't have been able to solve your question without googling the answer myself, which anyone could have done. Sorry!

Earth.USA.Indiana.People["Storer"]["John"][2].SendGlobalMessage("Hi!");

GeneralRe: Question about me converting your utility to VB.Net
Jim Tyler
8:22 14 Oct '09  
I will try to google this. It's funny how each person who favors a syntax finds the other one counter-intuitive.

I was actually an assembler programmer on an IBM mainframe for 10 years and find vb easier to understand than C#. I guess I just like the readability of VB and the lack of all the special characters. Other than that I can read it, just don't favor it.

I do love your utility though and thanks for the quick response!
GeneralEnhancements I made
jverbarg
6:54 13 Oct '08  
Fixed the problem with having multiple xDirectory objects.

Split the indexing function out into a separate method so I could simply index a directory to get size and # of files for informational purposes.

Pass back the last modified date in the OnItemIndexed Event.

Added the ability to turn off recursive searching. Seems stupid I know, as recursiveness is the main purpose of this utility, but this will thread the reading of a large directory.

Added a switch to not index or copy files, just the directories.

Let me know if you want my changes for an update version.

Josh
GeneralRe: Enhancements I made
Jan R Hansen
2:06 23 Sep '09  
Hi,

What's the problem with having multiple xDirectory objects - and the fix?. Could you post your changes here somehow?

/Jan

Do you know why it's important to make fast decisions? Because you give yourself more time to correct your mistakes, when you find out that you made the wrong one. Chris Meech on deciding whether to go to his daughters graduation or a Neil Young concert

GeneralRe: Enhancements I made
jverbarg
3:26 23 Sep '09  
Don't remember all the exact details, but had to remove many if not all of the static keywords. "Static" uses the same memory location for all instances of an object, and that doesn't work for multiple instances.

John Storer is not responding on here, so if you want my code send me your e-mail address and I'll get it to you.
GeneralRe: Enhancements I made
John Storer II
5:28 23 Sep '09  
How about posting it on here. That would be easier and give everyone access to it.

Earth.USA.Indiana.People["Storer"]["John"][2].SendGlobalMessage("Hi!");

Generalgreat utility
dmitriy_burdan
10:54 21 Jul '08  
John,

Thanks for a great utility and help project! Well written & nicely done! I found it very easy to follow and a great help.

-dmitriy
Questionexiting appliction while copying large file will cause uncomplete copy.
depaniy
14:34 1 Jul '08  
It's a great job indeed,thanks a lot!
But I found a bug: if exiting application forcely when it hasn't finished copying a large file, you'll find the destination file exists and its' file size is equal to the source file. Then,if you run copy again and set the overwrite to false, your program will skip the file which doesn't copied correctly.
Could you give some explaination on it? solution is prefered, thanks.

be better!

JokeRe: exiting appliction while copying large file will cause uncomplete copy.
P3 Tech
8:15 27 Jan '09  
Here's a thought: Don't exit an application that's doing work for you!
QuestionGood program but can be done in DOS easily...
Member 3737803
23:29 11 May '08  
Hi,
Nice code. Any special requirement for which this code will work. I tried command below:
xCopy c:\A\*.* c:\B
This will copy all files in sub folders of A to B

Thanks

D'Oh! Sigh Smile Confused
AnswerRe: Good program but can be done in DOS easily...
John Storer II
5:03 12 May '08  
Don't know about you, but I'd like to choose what I do with every single file I copy, and every single folder I create, not to mention choosing what to do if something (like a corrupt file or a network outage) fails, especially to my clients. (nice warning/error dialogs verses looking at a C-Prompt.)

Earth.USA.Indiana.People["Storer"]["John"][2].SendGlobalMessage("Hi!");

AnswerRe: Good program but can be done in DOS easily...
Dionito
12:17 18 Jun '08  
Member 3737803 wrote:
xCopy c:\A\*.* c:\B
This will copy all files in sub folders of A to B

No, it won't copy subfolders. You're missing the "/s" Poke tongue . Not comparable, though.

Dioni

General:-S
Chris Nevill
2:36 7 May '08  
I'm having problems with files not appearing when performing the copy
to a memory stick...
GeneralRe: :-S
John Storer II
5:06 12 May '08  
Okay... Make sure the memory stick is plugged in?

Earth.USA.Indiana.People["Storer"]["John"][2].SendGlobalMessage("Hi!");

GeneralRe: :-S
Chris Nevill
5:10 12 May '08  
Not far off, it turned out the memory stick was full, I guess I needed
to check somehow that the copy succeeded as no exception is received as it's done in the thread pool.
My mistake,

Cheers,
Chris.
AnswerRe: :-S
Chris Nevill
5:11 12 May '08  
Hence why you put in the copy error Event... D'Oh!
General_CopierStatus not set when StartCopy is called
Chris Nevill
7:08 30 Apr '08  
This is a great project much appreciated thanks Smile

I was thinking shouldn't _CopierStatus be set in StartCopy or at least
set to Starting?

At the moment it's done in the thread pool, and as a result it can look like the copier
is finished, despite the fact it just hasn't had a chance to start yet?
GeneralRe: _CopierStatus not set when StartCopy is called
John Storer II
5:07 12 May '08  
When I put this project up here, I knew it wasn't completely perfect. You've absolutely more than welcome to modify the code as much as possible to suit your own needs. In fact, I welcome the input, and if you have a unique way of using this code, please feel free to share what you can with us!

Earth.USA.Indiana.People["Storer"]["John"][2].SendGlobalMessage("Hi!");


Last Updated 20 Oct 2006 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010