tags:

views:

7619

answers:

13

I want to copy the entire contents of a directory from one location to another in C#.

There doesn't appear to be a way to do this using System.IO classes without lots of recursion.

There is a method in VB that we can use if we add a reference to Microsoft.VisualBasic:

new Microsoft.VisualBasic.Devices.Computer().
    FileSystem.CopyDirectory( sourceFolder, outputFolder );

This seems like a rather ugly hack. Is there a better way?

+2  A: 

Or, if you want to go the hard way, add a reference to your project for Microsoft.VisualBasic and then use the following:

Microsoft.VisualBasic.FileIO.FileSystem.CopyDirectory(fromDirectory, toDirectory);

However, using one of the recursive functions is a better way to go since it won't have to load the VB dll.

Josef
That isn't really different from how I did it anyway - you still need to load VB's backward-compatibility stuff in order to be able to do it.
Keith
Is loading the VB assembly expensive? The VB options are much more elegant than the C# versions.
jwmiller5
What "VB's backward-compatibility stuff"? CopyDirectory uses either the Shell or the Framework.
AMissico
+1  A: 

Sorry for the previous code, it still had bugs :( (fell prey to the fastest gun problem) . Here it is tested and working. The key is the SearchOption.AllDirectories, which eliminates the need for explicit recursion.

string path = "C:\\a";
string[] dirs = Directory.GetDirectories(path, "*.*", SearchOption.AllDirectories);
string newpath = "C:\\x";
try
{
    Directory.CreateDirectory(newpath);
}
catch (IOException ex)
{
    Console.WriteLine(ex.Message);
}
for (int j = 0; j < dirs.Length; j++)
{
    try
    {
        Directory.CreateDirectory(dirs[j].Replace(path, newpath));
    }
    catch (IOException ex)
    {
        Console.WriteLine(ex.Message);
    }
}

string[] files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);
for (int j = 0; j < files.Length; j++)            
{
    try
    {
        File.Copy(files[j], files[j].Replace(path, newpath));
    }
    catch (IOException ex)
    {
        Console.WriteLine(ex.Message);
    }
}
Vinko Vrsalovic
+1  A: 

Here's a utility class I've used for IO tasks like this.

using System;
using System.Runtime.InteropServices;

namespace MyNameSpace
{
    public class ShellFileOperation
    {
     private static String StringArrayToMultiString(String[] stringArray)
     {
      String multiString = "";

      if (stringArray == null)
       return "";

      for (int i=0 ; i<stringArray.Length ; i++)
       multiString += stringArray[i] + '\0';

      multiString += '\0';

      return multiString;
     }

     public static bool Copy(string source, string dest)
     {
      return Copy(new String[] { source }, new String[] { dest });
     }

     public static bool Copy(String[] source, String[] dest)
     {
      Win32.SHFILEOPSTRUCT FileOpStruct = new Win32.SHFILEOPSTRUCT();

      FileOpStruct.hwnd = IntPtr.Zero;
      FileOpStruct.wFunc = (uint)Win32.FO_COPY;

      String multiSource = StringArrayToMultiString(source);
      String multiDest = StringArrayToMultiString(dest);
      FileOpStruct.pFrom = Marshal.StringToHGlobalUni(multiSource);
      FileOpStruct.pTo = Marshal.StringToHGlobalUni(multiDest);

      FileOpStruct.fFlags = (ushort)Win32.ShellFileOperationFlags.FOF_NOCONFIRMATION;
      FileOpStruct.lpszProgressTitle = "";
      FileOpStruct.fAnyOperationsAborted = 0;
      FileOpStruct.hNameMappings = IntPtr.Zero;

      int retval = Win32.SHFileOperation(ref FileOpStruct);

      if(retval != 0) return false;
      return true;
     }

     public static bool Move(string source, string dest)
     {
      return Move(new String[] { source }, new String[] { dest });
     }

     public static bool Delete(string file)
     {
      Win32.SHFILEOPSTRUCT FileOpStruct = new Win32.SHFILEOPSTRUCT();

      FileOpStruct.hwnd = IntPtr.Zero;
      FileOpStruct.wFunc = (uint)Win32.FO_DELETE;

      String multiSource = StringArrayToMultiString(new string[] { file });
      FileOpStruct.pFrom = Marshal.StringToHGlobalUni(multiSource);
      FileOpStruct.pTo =  IntPtr.Zero;

            FileOpStruct.fFlags = (ushort)Win32.ShellFileOperationFlags.FOF_SILENT | (ushort)Win32.ShellFileOperationFlags.FOF_NOCONFIRMATION | (ushort)Win32.ShellFileOperationFlags.FOF_NOERRORUI | (ushort)Win32.ShellFileOperationFlags.FOF_NOCONFIRMMKDIR;
      FileOpStruct.lpszProgressTitle = "";
      FileOpStruct.fAnyOperationsAborted = 0;
      FileOpStruct.hNameMappings = IntPtr.Zero;

      int retval = Win32.SHFileOperation(ref FileOpStruct);

      if(retval != 0) return false;
      return true;
     }

     public static bool Move(String[] source, String[] dest)
     {
      Win32.SHFILEOPSTRUCT FileOpStruct = new Win32.SHFILEOPSTRUCT();

      FileOpStruct.hwnd = IntPtr.Zero;
      FileOpStruct.wFunc = (uint)Win32.FO_MOVE;

      String multiSource = StringArrayToMultiString(source);
      String multiDest = StringArrayToMultiString(dest);
      FileOpStruct.pFrom = Marshal.StringToHGlobalUni(multiSource);
      FileOpStruct.pTo = Marshal.StringToHGlobalUni(multiDest);

      FileOpStruct.fFlags = (ushort)Win32.ShellFileOperationFlags.FOF_NOCONFIRMATION;
      FileOpStruct.lpszProgressTitle = "";
      FileOpStruct.fAnyOperationsAborted = 0;
      FileOpStruct.hNameMappings = IntPtr.Zero;

      int retval = Win32.SHFileOperation(ref FileOpStruct);

      if(retval != 0) return false;
      return true;
     }
    }
}
+26  A: 

Hmm, I think I misunderstand the question but I'm going to risk it. What's wrong with the following straightforward method?

public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target) {
    foreach (DirectoryInfo dir in source.GetDirectories())
        CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name));
    foreach (FileInfo file in source.GetFiles())
        file.CopyTo(Path.Combine(target.FullName, file.Name));
}

EDIT Since this posting has garnered an impressive number of downvotes for such a simple answer to an equally simple question, let me add an explanation. Please read this before downvoting.

First of all, this code is not intendend as a drop-in replacement to the code in the question. It is for illustration purpose only.

Microsoft.VisualBasic.Devices.Computer.FileSystem.CopyDirectory does some additional correctness tests (e.g. whether the source and target are valid directories, whether the source is a parent of the target etc.) that are missing from this answer. That code is probably also more optimized.

That said, the code works well. It has (almost identically) been used in a mature software for years. Apart from the inherent fickleness present with all IO handlings (e.g. what happens if the user manually unplugs the USB drive while your code is writing to it?), there are no known problems.

In particular, I’d like to point out that the use of recursion here is absolutely not a problem. Neither in theory (conceptually, it’s the most elegant solution) nor in practice: this code will not overflow the stack. The stack is large enough to handle even deeply nested file hierarchies. Long before stack space becomes a problem, the folder path length limitation kicks in.

Notice that a malicious user might be able to break this assumption by using deeply-nested directories of one letter each. I haven’t tried this. But just to illustrate the point: in order to make this code overflow on a typical computer, the directories would have to be nested a few thousand times. This is simply not a realistic scenario.

Konrad Rudolph
Nowt, 'cept the recursive call. Why do we need to do that in C#?
Keith
What's wrong with recursion?
Mark Ingram
This is head recursion. It can fall prey to a stack overflow if the directories are nested deep enough.
spoulson
Until very recently, directory nesting depth was restricted by the OS. I doubt that you'll find directories that are nested more than a few hundred times (if even). The above code can take *much* more.
Konrad Rudolph
I like the recursive approach, the risk of a stack overflow is minimum at worst.
David Basarab
Downvoted since this code is obvious as well as the idea of writing new method instead of finding built-in solution. Both of them are not beautiful at all.
Dmitry Tashkinov
@DTashkinov: well excuse me but that seems a tad excessive. Why is obvious code == downvote? The opposite should be true. The built-in method had already been posted but Keith asked specifically for *another* method. Also, what do you mean by your last sentence? Sorry, but I just don't understand your reasons for downvoting at all.
Konrad Rudolph
@DTashkinov strange reasoning. It works and the code is usefull, therefore +!
PoweRoy
Sorry if don't understand the rules of upvoting and downvoting. The posted code itself looks ok. What I wanted to say is writing your own code for trivial tasks is the first temptation to come up when facing them, but the last thing to do. Personnaly I visited this page trying to find a built-in solution and was dissapointed.
Dmitry Tashkinov
This is not a better way. Period. Use the debugged and production ready code that Microsoft provided in the Framework.
AMissico
@AMissico: better than *what*? Nobody claimed it to be better than the VB code from the framework. We *know* it isn’t.
Konrad Rudolph
Works pretty good for me, you perfectionists! (just teasing)
Chris
+10  A: 

Try this:

Process proc = new Process();
proc.StartInfo.UseShellExecute = true;
proc.StartInfo.FileName = @"C:\WINDOWS\system32\xcopy.exe";
proc.StartInfo.Arguments = @"C:\source C:\destination /E /I";
proc.Start();

Your xcopy arguments may vary but you get the idea.

d4nt
I know I'm being pedantic, but it's "Your" not "You're".
Si
If you can't be pedantic on a site for programmers, where can you?!
d4nt
what do the /E /I stand for? Overwrite?
aron
/E tells it to copy all sub directories (even empty ones). /I tells it that if the destination doesn't exist create a directory with that name.
d4nt
A: 

Copy directory in C#

+5  A: 

Copied from MSDN:

using System;
using System.IO;

class CopyDir
{
    public static void Copy(string sourceDirectory, string targetDirectory)
    {
        DirectoryInfo diSource = new DirectoryInfo(sourceDirectory);
        DirectoryInfo diTarget = new DirectoryInfo(targetDirectory);

        CopyAll(diSource, diTarget);
    }

    public static void CopyAll(DirectoryInfo source, DirectoryInfo target)
    {
        // Check if the target directory exists, if not, create it.
        if (Directory.Exists(target.FullName) == false)
        {
            Directory.CreateDirectory(target.FullName);
        }

        // Copy each file into it's new directory.
        foreach (FileInfo fi in source.GetFiles())
        {
            Console.WriteLine(@"Copying {0}\{1}", target.FullName, fi.Name);
            fi.CopyTo(Path.Combine(target.ToString(), fi.Name), true);
        }

        // Copy each subdirectory using recursion.
        foreach (DirectoryInfo diSourceSubDir in source.GetDirectories())
        {
            DirectoryInfo nextTargetSubDir =
                target.CreateSubdirectory(diSourceSubDir.Name);
            CopyAll(diSourceSubDir, nextTargetSubDir);
        }
    }

    public static void Main()
    {
        string sourceDirectory = @"c:\sourceDirectory";
        string targetDirectory = @"c:\targetDirectory";

        Copy(sourceDirectory, targetDirectory);
    }

    // Output will vary based on the contents of the source directory.
}
fatcat1111
+1  A: 

Here's a twist on all this ...

I am migrating from "serverSrc" to "serverDest". serverSrc has previously had directories shared. (for instance, "E:\Divisions\Division1\" was mapped to "\serverSrc\D1".

Users were given access to the Share via a Mapped Drive. Within the scope of their Mapped Drive, users not only created Directories nested excessively deep, but also created some interesting file names and directory names. For instance ...

User mapped "\serverSrc\D1" to Drive Letter "T". Then created a directory structure and a file: "T:\simpleFolder\Folder with 228 characters\file.txt"

Windows allowed this operation because the sum of the path and filename "T:\simpleFolder\Folder with 200 characters\file.txt" is equal to only 225 (below the maximum of 240 - and I am being liberal with the Drive Letter and File Extention)

For the server migration, we'll use any of the above examples to copy "\serverSrc\E$" to \serverDest\E$".

When the Copy hits the path "E:\Divisions\Division1\simpleFolder\Folder with 228 characters\", a "PathTooLongException" is thrown because the sum of the characters in the path plus filename now exceeds 240 characters.

Anyone have an idea of how to combat this situation?

Chris
Best in a separate question which links back to this.
RCIX
+2  A: 

That would be a negative. Been there, done that. Still run into "PathTooLongException" "The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters."

As indicated above, the path is greater than the maximum allowed 260 characters. This problem is caused by shares and mappings. Once a share is mapped, Windows allows users to create paths exceeding 260 characters. a server path of "E:\User Drives\Home Directories\Divisions\User.Name" gets mapped to each user's H-Drive or "H:\". The user of course has full control over their home drive. Because each user's share is mapped to a drive letter, their built-in Windows File path begins at 3 characters - "H", ":" and "\" They are allowed a path of 257 additional characters (and they have used every bit of it in many cases)

So, from the user's point of view, they are within their rights to create folders and files. But from the server's point of view there is an extra 48 characters (give ot rake pending the length of the Division and the length of the user's name) taxxed onto the file paths. So if a user creates a path that is only 240 characters, when the server views it, it's over 288 characters.

The Visual Basic Object does not compensate. Microsoft.VisualBasic.Devices.Computer comp = new Microsoft.VisualBasic.Devices.Computer(); comp.FileSystem.CopyDirectory(strSource, strDestination);

I did discover that using old school DOS 8.3 path and filename truncation DOES work. For instance: I can remap a drive based on 8.3 Truncation "..\LongFo~1\" mapping the destination in the same manner and the files copy flawlessly.

So far, things that do not work: xCopy

C# System.IO

VB FileSystem

Attempting to use API calls to share a folder, then map to that new share (the server path to share a directory is subject to the same 260 character limitations)

But it was a nice attempt.

What has worked ...

Map a Drive to the Share needing to be copied. When PathTooLongException is thrown, Truncate the path to DOS 8.3 Short Path name

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    public static extern int GetShortPathName(
         [MarshalAs(UnmanagedType.LPTStr)] string path,
         [MarshalAs(UnmanagedType.LPTStr)] StringBuilder shortPath,
         int shortPathLength
         );

using

    [DllImport("Netapi32.dll")]
    private static extern uint NetShareAdd(
        [MarshalAs(UnmanagedType.LPWStr)] string strServer,
        Int32 dwLevel,
        ref SHARE_INFO_502 buf,
        out uint parm_err
    );

Share the DOS 8.3 short file name

Map to the newly created Share

Continue the File Copy

Disconnect the Mapped Drive

Delete the Share

Continue to the next Directory

Chris
+2  A: 

Copy folder recursively without recursion to avoid stack overflow.

public static void CopyDirectory(string source, string target)
{
    var stack = new Stack();
    stack.Push(new Folders(source, target));

    while (stack.Count > 0)
    {
        var folders = stack.Pop();
        Directory.CreateDirectory(folders.Target);
        foreach (var file in Directory.GetFiles(folders.Source, "*.*"))
        {
            File.Copy(file, Path.Combine(folders.Target, Path.GetFileName(file)));
        }

        foreach (var folder in Directory.GetDirectories(folders.Source))
        {
            stack.Push(new Folders(folder, Path.Combine(folders.Target, Path.GetFileName(folder))));
        }
    }
}
public class Folders
{
    public string Source { get; private set; }
    public string Target { get; private set; }

    public Folders(string source, string target)
    {
        Source = source;
        Target = target;
    }
}
Jens Granlund
+1  A: 

How to: Copy, Delete, and Move Files and Folders (C# Programming Guide)
http://msdn.microsoft.com/en-us/library/cc148994.aspx

How to: Iterate Through a Directory Tree (C# Programming Guide)
http://msdn.microsoft.com/en-us/library/bb513869.aspx

Robert Harvey
+1 for the links but it’s important to point out that the first link does **not** show how to copy nested directories – only files. A very incomplete piece of documentation indeed.
Konrad Rudolph
+2  A: 

Much easier

//Now Create all of the directories
            foreach (string dirPath in Directory.GetDirectories(SourcePath, "*", SearchOption.AllDirectories))
                Directory.CreateDirectory(dirPath.Replace(SourcePath, DestinationPath));

            //Copy all the files
            foreach (string newPath in Directory.GetFiles(SourcePath, "*.*", SearchOption.AllDirectories))
                File.Copy(newPath, newPath.Replace(SourcePath, DestinationPath));
tboswell
Neat idea - I don't know why I never thought of using `SearchOption.AllDirectories`. I'd probably use the `SubString` method rather than `Replace`, but that's just coding style stuff.
Keith