Copy multiple files from one location to another using powershell script

Solution 1:

It depends on what your goals are. Separate mkdir and Copy-Item is "conceptually" the "simplest" and maintainable by any beginner PowerShell scripter. If you want to make it more flexible, you can use loops and variables, but that may require more knowledge of PowerShell.

PowerShell Tricks

But to answer your question, yes, there are some optimizations and PowerShell tricks we can take advantage of. The first is to take advantage of arrays of strings, the ForEach-Object, the Subexpression operator $(), and $_ variable to create our paths with "A", "B", and "C"

"A","B","C" | ForEach-Object { "path\$($_)\file.txt" }

Flexible All Variables Method

If you want the more flexible methods, I tend to put all the path information into variables, separate out my shared Source, Destination, and File components. We still do "data" and "compute" separately just so that it is conceptually easier to maintain:

$src = "public\Service\ABC\Hosts"
$dest = "Service\ABC\Hosts"

$path1 = "Infrastructure\compute\parameters"
$path2 = "Infrastructure\data\parameters"

$srcFile = "parameters.int.json"
$destFile = "parameters.xyz.json"

"A","B","C" | ForEach-Object {
     #Compute 
     New-Item "$dest\$($_)\$path1\" -ItemType Directory -Force

     Copy-Item "$src\$($_)\$path1\$srcFile" -Destination  "$dest\$($_)\$path1\$destFile"

     #Data
     New-Item "$dest\$($_)\$path2\" -ItemType Directory -Force

     Copy-Item "$src\$($_)\$path2\$srcFile" -Destination  "$dest\$($_)\$path1\$destFile"
}

PowerShell Trick #2

@AbrahamZinala mentioned using Copy-Item with the -Force parameter to create the directory. Unfortunately it doesn't work. The -Force parameter is used for read-only files or aliases. The directories only get created automatically if you are copying entire directories with -Recurse. Since we are copying individual files, we have to pre-create the directories we are copying to.

The next PowerShell trick is combining Copy-Item with New-Item. First, we put a bracketed New-Item in the Copy-Item -Destination parameter. When New-Item creates the directory (and -Force handles if it already exists), it outputs the object it creates, e.g. the new directory. Copy-Item can take this directory and use it. As long as you don't need to rename the file, this is a neat trick.

Copy-Item "path\file.txt" -Destination (New-Item "path2\newDir\" -ItemType Directory -Force)

If we do have to rename the file, then we simply wrap the entire New-Item in quotes and use the Subexpression operator $() for evaluation.

Copy-Item "path\file.txt" -Destination "$(New-Item "path2\newDir\" -ItemType Directory -Force)\newFileName.txt"

Optimization 1

Using Trick #2, we can reduce the script down a bit, but still pretty maintainable and understandable with a little more PowerShell knowledge:

$src = "public\Service\ABC\Hosts"
$dest = "Service\ABC\Hosts"

$path1 = "Infrastructure\compute\parameters"
$path2 = "Infrastructure\data\parameters"

$srcFile = "parameters.int.json"
$destFile = "parameters.xyz.json"

"A","B","C" | ForEach-Object {
     #Compute 
     Copy-Item "$src\$($_)\$path1\$srcFile" -Destination "$(New-Item "$dest\$($_)\$path1\" -ItemType Directory -Force)\$destFile"

     #Data
     Copy-Item "$src\$($_)\$path2\$srcFile" -Destination "$(New-Item "$dest\$($_)\$path2\" -ItemType Directory -Force)\$destFile"
}

The 2 Liner

If you are looking for simply less, this next script is a bit less maintainable, a little harder to read but if we forego variables, and also using Trick #2, we can reduce the script down to 2 lines:

#Compute
"A","B","C" | ForEach-Object { Copy-Item "public\Service\ABC\Hosts\$($_)\Infrastructure\compute\parameters\parameters.int.json" -Destination "$(New-Item "public\Service\ABC\Hosts\$($_)\Infrastructure\compute\parameters\" -ItemType Directory -Force)\parameters.xyz.json" }

#Data
"A","B","C" | ForEach-Object { Copy-Item "public\Service\ABC\Hosts\$($_)\Infrastructure\data\parameters\parameters.int.json" -Destination "$(New-Item "public\Service\ABC\Hosts\$($_)\Infrastructure\data\parameters\" -ItemType Directory -Force)\parameters.xyz.json" }

The 1 Liner

This next script is even less maintainable, but if the compute and data paths are the same, doing 2 ForEach-Object loops where we prebuild the file path, we can reduce the script down to 1 line:

"A","B","C" | ForEach-Object { "$($_)\Infrastructure\compute\parameters", "$($_)\Infrastructure\data\parameters" | ForEach-Object { Copy-Item "public\Service\ABC\Hosts\$($_)\parameters.int.json" -Destination "$(New-Item "public\Service\ABC\Hosts\$($_)\" -ItemType Directory -Force)\parameters.xyz.json" } }

The CSV

If you have files all over the place, or differing names, you could also put all the files in a CSV e.g.:

InputFile.csv

Path, Destination
"public\Service\ABC\Hosts\A\Infrastructure\compute\parameters\parameters.int.json", "Service\ABC\Hosts\A\Infrastructure\compute\parameters\parameters.xyz.json"
"public\Service\ABC\Hosts\A\Infrastructure\data\parameters\parameters.int.json", "Service\ABC\Hosts\A\Infrastructure\data\parameters\parameters.xyz.json"
"public\Service\ABC\Hosts\B\Infrastructure\compute\parameters\parameters.int.json", "Service\ABC\Hosts\B\Infrastructure\compute\parameters\parameters.xyz.json"
"public\Service\ABC\Hosts\B\Infrastructure\data\parameters\parameters.int.json", "Service\ABC\Hosts\B\Infrastructure\data\parameters\parameters.xyz.json"
"public\Service\ABC\Hosts\C\Infrastructure\compute\parameters\parameters.int.json", "Service\ABC\Hosts\C\Infrastructure\compute\parameters\parameters.xyz.json"
"public\Service\ABC\Hosts\C\Infrastructure\data\parameters\parameters.int.json", "Service\ABC\Hosts\C\Infrastructure\data\parameters\parameters.xyz.json"

PowerShell

$files = Import-CSV -Path "InputFile.csv"
$files | ForEach-Object { Copy-Item $_.Path -Destination $_.Destination }