Duplicating files and folder structures without altering the source data is handled by the Copy-Item cmdlet (aliases: copy, cp, ci). Unlike Move-Item, which removes the source, copying creates an exact replica of filesystem objects, including content streams and basic metadata, while leaving the original untouched. Understanding how path resolution operates, how directory trees are duplicated recursively with -Recurse, and how to properly apply -Include and -Exclude filters helps ensure accurate and efficient file management.
The Copy-Item Cmdlet
Basic Syntax
Copy-Item -Path <String[]> -Destination <String> [-Recurse] [-Force] [-PassThru] [-Include <String[]>] [-Exclude <String[]>] [-WhatIf] [-Confirm]Copying a Single File
If the destination path points to an existing directory, the file is copied into that directory with its original name. If the destination path includes a new file name, the file is duplicated and renamed in a single step.
Copy-Item -Path "C:\Templates\report-template.docx" -Destination "C:\Projects\Q1\report.docx"Verify the copy:
Test-Path "C:\Templates\report-template.docx"
Test-Path "C:\Projects\Q1\report.docx"True
TrueNotice both paths return True because, unlike Move-Item, the original file remains at the source location after copying.
Where:
-Pathspecifies the source file.-Destinationspecifies the target path, either a full file path to rename during copy or an existing folder to keep the original name.-PassThruinstructsCopy-Itemto emit the resultingFileInfoobject to the pipeline (by default,Copy-Itemis silent).-Forceallows overwriting read-only files in the destination.
Copying Without Renaming
Copy-Item -Path "C:\Templates\report-template.docx" -Destination "C:\Projects\Q1\"A more complete script form:
$SourcePath = "$env:TEMP\appsettings.json"
$TargetDir = "$env:TEMP\Backup"
if (-not (Test-Path -Path $TargetDir -PathType Container)) {
$null = New-Item -ItemType Directory -Path $TargetDir -Force
}
Copy-Item -Path $SourcePath -Destination $TargetDir -PassThrupwsh ./copy-single-file.ps1 Directory: C:\Users\Tom\AppData\Local\Temp\Backup
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 10/24/2024 11:45 PM 1024 appsettings.jsonRecursive Folder Copying (-Recurse)
By default, Copy-Item on a folder copies only the folder itself (empty, without contents). The -Recurse switch copies the folder and everything inside it, including all subfolders and their contents.
Copy-Item -Path "C:\Projects\WebApp" -Destination "D:\Backups\WebApp" -RecurseWhere -Recurse ensures every file and subfolder within C:\Projects\WebApp is duplicated into D:\Backups\WebApp.
Verify the copy:
Get-ChildItem -Path "D:\Backups\WebApp" -Recurse | Measure-ObjectCount : 20
Average :
Sum :
Maximum :
Minimum :
Property :The Trailing Asterisk Path Rule
A common point of confusion when copying folders with -Recurse is whether the source path includes a trailing wildcard (*):
Source Structure:
C:\SourceFolder\
├── fileA.txt
└── SubDir\
└── fileB.txt
Scenario 1: Copy-Item -Path "C:\SourceFolder" -Destination "C:\Target" -Recurse
Result:
C:\Target\SourceFolder\ <-- Source folder itself is copied INTO Target
├── fileA.txt
└── SubDir\...
Scenario 2: Copy-Item -Path "C:\SourceFolder\*" -Destination "C:\Target" -Recurse
Result:
C:\Target\ <-- Only the CONTENTS are copied INTO Target
├── fileA.txt
└── SubDir\...- To copy the folder and its contents (creating
Target\SourceFolder), omit the wildcard:-Path "C:\SourceFolder". - To copy only the contents directly into the target directory, append
\*:-Path "C:\SourceFolder\*".
$SourceFolder = "$env:TEMP\SourceData"
$TargetFolder = "$env:TEMP\TargetData"
Copy-Item -Path "$SourceFolder\*" -Destination $TargetFolder -Recurse -Force
Get-ChildItem -Path $TargetFolder -Recurse | Select-Object -Property Name, Lengthpwsh ./copy-recursive-folder.ps1Name Length
---- ------
SubDir 0
data.csv 45120
nested.log 2048Where:
"$SourceFolder\*"selects all items insideSourceData.-Recurserecursively traverses all subfolders and copies their contents.-Forceallows overwriting existing files in the destination.
Filtering Copies with -Include and -Exclude
The -Include and -Exclude parameters filter which items are copied based on wildcard patterns. They are especially useful in combination with -Recurse for large directory trees.
The -Include Wildcard Requirement
The most critical rule when using the -Include parameter with the FileSystem provider is that the -Path must end with a trailing wildcard (*).
If -Path points to a container directly (e.g., -Path "C:\Source"), the FileSystem provider checks whether the directory container itself matches the -Include filter, finds that it does not, and copies nothing.
$SourceDir = "$env:TEMP\ProjectLogs"
$BackupDir = "$env:TEMP\FilteredBackup"
Copy-Item -Path "$SourceDir\*" `
-Destination $BackupDir `
-Include "*.log", "*.txt" `
-Exclude "*temp*", "*debug*" `
-Recurse `
-Forcepwsh ./copy-filtered-files.ps1
Get-ChildItem -Path $BackupDir -Recurse | Select-Object -Property NameName
----
application.log
production.txtWhere:
"$SourceDir\*"targets all child items for pattern evaluation.-Include "*.log", "*.txt"restricts the operation to log and text files.-Exclude "*temp*", "*debug*"filters out any file whose name contains "temp" or "debug".-Recurseapplies the include/exclude filters throughout the entire directory tree.
-Include Alone
-Include restricts the copy to items matching a specified pattern. It requires either a wildcard in -Path, or -Recurse, to take effect.
Copy-Item -Path "C:\Projects\WebApp\*" -Destination "D:\Backups\WebApp-CSharpOnly" -Recurse -Include "*.cs"Where -Include "*.cs" copies only files ending in .cs, skipping everything else, throughout the recursive traversal.
-Exclude Alone
-Exclude does the opposite, copying everything except items matching the specified pattern.
Copy-Item -Path "C:\Projects\WebApp\*" -Destination "D:\Backups\WebApp-NoLogs" -Recurse -Exclude "*.log"Where -Exclude "*.log" copies the entire folder structure while skipping any .log files encountered along the way.
Combining -Include and -Exclude
Copy-Item -Path "C:\Projects\WebApp\*" -Destination "D:\Backups\Source" -Recurse -Include "*.cs", "*.json" -Exclude "*Test*"Where:
-Include "*.cs", "*.json"accepts an array of patterns, copying files matching either extension.-Exclude "*Test*"further filters out any file with "Test" anywhere in its name, even if it matches an include pattern.
Practical Examples
Selective Project Backup
param(
[string]$SourcePath = "C:\Projects\WebApp",
[string]$BackupPath = "D:\Backups\WebApp-$(Get-Date -Format 'yyyyMMdd')"
)
if (-not (Test-Path $BackupPath)) {
New-Item -ItemType Directory -Path $BackupPath | Out-Null
}
Copy-Item -Path "$SourcePath\*" -Destination $BackupPath -Recurse -Exclude "*.tmp", "bin", "obj"
Write-Output "Backup completed: $BackupPath"Backup completed: D:\Backups\WebApp-20240314Where:
- The destination folder includes a dynamic timestamp, keeping each backup run in its own dated folder.
-Exclude "*.tmp", "bin", "obj"skips temporary files and common build output folders that don't need to be backed up.
Automated Configuration Backup Engine
The following script backs up configuration files from an application folder, filters out temporary and cache files, duplicates directory hierarchies using -Recurse, and emits a structured audit summary of all copied files.
param (
[string]$SourceRoot = "$env:TEMP\AppRoot",
[string]$BackupRoot = "$env:TEMP\AppBackups"
)
if (-not (Test-Path -Path $SourceRoot -PathType Container)) {
Write-Error "Source directory '$SourceRoot' does not exist."
exit 1
}
$Timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$TargetDestination = Join-Path -Path $BackupRoot -ChildPath "Backup_$Timestamp"
$null = New-Item -ItemType Directory -Path $TargetDestination -Force
Write-Output "Initiating recursive backup to: $TargetDestination"
$CopiedItems = Copy-Item -Path "$SourceRoot\*" `
-Destination $TargetDestination `
-Include "*.json", "*.xml", "*.conf" `
-Exclude "*cache*", "*tmp*" `
-Recurse `
-PassThru `
-Force
$AuditReport = foreach ($item in $CopiedItems) {
if (-not $item.PSIsContainer) {
[PSCustomObject]@{
FileName = $item.Name
SizeBytes = $item.Length
Destination = $item.FullName
CopiedAt = (Get-Date -Format "HH:mm:ss")
}
}
}
$AuditReport | Format-Table -AutoSizeWhere:
Join-Pathconstructs a unique, timestamped backup directory name.Copy-Item -Path "$SourceRoot\*"selects all child items for evaluation.-Includecaptures.json,.xml, and.confconfiguration files.-Exclude "*cache*", "*tmp*"filters out cache files and temporary data.-PassThrustreams the copiedFileInfoobjects back into$CopiedItems.if (-not $item.PSIsContainer)filters the summary table to include only files, skipping directory container objects.
Execute the script from the console:
pwsh ./backup-application-configs.ps1Initiating recursive backup to: C:\Users\Tom\AppData\Local\Temp\AppBackups\Backup_20241024_235015
FileName SizeBytes Destination CopiedAt
-------- --------- ----------- --------
appsettings.json 1024 C:\Users\Tom\AppData\Local\Temp\AppBackups\Backup_... 23:50:16
web.xml 4096 C:\Users\Tom\AppData\Local\Temp\AppBackups\Backup_... 23:50:16
providers.conf 512 C:\Users\Tom\AppData\Local\Temp\AppBackups\Backup_... 23:50:16Quick Reference
| Task | Command |
|---|---|
| Copy a single file | Copy-Item -Path "C:\Temp\file.txt" -Destination "C:\Archive\file.txt" |
| Copy into an existing folder | Copy-Item -Path "C:\Temp\file.txt" -Destination "C:\Archive\" |
| Copy and rename | Copy-Item -Path "C:\Temp\old.txt" -Destination "C:\Archive\new.txt" |
| Copy a folder recursively | Copy-Item -Path "C:\Source" -Destination "D:\Backup" -Recurse |
| Copy folder contents only | Copy-Item -Path "C:\Source\*" -Destination "D:\Backup" -Recurse |
| Copy with include filter | Copy-Item -Path "C:\Source\*" -Destination "D:\Dest" -Recurse -Include "*.cs" |
| Copy with exclude filter | Copy-Item -Path "C:\Source\*" -Destination "D:\Dest" -Recurse -Exclude "*.log" |
| Overwrite read-only files | Copy-Item -Path ... -Destination ... -Force |
| Emit copied object info | Copy-Item -Path ... -Destination ... -PassThru |
| Preview a copy | Copy-Item -Path ... -Destination ... -WhatIf |