Flatten Directory

It’s easy enough to copy files with File Explorer, robocopy or batch script. But when you need to flatten all directories into one while renaming duplicates, the task is a little bit trickier.

I couldn’t find any help for this in the usual places. The best suggestion I found was to search the directory for * and copy the resulting list of files manually. This works well, you can even have Windows rename duplicate files, but I really needed to automate this.

PowerShell is a scripting language for task automation and can be useful for dealing with these types of problems from Windows. The following PowerShell script renames the files using their subdirectory path, allowing duplicate filenames to co-exist and I’ve provided an option to exclude a list of directories as well.

To run this script, update the following:

  • $source: full path of the directory to flatten
  • $dest: full path of the flattened, output directory
  • $exclude: comma-delimited list of directory names to ignore
$source = [path_to_source_dir]
$dest = [path_to_dest_dir]
$exclude = [x], [y], [z]
mkdir $dest -Force
@(gi $source) + @(gci -Path $source -Recurse -Directory -Exclude $exclude) |
gci -File |
ForEach {
  $filename = $_.FullName.substring($source.Length + 1)
  $filename = $filename -replace \\, _
  cp $_.FullName $dest\$filename
}

This script would convert the following source directory:

  • a.txt
  • b.txt
  • dir
    • a.txt
    • b.txt

Into this flat, destination directory:

  • a.txt
  • b.txt
  • dir_a.txt
  • dir_b.txt

Categories:

2 Responses

  1. This is really great script! Really useful.
    The only issue is that if there is a file called “a.jpg” and another file “a.dwg”. The script seems to identify both as same file. And so it appends the folder path to both the files. Although they are different files.
    Is there a way to overcome this?
    Thanks!

  2. @Tushar thanks for the great feedback. It will depend on your specific use case. If you don’t want any renaming at all and you know that your filenames are always going to be unique, you can really simplify the for-each loop:

    ForEach {
    cp $_.FullName "$dest\$_.FullName"
    }

    Unfortunately, that’s rarely the case and you probably need something to prevent naming conflicts. The solution provided is not perfect e.g., C:\dir\a_file.txt and C:\dir_a\file.txt would end up in conflict.

    A more robust idea might be to introduce a UUID, or unique identifier to each file that gets copied. For example, we could rename the output files using a pattern like: [filename].[UUID].[extension]. To do so, modify the for-each loop to:

    ForEach {
    $uuid = [guid]::NewGuid().ToString();
    $filename = $_.Basename + "." + $uuid + "." + $_.Extension;
    cp $_.FullName "$dest\$filename"
    }

    The filenames will be a lot longer, but the UUID should avoid any naming conflicts. Again, the solution you go with will probably depend on what you’re trying to do. If you have a chance to work on this, you’re welcome to share your solution!

Leave a Reply

Your email address will not be published. Required fields are marked *