622

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?

Dariusz Woźniak
  • 9,640
  • 6
  • 60
  • 73
Keith
  • 150,284
  • 78
  • 298
  • 434
  • 117
    I would say that looking at the alternatives posted below, that the VB way doesn't look so ugly. – Kevin Kershaw Sep 12 '08 at 13:02
  • 3
    The real question is, why isn't this in the default IO library? By now we probably all have put the same code in our own personal library. – Boris Callens Mar 30 '09 at 11:42
  • 47
    How can it be a hack when it is part of the .NET Framework? Stop writing code and use what you got. – AMissico Dec 22 '09 at 03:51
  • 1
    `Microsoft.VisualBasic` is a bunch of add on stuff for making legacy VB6 projects easier to upgrade. You wouldn't normally use it in a C# application. If it was a 'proper' part of the .Net framework it would be in `System.IO`. Also only the `System.[something]` namespaces are part of Mono. – Keith Dec 22 '09 at 11:01
  • 15
    That is a common misconception. Microsft.VisualBasic contains all the common Visual Basic procedures that makes coding in VB so much easier. Microsot.VisualBasic.Compatibility is the assembly used for VB6 legacy. – AMissico Dec 23 '09 at 20:14
  • 1
    If you look at the source for CopyDirectory you will see that it uses System.IO or internal Shell API calls, depending on how CopyDirectory is called. – AMissico Dec 23 '09 at 20:17
  • The reason why Microsoft.VisualBasic is not added to a C# project is because it is not a VB project. Even VB.NET projects must add Microsoft.VisualBasic.Compatibility if they want to use legacy features. The compatibility layer is only added by the migration wizard or by the user. – AMissico Dec 23 '09 at 20:19
  • 72
    There is over 2,000 lines of code to Microsoft.VisualBasic.Devices.Computer.FileSystem. CopyDirectory ensures you are not copying a parent folder into a child folder and other checks. It is highly optimized, and so on. The selected answer is fragile code at best. – AMissico Dec 23 '09 at 20:26
  • 1
    That is a limitation of Mono. Not part of your answer. You want a better way and there is none. – AMissico Dec 23 '09 at 20:30
  • 6
    If you need System.Windows.Forms.Design, are you going to avoid adding that reference just because it has the words "Design"? Of course not. Therefore, avoiding something that is built into the framework just because it has the word "VisualBasic" in its name is, well..., just plain...silly. – AMissico Jan 11 '10 at 15:39
  • 21
    @AMissico - ok, so why is this optimised and complete code in `Microsoft.VisualBasic` and not `System.IO`? The reason it isn't in Mono is because all the libraries that are considered 'core' are `System.[something]` - all the other ones are not. I've got no problem referencing an extra DLL, but there's a good reason why Microsoft haven't included this feature in `System.IO`. – Keith Jan 12 '10 at 09:11
  • 4
    To all of you who think it just fine to use `Microsoft.VisualBasic`: would you be happy using a library from Perl in python? That's basically what's happening, with minor differences. Also, by not using `System.*` libraries, they are potentially constraining themselves from using Mono, which i gather may be a problem from the comments made by the OP. – RCIX Feb 03 '10 at 02:23
  • 13
    @RCIX: The best developers are the ones that get the job done effectively and quickly. So what if the *name* has Visual Basic in it, the fact is, the code in the DLL is merely MSIL. Microsoft has clearly written an effective algorithm and it would be a shame for someone to overlook it just because it has the word Visual Basic in its name. Especially so if an algorithm he writes just because of a DLL name oddity ends up being buggy and costs more time to fix. – jasonh Feb 03 '10 at 05:12
  • 1
    @Keith: Perhaps the BCL was finalized prior to them discovering that this function was needed. All we can do is speculate at this point. Do you have a good reason why you shouldn't include the DLL other than the name? Does the MSDN documentation say that this function is deprecated? – jasonh Feb 03 '10 at 05:14
  • 4
    @jasonh: nah - this is quite an old question now and the Microsoft.VisualBasic reference has been in shipped software for over a year. I just wondered why it was in the strange location - it should be something like `System.IO.Directory.Copy(sourceFolder, outputFolder)` – Keith Feb 03 '10 at 11:34
  • 1
    I agree that's where it should be, but it's definitely plausible that the BCL was finalized before they realized it would be necessary for VB and thus, the utility of it and they never thought to revisit it. Just theorizing mind you. :) – jasonh Feb 05 '10 at 04:31
  • I just Q&A'd this with some xcopy like options in C#. http://stackoverflow.com/questions/22151995/copy-a-folders-files-xcopyish-style-qa – Joe Johnston Mar 03 '14 at 16:36
  • [Copy Directory and its Content to Another Directory in C#](http://xneuron.wordpress.com/2007/04/12/copy-directory-and-its-content-to-another-directory-in-c/). – Eugene Yokota Sep 12 '08 at 11:42
  • Note that `Microsoft.VisualBasic.Devices` is not available in .NET core: https://github.com/dotnet/docs/issues/14546 – Ohad Schneider Aug 21 '20 at 10:25
  • 1
    Using this code in production to copy nested directories and files. Didn't find any issue. https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-copy-directories – shashwat Jan 15 '21 at 07:14
  • Give this an upvote: https://github.com/microsoft/dotnet/issues/794 – juFo Oct 26 '21 at 07:22
  • 1
    @juFo, sorry, I really don't think we need it now, I've responded to the GH issue with detail. – Keith Nov 03 '21 at 22:04

29 Answers29

651

Much easier

private static void CopyFilesRecursively(string sourcePath, string targetPath)
{
    //Now Create all of the directories
    foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories))
    {
        Directory.CreateDirectory(dirPath.Replace(sourcePath, targetPath));
    }

    //Copy all the files & Replaces any files with the same name
    foreach (string newPath in Directory.GetFiles(sourcePath, "*.*",SearchOption.AllDirectories))
    {
        File.Copy(newPath, newPath.Replace(sourcePath, targetPath), true);
    }
}
JKennedy
  • 18,150
  • 17
  • 114
  • 198
tboswell
  • 6,678
  • 1
  • 15
  • 2
  • 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 Sep 30 '10 at 07:29
  • 36
    It's a nice piece of code indeed but this is not the kind of code that can be used anywhere. Developers should be careful because dirPath.Replace could cause unwanted consequences. Just a warning to people that like doing copy and paste over the net. The code posted by @jaysponsored is safer because it doesn't use string.Replace but I'm sure it also has its corner cases. – Alex Dec 03 '11 at 18:58
  • Awesome! Its resolve my issue. But is there any way to copy/move files with billion of numbers and without removing existing files/folders ? – Gaurav Arora Feb 12 '12 at 09:26
  • 22
    Be careful with this code as it will throw an exception if the target directory exists already. It will also not overwrite files that already exists. Simply add a check before creating each directory and use the overload of File.Copy to overwrite target file if exists. – joerage May 15 '12 at 15:02
  • @Alex - What makes String.Substring better than String.Replace in this case? – Xaisoft Oct 01 '12 at 14:44
  • 1
    @Alex - that's exactly why I'd use the `SubString` method rather than `Replace` – Keith Oct 03 '12 at 08:26
  • 41
    @Xaisoft - `Replace` has a problem if you have a repeating pattern inside the path, for instance `"sourceDir/things/sourceDir/things"` should become `"destinationDir/things/sourceDir/things"`, but if you use replace it becomes `"destinationDir/things/destinationDir/things"` – Keith Oct 03 '12 at 08:35
  • @Rick this method does `File.Copy` so it leaves the original. If you wanted to remove the original you could use `File.Move` or (probably better) add a loop to delete the files after the copy is complete. – Keith Oct 03 '12 at 08:37
  • 45
    Why `*.*` instead of `*`? Don't you want to copy files without extensions too? – Daryl Mar 26 '13 at 22:43
  • 2
    Should also note that the paths require a backslash at the end. Without, the file and folder names will get appended to the destination root name for each item. Just a tip! – Dave Oct 08 '13 at 13:42
  • 2
    @AaronLS the `AllDirectories` option means the files may be in sub-directories of the source directory. Using your code will cause all files to be copied into the target directory itself rather than into a sub-directory of the target directory. – Pete Kirkham May 27 '14 at 12:48
  • 1
    @Alex: what's the problem with dirPath.Replace ??? Could you elaborate on the issues with this code assuming that Source and Destination paths are rooted? – Piotr Owsiak Aug 26 '14 at 14:30
  • Nice answer, but one remark: Isn't newPath variable name a little bit misleading? Shouldn't it be called rather sourcePath or sourceFilePath? – Dawid Ohia Jul 03 '15 at 14:06
  • What if we only want to files from directory and sub directories. and dont want directories? Any one ? – Adnan Ali Aug 20 '15 at 06:37
  • @Keith the chances of that are small, but i still agree that substring is a more correct way then to call replace – John Smith Nov 11 '15 at 02:42
  • 1
    This fails when the directory contains any junction link or symbolic link. Is there any way to overcome that? – Gokhan Kurt Mar 24 '17 at 06:50
  • The ```Directory.GetDirectories("*.*", System.IO.SearchOption.AllDirectories)``` will fail if any one of the subdirectories under the specified root causes a DirectoryNotFoundException or UnauthorizedAccessException, the whole method fails and returns no directories. Can you suggest a method that will take into consideration when the above mention exceptions are thrown – Phathutshedzo Khabubu Dec 10 '19 at 00:15
  • 2
    This doesn't work for me on Linux. Just use the recursive example outlined by Microsoft at [https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-copy-directories](https://learn.microsoft.com/en-us/dotnet/standard/io/how-to-copy-directories) – djrecipe Jun 28 '21 at 08:27
  • 1
    It's great. I'll add these lines at start of this method to enhance it: `if (!sourcePath.EndsWith(@"\")) sourcePath += @"\"; if (!targetPath.EndsWith(@"\")) targetPath += @"\"; if (!Directory.Exists(targetPath)) Directory.CreateDirectory(targetPath);` The last "if" corrects a bug where I want to copy contents from a folder where there's no subfolder in it, and my target path doesn't exist (i'm creating it from string in targetPath). – Youkko Aug 12 '21 at 02:17
  • In my last comment, the first 2 "ifs" corrects a bug that happened to me where it appended the folder from target to each filename being copied. StackOverflow didn't allow me to edit my comment after 5 minutes... – Youkko Aug 12 '21 at 02:24
273

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
  • 530,221
  • 131
  • 937
  • 1,214
  • Nowt, 'cept the recursive call. Why do we need to do that in C#? – Keith Sep 12 '08 at 12:03
  • 6
    This is head recursion. It can fall prey to a stack overflow if the directories are nested deep enough. – spoulson Sep 12 '08 at 12:30
  • 30
    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 Sep 12 '08 at 12:55
  • 6
    I like the recursive approach, the risk of a stack overflow is minimum at worst. – David Basarab Sep 12 '08 at 12:58
  • 56
    @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 Jul 22 '09 at 15:46
  • 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 Nov 26 '09 at 15:48
  • 1
    This is not a better way. Period. Use the debugged and production ready code that Microsoft provided in the Framework. – AMissico Dec 23 '09 at 21:25
  • 6
    @AMissico: better than *what*? Nobody claimed it to be better than the VB code from the framework. We *know* it isn’t. – Konrad Rudolph Dec 24 '09 at 15:42
  • 6
    Seems a reasonable approach *IF* you don't want to use the VB code mentioned in the question. I don't understand the objections to recursion. That code is never going to blow the stack unless you hit a situation with a symbolic link (aka reparse point) back to a parent directory and it's easy enough to avoid following those and also count recursion depth to protect against other weird loops. Microsoft recommend recursion in their HowTo answer: http://msdn.microsoft.com/en-us/library/vstudio/bb513869%28v=vs.100%29.aspx – GrahamS Sep 29 '14 at 20:02
  • Upvoted, and added an [answer](http://stackoverflow.com/questions/58744/best-way-to-copy-the-entire-contents-of-a-directory-in-c-sharp/29463011#29463011) which is a variation of this, where the `source` itself becomes a folder under `target`, then it's children go under that. – toddmo Apr 05 '15 at 22:41
  • I'm not sure exactly why or how, but when I tried, the code would sometimes fail by trying to copy files before the directory is actually made - I had to add Directory.CreateDirectory(target.FullName); to the second if statement for the code to always work. Love the recursive code though, great solution – The Lemon Jul 20 '20 at 04:10
  • 1
    @TheLemon The code currently assumes that the top-level target directory already exists. If the code called with a non-existent target, it will fail (and adding a check for this might be a good idea!). However, *subdirectories* are correctly created: that’s what the `target.CreateSubdirectory` call does. – Konrad Rudolph Jul 20 '20 at 08:39
164

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)
    {
        Directory.CreateDirectory(target.FullName);

        // Copy each file into the new directory.
        foreach (FileInfo fi in source.GetFiles())
        {
            Console.WriteLine(@"Copying {0}\{1}", target.FullName, fi.Name);
            fi.CopyTo(Path.Combine(target.FullName, 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.
}
Justin R.
  • 23,435
  • 23
  • 108
  • 157
  • 13
    There's no reason to check if the directory exists, simply call Directoty.CreateDirectory which will do nothing if the directory already exists. – Tal Jerome Sep 21 '15 at 15:45
  • 1
    For those looking to deal with paths greater than 256 characters, you can use a Nuget package called ZetaLongPaths – A.K May 11 '16 at 06:44
  • 4
    This answer seems to be the most useful of them all. By using DirectoryInfo instead of strings a lot of potential problems are avoided. – DaedalusAlpha Dec 12 '18 at 16:13
60

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
  • 7,431
  • 3
  • 31
  • 33
  • 1
    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 Sep 12 '08 at 12:05
  • 13
    Is loading the VB assembly expensive? The VB options are much more elegant than the C# versions. – jwmiller5 Mar 27 '09 at 19:12
  • 3
    What "VB's backward-compatibility stuff"? CopyDirectory uses either the Shell or the Framework. – AMissico Dec 22 '09 at 03:50
  • 3
    I do wish it was on `System.IO.Directory`, but it's better than rewriting it! – Josh M. Sep 05 '15 at 02:11
54

Try this:

Process proc = new Process();
proc.StartInfo.UseShellExecute = true;
proc.StartInfo.FileName = Path.Combine(Environment.SystemDirectory, "xcopy.exe");
proc.StartInfo.Arguments = @"C:\source C:\destination /E /I";
proc.Start();

Your xcopy arguments may vary but you get the idea.

Jim G.
  • 15,141
  • 22
  • 103
  • 166
d4nt
  • 15,475
  • 9
  • 42
  • 51
  • what do the /E /I stand for? Overwrite? – aron Mar 03 '10 at 02:16
  • 4
    /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 Mar 03 '10 at 17:22
  • 6
    add double quote to be safe. – jaysonragasa Aug 01 '11 at 09:44
  • 7
    Add /Y to prevent getting prompted to overwrite existing files. http://stackoverflow.com/q/191209/138938 – Jon Crowell Feb 23 '12 at 00:13
  • 2
    Adding the /d option is useful to only copy modified files, along with the /i option – smirkingman Jan 24 '13 at 10:53
  • I know I'm getting here late, but I was looking for a solution to this problem as well. I think you should have `/S` as well if you're going to use `/E`: "Use /e with the /s and /t command-line options" (http://technet.microsoft.com/en-us/library/bb491035.aspx) – wlyles Aug 12 '14 at 16:43
  • If cross-platform support is not needed, this is hands down the best approach. I'd use *robocopy* though, e.g. `Robocopy C:\A C:\B /E` – Ohad Schneider Dec 04 '14 at 06:05
  • 31
    Sorry, but this is horrible. It assumes that the target system is windows. It assumes that future versions include xcopy.exe at that specific path. It assumes that the parameters of xcopy do not change. It requires to assemble the parameters for xcopy as string, which introduces plenty of error potential. Also the sample does not mention any error handling for the results of the started process, which i would expect, because contrary to other methods this would fail silently. – cel sharp Sep 05 '16 at 09:33
  • 4
    @MatthiasJansen, I think you took it very personal. The answer is to the point and explains much about how to achieve it... Since the question doesnt demand the cross platform compatibility or not using xcopy or anything else the poster just answered to explain how this can be achieved one way... There might be 1000 ways to do same thing and the answers vary.. that's why this forum is here to address and programmers around the globe come here to share their experiences. I down vote your comment. – KMX Oct 18 '16 at 17:05
  • 1
    It worked perfectly - thank you for posting. I needed a quick and dirty solution for a utility I'm writing. I didn't want a lot of code. – Matt Jan 07 '18 at 16:05
  • what are better or faster, `xcopy.exe` or `System.IO` ? – ewwink May 28 '18 at 19:28
45

This site always have helped me out a lot, and now it's my turn to help the others with what I know.

I hope that my code below be useful for someone.

string source_dir = @"E:\";
string destination_dir = @"C:\";

// substring is to remove destination_dir absolute path (E:\).

// Create subdirectory structure in destination    
    foreach (string dir in System.IO.Directory.GetDirectories(source_dir, "*", System.IO.SearchOption.AllDirectories))
    {
        System.IO.Directory.CreateDirectory(System.IO.Path.Combine(destination_dir, dir.Substring(source_dir.Length + 1)));
        // Example:
        //     > C:\sources (and not C:\E:\sources)
    }

    foreach (string file_name in System.IO.Directory.GetFiles(source_dir, "*", System.IO.SearchOption.AllDirectories))
    {
        System.IO.File.Copy(file_name, System.IO.Path.Combine(destination_dir, file_name.Substring(source_dir.Length + 1)));
    }
eduardomozart
  • 1,444
  • 15
  • 14
  • 1
    Remember about the trailing backslash – Alexey F Oct 16 '15 at 12:06
  • 33
    Folks, use `Path.Combine()`. Never use string concatenation to put file paths together. – Andy Aug 18 '17 at 12:48
  • 3
    You have an OBOB in the above code snippet. You should be using `source_dir.Length + 1`, not `source_dir.Length`. – PellucidWombat Feb 14 '18 at 04:07
  • This code is a good concept, but... A file doesn't have to have a "." in it, so it would be better to use ystem.IO.Directory.GetFiles(source_dir, "*", System.IO.SearchOption.AllDirectories)) – Jean Libera May 18 '18 at 20:33
  • Thank you @JeanLibera, you're right. I changed the code with your suggestion. – eduardomozart Jun 12 '18 at 16:16
  • replace 'file_name.Substring(..)' with 'Path.GetFileName(file_name)' – dgzornoza Jul 05 '18 at 13:13
  • I believe that the replacement will not work @dgzornoza because the substring functions remove the source_dir part from file name, but keep it's full path. Example: you're copying a file from D:\sources\install.wim to E:\. With the substring function, it will became E:\sources\install.wim, but if I use GetFileName it will be E:\install.wim. With recursion, the mess is even worse. – eduardomozart Jul 05 '18 at 13:24
  • yes, is not valid GetFileName. Best solution is Substring, replace from previous answer is not safe. Thanks – dgzornoza Jul 06 '18 at 06:43
  • Best answer, but you should use `source_dir.Length + 1` as mentioned or `TrimStart('\\'))` – juanora Sep 04 '18 at 09:58
  • Nice. Initially my folders would be created with the first letter missing Eg: ongs instead of Songs. Then I removed the slash in source_dir = @"E:\"; and made it source_dir = @"E:";. Then it worked in a jiffy !!! – Venugopal M Dec 05 '20 at 08:32
16

Copy folder recursively without recursion to avoid stack overflow.

public static void CopyDirectory(string source, string target)
{
    var stack = new Stack<Folders>();
    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;
    }
}
sarnold
  • 102,305
  • 22
  • 181
  • 238
Jens Granlund
  • 4,950
  • 1
  • 31
  • 31
5

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;
        }
    }
}
4

tboswell 's replace Proof version (which is resilient to repeating pattern in filepath)

public static void copyAll(string SourcePath , string DestinationPath )
{
   //Now Create all of the directories
   foreach (string dirPath in Directory.GetDirectories(SourcePath, "*", SearchOption.AllDirectories))
      Directory.CreateDirectory(Path.Combine(DestinationPath ,dirPath.Remove(0, SourcePath.Length ))  );

   //Copy all the files & Replaces any files with the same name
   foreach (string newPath in Directory.GetFiles(SourcePath, "*.*",  SearchOption.AllDirectories))
      File.Copy(newPath, Path.Combine(DestinationPath , newPath.Remove(0, SourcePath.Length)) , true);
    }
bh_earth0
  • 2,537
  • 22
  • 24
  • In my case, for the directories, I had to use `Path.Join()` instead of `Path.Combine()`. I don't fully understand why though, but I guess I was doing something related to this remark in [the documentation](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.combine?view=net-5.0#remarks), which recommends `Path.Join()` – davrob01 Jan 10 '21 at 09:03
4

My solution is basically a modification of @Termininja's answer, however I have enhanced it a bit and it appears to be more than 5 times faster than the accepted answer.

public static void CopyEntireDirectory(string path, string newPath)
{
    Parallel.ForEach(Directory.GetFileSystemEntries(path, "*", SearchOption.AllDirectories)
    ,(fileName) =>
    {
        string output = Regex.Replace(fileName, "^" + Regex.Escape(path), newPath);
        if (File.Exists(fileName))
        {
            Directory.CreateDirectory(Path.GetDirectoryName(output));
            File.Copy(fileName, output, true);
        }
        else
            Directory.CreateDirectory(output);
    });
}

EDIT: Modifying @Ahmed Sabry to full parallel foreach does produce a better result, however the code uses recursive function and its not ideal in some situation.

public static void CopyEntireDirectory(DirectoryInfo source, DirectoryInfo target, bool overwiteFiles = true)
{
    if (!source.Exists) return;
    if (!target.Exists) target.Create();

    Parallel.ForEach(source.GetDirectories(), (sourceChildDirectory) =>
        CopyEntireDirectory(sourceChildDirectory, new DirectoryInfo(Path.Combine(target.FullName, sourceChildDirectory.Name))));

    Parallel.ForEach(source.GetFiles(), sourceFile =>
        sourceFile.CopyTo(Path.Combine(target.FullName, sourceFile.Name), overwiteFiles));
}
OKEEngine
  • 888
  • 11
  • 28
4

Here is a concise and efficient solution:

namespace System.IO {
  public static class ExtensionMethods {

    public static void CopyTo(this DirectoryInfo srcPath, string destPath) {
      Directory.CreateDirectory(destPath);
      Parallel.ForEach(srcPath.GetDirectories("*", SearchOption.AllDirectories), 
        srcInfo => Directory.CreateDirectory($"{destPath}{srcInfo.FullName[srcPath.FullName.Length..]}"));
      Parallel.ForEach(srcPath.GetFiles("*", SearchOption.AllDirectories), 
        srcInfo => File.Copy(srcInfo.FullName, $"{destPath}{srcInfo.FullName[srcPath.FullName.Length..]}", true));
      });
    }

  }
}

To use:

new DirectoryInfo(sourcePath).CopyTo(destinationPath);
kofifus
  • 17,260
  • 17
  • 99
  • 173
3

It may not be performance-aware, but I'm using it for 30MB folders and it works flawlessly. Plus, I didn't like all the amount of code and recursion required for such an easy task.

var src = "c:\src";
var dest = "c:\dest";
var cmp = CompressionLevel.NoCompression;
var zip = source_folder + ".zip";

ZipFile.CreateFromDirectory(src, zip, cmp, includeBaseDirectory: false);
ZipFile.ExtractToDirectory(zip, dest_folder);

File.Delete(zip);

Note: ZipFile is available on .NET 4.5+ in the System.IO.Compression namespace

Ohad Schneider
  • 36,600
  • 15
  • 168
  • 198
AlexanderD
  • 596
  • 2
  • 10
  • 22
  • 1
    Neither do I, hence the question, but the selected answer doesn't need recursion. This answer creates a zip file on disk, which is a lot of additional work for a file copy - not only are you creating an additional copy of the data, but you're spending processor time compressing and decompressing it. I'm sure it works, the same way you can probably knock a nail in with your shoe, but it's more work with more things that can go wrong, while there are better ways of doing it. – Keith Jul 17 '18 at 09:12
  • The reason I ended up with this is string replacements. As others have pointed out, the accepted answer presents many concerns; junction link may not work, as well as repeating folder pattern or files without extension or name. Less code, less chance to go wrong. And since processor time is not a concern for me, it makes it suitable for my specific case – AlexanderD Jul 17 '18 at 13:16
  • 2
    Yeah, that's like driving 1000 miles out of your way to avoid a single traffic light, but it's your journey, so go for it. Checking for folder patterns is trivial compared to what ZIP needs to do under the hood. I'd strongly recommend against this for anyone who cares about not wasting processor, disk, electricity or where this needs to run alongside other programs on the same machine. Also, if you're ever asked this type of question at interview _never_ go with "my code is simple so I don't care about processor time" - you won't get the job. – Keith Jul 17 '18 at 15:31
  • 1
    I switched to the [answer provided by @justin-r](https://stackoverflow.com/a/690980/5284818). Still, I'll leave this answer there as just another way of doing it – AlexanderD Jul 18 '18 at 09:31
  • 4
    If the folders are on separate network shares and contain a lot of files, this would be the best option in my opinion. – Danny Parker Nov 22 '18 at 15:52
  • Very creative! In terms of correctness this should probably be the #1 answer, I'm sure it handles a 1000 edge cases other naïve answers miss. Alternatively execute `robocopy.exe` (if x-plat is not an issue). – Ohad Schneider Aug 21 '20 at 10:29
3

A minor improvement on d4nt's answer, as you probably want to check for errors and not have to change xcopy paths if you're working on a server and development machine:

public void CopyFolder(string source, string destination)
{
    string xcopyPath = Environment.GetEnvironmentVariable("WINDIR") + @"\System32\xcopy.exe";
    ProcessStartInfo info = new ProcessStartInfo(xcopyPath);
    info.UseShellExecute = false;
    info.RedirectStandardOutput = true;
    info.Arguments = string.Format("\"{0}\" \"{1}\" /E /I", source, destination);

    Process process = Process.Start(info);
    process.WaitForExit();
    string result = process.StandardOutput.ReadToEnd();

    if (process.ExitCode != 0)
    {
        // Or your own custom exception, or just return false if you prefer.
        throw new InvalidOperationException(string.Format("Failed to copy {0} to {1}: {2}", source, destination, result));
    }
}
Chris S
  • 64,770
  • 52
  • 221
  • 239
2

This is my code hope this help

    private void KCOPY(string source, string destination)
    {
        if (IsFile(source))
        {
            string target = Path.Combine(destination, Path.GetFileName(source));
            File.Copy(source, target, true);
        }
        else
        {
            string fileName = Path.GetFileName(source);
            string target = System.IO.Path.Combine(destination, fileName);
            if (!System.IO.Directory.Exists(target))
            {
                System.IO.Directory.CreateDirectory(target);
            }

            List<string> files = GetAllFileAndFolder(source);

            foreach (string file in files)
            {
                KCOPY(file, target);
            }
        }
    }

    private List<string> GetAllFileAndFolder(string path)
    {
        List<string> allFile = new List<string>();
        foreach (string dir in Directory.GetDirectories(path))
        {
            allFile.Add(dir);
        }
        foreach (string file in Directory.GetFiles(path))
        {
            allFile.Add(file);
        }

        return allFile;
    }
    private bool IsFile(string path)
    {
        if ((File.GetAttributes(path) & FileAttributes.Directory) == FileAttributes.Directory)
        {
            return false;
        }
        return true;
    }
Khoi_Vjz_Boy
  • 41
  • 1
  • 6
  • See the selected answer, by using the `SearchOption` flag on the searches for folders and files it does this in 4 lines of code. Also check out the `.HasFlag` extension now on enums. – Keith Sep 24 '12 at 09:08
2

If you like Konrad's popular answer, but you want the source itself to be a folder under target, rather than putting it's children under the target folder, here's the code for that. It returns the newly created DirectoryInfo, which is handy:

public static DirectoryInfo CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target)
{
  var newDirectoryInfo = target.CreateSubdirectory(source.Name);
  foreach (var fileInfo in source.GetFiles())
    fileInfo.CopyTo(Path.Combine(newDirectoryInfo.FullName, fileInfo.Name));

  foreach (var childDirectoryInfo in source.GetDirectories())
    CopyFilesRecursively(childDirectoryInfo, newDirectoryInfo);

  return newDirectoryInfo;
}
toddmo
  • 20,682
  • 14
  • 97
  • 107
2

You can always use this, taken from Microsofts website.

static void Main()
{
    // Copy from the current directory, include subdirectories.
    DirectoryCopy(".", @".\temp", true);
}

private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
{
    // Get the subdirectories for the specified directory.
    DirectoryInfo dir = new DirectoryInfo(sourceDirName);

    if (!dir.Exists)
    {
        throw new DirectoryNotFoundException(
            "Source directory does not exist or could not be found: "
            + sourceDirName);
    }

    DirectoryInfo[] dirs = dir.GetDirectories();
    // If the destination directory doesn't exist, create it.
    if (!Directory.Exists(destDirName))
    {
        Directory.CreateDirectory(destDirName);
    }

    // Get the files in the directory and copy them to the new location.
    FileInfo[] files = dir.GetFiles();
    foreach (FileInfo file in files)
    {
        string temppath = Path.Combine(destDirName, file.Name);
        file.CopyTo(temppath, false);
    }

    // If copying subdirectories, copy them and their contents to new location.
    if (copySubDirs)
    {
        foreach (DirectoryInfo subdir in dirs)
        {
            string temppath = Path.Combine(destDirName, subdir.Name);
            DirectoryCopy(subdir.FullName, temppath, copySubDirs);
        }
    }
}
iato
  • 356
  • 5
  • 16
  • 2
    This is great -- Keep in mind the line `file.CopyTo(temppath, false);` says "copy this file to this place, only if it doesn't exist", which most of the time isn't what we want. But, i can understand why it defaults to that. Maybe add a flag to the method for overwriting files. – Andy Aug 18 '17 at 13:42
1

Here is an extension method for DirectoryInfo a la FileInfo.CopyTo (note the overwrite parameter):

public static DirectoryInfo CopyTo(this DirectoryInfo sourceDir, string destinationPath, bool overwrite = false)
{
    var sourcePath = sourceDir.FullName;

    var destination = new DirectoryInfo(destinationPath);

    destination.Create();

    foreach (var sourceSubDirPath in Directory.EnumerateDirectories(sourcePath, "*", SearchOption.AllDirectories))
        Directory.CreateDirectory(sourceSubDirPath.Replace(sourcePath, destinationPath));

    foreach (var file in Directory.EnumerateFiles(sourcePath, "*", SearchOption.AllDirectories))
        File.Copy(file, file.Replace(sourcePath, destinationPath), overwrite);

    return destination;
}
Daryl
  • 3,253
  • 4
  • 29
  • 39
1

Use this class.

public static class Extensions
{
    public static void CopyTo(this DirectoryInfo source, DirectoryInfo target, bool overwiteFiles = true)
    {
        if (!source.Exists) return;
        if (!target.Exists) target.Create();

        Parallel.ForEach(source.GetDirectories(), (sourceChildDirectory) => 
            CopyTo(sourceChildDirectory, new DirectoryInfo(Path.Combine(target.FullName, sourceChildDirectory.Name))));

        foreach (var sourceFile in source.GetFiles())
            sourceFile.CopyTo(Path.Combine(target.FullName, sourceFile.Name), overwiteFiles);
    }
    public static void CopyTo(this DirectoryInfo source, string target, bool overwiteFiles = true)
    {
        CopyTo(source, new DirectoryInfo(target), overwiteFiles);
    }
}
BenSabry
  • 483
  • 4
  • 10
  • 1
    This is similar to other answers, refactored to use `.ToList().ForEach(` (which is slightly more work, memory and slightly slower than just enumerating the directories directly) and as an extension method. The selected answer uses `SearchOption.AllDirectories` and avoids recursion, so I'd recommend switching to that model. Also, you usually don't need the name of the type in extension methods - I'd rename it to `CopyTo()` so that it became `sourceDir.CopyTo(destination);` – Keith Aug 10 '17 at 13:16
1

One variant with only one loop for copying of all folders and files:

foreach (var f in Directory.GetFileSystemEntries(path, "*", SearchOption.AllDirectories))
{
    var output = Regex.Replace(f, @"^" + path, newPath);
    if (File.Exists(f)) File.Copy(f, output, true);
    else Directory.CreateDirectory(output);
}
Termininja
  • 6,620
  • 12
  • 48
  • 49
  • If you're going to use `Regex`, you should probably also `Regex.Escape(path)` as part of your expression composition (especially considering the Windows path separator). You *might* also get benefit from creating (and maybe compiling) a `new Regex()` object outside of the loop, rather than relying on the static method. – jimbobmcgee Nov 02 '19 at 00:57
1

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
  • 330,807
  • 53
  • 334
  • 373
0

Better than any code (extension method to DirectoryInfo with recursion)

public static bool CopyTo(this DirectoryInfo source, string destination)
    {
        try
        {
            foreach (string dirPath in Directory.GetDirectories(source.FullName))
            {
                var newDirPath = dirPath.Replace(source.FullName, destination);
                Directory.CreateDirectory(newDirPath);
                new DirectoryInfo(dirPath).CopyTo(newDirPath);
            }
            //Copy all the files & Replaces any files with the same name
            foreach (string filePath in Directory.GetFiles(source.FullName))
            {
                File.Copy(filePath, filePath.Replace(source.FullName,destination), true);
            }
            return true;
        }
        catch (IOException exp)
        {
            return false;
        }
    }
malballah
  • 681
  • 1
  • 9
  • 17
  • 1
    I'm not sure what this adds over the accepted answer, other than using recursion (where that doesn't need to) and hiding exceptions to make debugging harder. – Keith Oct 20 '17 at 21:13
0

Copy and replace all files of the folder

        public static void CopyAndReplaceAll(string SourcePath, string DestinationPath, string backupPath)
    {
            foreach (string dirPath in Directory.GetDirectories(SourcePath, "*", SearchOption.AllDirectories))
            {
                Directory.CreateDirectory($"{DestinationPath}{dirPath.Remove(0, SourcePath.Length)}");
                Directory.CreateDirectory($"{backupPath}{dirPath.Remove(0, SourcePath.Length)}");
            }
            foreach (string newPath in Directory.GetFiles(SourcePath, "*.*", SearchOption.AllDirectories))
            {
                if (!File.Exists($"{ DestinationPath}{newPath.Remove(0, SourcePath.Length)}"))
                    File.Copy(newPath, $"{ DestinationPath}{newPath.Remove(0, SourcePath.Length)}");
                else
                    File.Replace(newPath
                        , $"{ DestinationPath}{newPath.Remove(0, SourcePath.Length)}"
                        , $"{ backupPath}{newPath.Remove(0, SourcePath.Length)}", false);
            }
    }
Lakmal
  • 779
  • 1
  • 8
  • 16
0

The code below is microsoft suggestion how-to-copy-directories and it is shared by dear @iato but it just copies sub directories and files of source folder recursively and doesn't copy the source folder it self (like right click -> copy ).

but there is a tricky way below this answer :

private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs = true)
        {
            // Get the subdirectories for the specified directory.
            DirectoryInfo dir = new DirectoryInfo(sourceDirName);

            if (!dir.Exists)
            {
                throw new DirectoryNotFoundException(
                    "Source directory does not exist or could not be found: "
                    + sourceDirName);
            }

            DirectoryInfo[] dirs = dir.GetDirectories();
            // If the destination directory doesn't exist, create it.
            if (!Directory.Exists(destDirName))
            {
                Directory.CreateDirectory(destDirName);
            }

            // Get the files in the directory and copy them to the new location.
            FileInfo[] files = dir.GetFiles();
            foreach (FileInfo file in files)
            {
                string temppath = Path.Combine(destDirName, file.Name);
                file.CopyTo(temppath, false);
            }

            // If copying subdirectories, copy them and their contents to new location.
            if (copySubDirs)
            {
                foreach (DirectoryInfo subdir in dirs)
                {
                    string temppath = Path.Combine(destDirName, subdir.Name);
                    DirectoryCopy(subdir.FullName, temppath, copySubDirs);
                }
            }
        }

if you want to copy contents of source folder and subfolders recursively you can simply use it like this :

string source = @"J:\source\";
string dest= @"J:\destination\";
DirectoryCopy(source, dest);

but if you want to copy the source directory it self (similar that you have right clicked on source folder and clicked copy then in the destination folder you clicked paste) you should use like this :

 string source = @"J:\source\";
 string dest= @"J:\destination\";
 DirectoryCopy(source, Path.Combine(dest, new DirectoryInfo(source).Name));
Arash.Zandi
  • 1,010
  • 2
  • 13
  • 24
  • has been already posted some answers below: https://stackoverflow.com/a/45199038/1951524 – Martin Schneider Jun 18 '19 at 14:52
  • Thanks @MA-Maddin, but does it copy the source folder itself ? or just the contents ? – Arash.Zandi Jun 19 '19 at 17:45
  • FYI, VB.NET's new Microsoft.VisualBasic.Devices.Computer().FileSystem.CopyDirectory has override/Ship options and progress bar display... those c# codes are not fully equivalent. – Jonney Jun 10 '21 at 02:15
0

Below code to copy all files from source to destination of given pattern in same folder structure:

public static void Copy()
        {
            string sourceDir = @"C:\test\source\";
            string destination = @"C:\test\destination\";

            string[] textFiles = Directory.GetFiles(sourceDir, "*.txt", SearchOption.AllDirectories);

            foreach (string textFile in textFiles)
            {
                string fileName = textFile.Substring(sourceDir.Length);
                string directoryPath = Path.Combine(destination, Path.GetDirectoryName(fileName));
                if (!Directory.Exists(directoryPath))
                    Directory.CreateDirectory(directoryPath);

                File.Copy(textFile, Path.Combine(directoryPath, Path.GetFileName(textFile)), true);
            }
        }

enter image description here

Rahul Shukla
  • 646
  • 6
  • 19
0

Just wanted to add my version. It can handle both directories and files, and can overwrite or skip if destination file exists.

public static void Copy(
    string source,
    string destination,
    string pattern = "*",
    bool includeSubFolders = true,
    bool overwrite = true,
    bool overwriteOnlyIfSourceIsNewer = false)
{
    if (File.Exists(source))
    {
        // Source is a file, copy and leave
        CopyFile(source, destination);
        return;
    }

    if (!Directory.Exists(source))
    {
        throw new DirectoryNotFoundException($"Source directory does not exists: `{source}`");
    }

    var files = Directory.GetFiles(
        source,
        pattern,
        includeSubFolders ?
            SearchOption.AllDirectories :
            SearchOption.TopDirectoryOnly);

    foreach (var file in files)
    {
        var newFile = file.Replace(source, destination);
        CopyFile(file, newFile, overwrite, overwriteOnlyIfSourceIsNewer);
    }
}

private static void CopyFile(
    string source,
    string destination,
    bool overwrite = true,
    bool overwriteIfSourceIsNewer = false)
{
    if (!overwrite && File.Exists(destination))
    {
        return;
    }

    if (overwriteIfSourceIsNewer && File.Exists(destination))
    {
        var sourceLastModified = File.GetLastWriteTimeUtc(source);
        var destinationLastModified = File.GetLastWriteTimeUtc(destination);
        if (sourceLastModified <= destinationLastModified)
        {
            return;
        }

        CreateDirectory(destination);
        File.Copy(source, destination, overwrite);
        return;
    }

    CreateDirectory(destination);
    File.Copy(source, destination, overwrite);
}

private static void CreateDirectory(string filePath)
{
    var targetDirectory = Path.GetDirectoryName(filePath);
    if (targetDirectory != null && !Directory.Exists(targetDirectory))
    {
        Directory.CreateDirectory(targetDirectory);
    }
}
M. Mennan Kara
  • 10,072
  • 2
  • 35
  • 39
0

Properties of this code:

  • No parallel task, is less performant, but the idea is to treat file by file, so you can log or stop.
  • Can skip hiddden files
  • Can skip by modified date
  • Can break or not (you chose) on a file copy error
  • Uses Buffer of 64K for SMB and FileShare.ReadWrite to avoid locks
  • Personalize your Exceptions Message
  • For Windows

Notes
ExceptionToString() is a personal extension that tries to get inner exceptions and display stack. Replace it for ex.Message or any other code.
log4net.ILog _log I use ==Log4net== You can make your Log in a different way.

/// <summary>
/// Recursive Directory Copy
/// </summary>
/// <param name="fromPath"></param>
/// <param name="toPath"></param>
/// <param name="continueOnException">on error, continue to copy next file</param>
/// <param name="skipHiddenFiles">To avoid files like thumbs.db</param>
/// <param name="skipByModifiedDate">Does not copy if the destiny file has the same or more recent modified date</param>
/// <remarks>
/// </remarks>
public static void CopyEntireDirectory(string fromPath, string toPath, bool continueOnException = false, bool skipHiddenFiles = true, bool skipByModifiedDate = true)
{
    log4net.ILog _log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
    string nl = Environment.NewLine;

    string sourcePath = "";
    string destPath = "";
    string _exMsg = "";

    void TreateException(Exception ex)
    {
        _log.Warn(_exMsg);
        if (continueOnException == false)
        {
            throw new Exception($"{_exMsg}{nl}----{nl}{ex.ExceptionToString()}");
        }
    }

    try
    {
        foreach (string fileName in Directory.GetFileSystemEntries(fromPath, "*", SearchOption.AllDirectories))
        {
            sourcePath = fileName;
            destPath = Regex.Replace(fileName, "^" + Regex.Escape(fromPath), toPath);

            Directory.CreateDirectory(Path.GetDirectoryName(destPath));
            
            _log.Debug(FileCopyStream(sourcePath, destPath,skipHiddenFiles,skipByModifiedDate));
        }
    }
    // Directory must be less than 148 characters, File must be less than 261 characters
    catch (PathTooLongException)
    {
        throw new Exception($"Both paths must be less than 148 characters:{nl}{sourcePath}{nl}{destPath}");
    }
    // Not enough disk space. Cancel further copies
    catch (IOException ex) when ((ex.HResult & 0xFFFF) == 0x27 || (ex.HResult & 0xFFFF) == 0x70)
    {
        throw new Exception($"Not enough disk space:{nl}'{toPath}'");
    }
    // used by another process
    catch (IOException ex) when ((uint)ex.HResult == 0x80070020)
    {
        _exMsg = $"File is being used by another process:{nl}'{destPath}'{nl}{ex.Message}";
        TreateException(ex);
    }
    catch (UnauthorizedAccessException ex)
    {
        _exMsg = $"Unauthorized Access Exception:{nl}from:'{sourcePath}'{nl}to:{destPath}";
        TreateException(ex);
    }
    catch (Exception ex)
    {
        _exMsg = $"from:'{sourcePath}'{nl}to:{destPath}";
        TreateException(ex);
    }
}

/// <summary>
/// File Copy using Stream 64K and trying to avoid locks with fileshare
/// </summary>
/// <param name="sourcePath"></param>
/// <param name="destPath"></param>
/// <param name="skipHiddenFiles">To avoid files like thumbs.db</param>
/// <param name="skipByModifiedDate">Does not copy if the destiny file has the same or more recent modified date</param>
public static string FileCopyStream(string sourcePath, string destPath, bool skipHiddenFiles = true, bool skipByModifiedDate = true)
{
    // Buffer should be 64K = 65536‬ bytes 
    // Increasing the buffer size beyond 64k will not help in any circunstance,
    // as the underlying SMB protocol does not support buffer lengths beyond 64k."
    byte[] buffer = new byte[65536];

    if (!File.Exists(sourcePath))
        return $"is not a file: '{sourcePath}'";

    FileInfo sourcefileInfo = new FileInfo(sourcePath);
    FileInfo destFileInfo = null;
    if (File.Exists(destPath))
        destFileInfo = new FileInfo(destPath);

    if (skipHiddenFiles)
    {
        if (sourcefileInfo.Attributes.HasFlag(FileAttributes.Hidden))
            return $"Hidden File Not Copied: '{sourcePath}'";
    }

    using (FileStream input = sourcefileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
    using (FileStream output = new FileStream(destPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite, buffer.Length))
    {
        if (skipByModifiedDate && destFileInfo != null)
        {
            if (destFileInfo.LastWriteTime < sourcefileInfo.LastWriteTime)
            {
                input.CopyTo(output, buffer.Length);
                destFileInfo.LastWriteTime = sourcefileInfo.LastWriteTime;
                return $"Replaced: '{sourcePath}'";
            }
            else
            {
                return $"NOT replaced (more recent or same file): '{sourcePath}'";
            }
        }
        else
        {
            input.CopyTo(output, buffer.Length);
            destFileInfo = new FileInfo(destPath);
            destFileInfo.LastWriteTime = sourcefileInfo.LastWriteTime;
            return $"New File: '{sourcePath}'";
        }
    }
}
Rui Caramalho
  • 455
  • 8
  • 16
0

For UWP and Winui 3 (WindowsAppSdk) using Async API:

public async Task CopyAsync(StorageFolder source, StorageFolder dest)
{
    foreach (var item in await source.GetItemsAsync())

        if (item is StorageFile file)
            await file.CopyAsync(dest);

        else if (item is StorageFolder folder)
            await CopyAsync(folder, await dest.CreateFolderAsync(folder.Name, CreationCollisionOption.OpenIfExists));
}
Rafi Henig
  • 5,950
  • 2
  • 16
  • 36
0
public static class Extensions
{
    public static void Copy(this DirectoryInfo self, DirectoryInfo destination, bool recursively)
    {
        foreach (var file in self.GetFiles())
        {
            file.CopyTo(Path.Combine(destination.FullName, file.Name));
        }

        if (recursively)
        {
            foreach (var directory in self.GetDirectories())
            {
                directory.Copy(destination.CreateSubdirectory(directory.Name), recursively);
            }
        }
    }
}

Example of use:

var sourceDirectory = new DirectoryInfo(@"C:\source");
var destinationDirectory = new DirectoryInfo(@"C:\destination");

if (destinationDirectory.Exists == false)
{
    sourceDirectory.Copy(destinationDirectory, recursively: true);
}
fdafadf
  • 809
  • 5
  • 13
0

For completeness, using relative file paths and optionally replacing files:

        public static void DuplicateDirectories(
            string sourceDirectory, 
            string targetDirectory,
            string searchPattern = "*.*",
            SearchOption searchOption = SearchOption.AllDirectories)
        {
            foreach (string dir in Directory.GetDirectories(sourceDirectory, searchPattern, searchOption)) 
            {
                var relativePath = Path.GetRelativePath(sourceDirectory, dir);
                var targetPath = Path.Combine(targetDirectory, relativePath);
                Directory.CreateDirectory(targetPath);
            }
        }

        public static void CopyFilesToDirectories(
            string sourceDirectory, 
            string targetDirectory,
            bool replaceIfExists,
            string searchPattern = "*.*",
            SearchOption searchOption = SearchOption.AllDirectories)
        {
            foreach (string filePath in Directory.GetFiles(sourceDirectory, searchPattern, searchOption))
            {
                var relativePath = Path.GetRelativePath(sourceDirectory, filePath);
                var targetPath = Path.Combine(targetDirectory, relativePath);
                File.Copy(filePath, targetPath, replaceIfExists);
            }
        }

Then:


      var sourceDirectory = @"path\to\source";
      var targetDirectory = @"path\to\target";

       DuplicateDirectories(
            sourceDirectory,
            targetDirectory);

        CopyFilesToDirectories(
            sourceDirectory,
            targetDirectory,
            true);
TJames
  • 105
  • 1
  • 1
  • 10