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 }