Skip to content

PowerShell Copying Files

How to use PowerShell's Copy-Item cmdlet (aliases: copy, cp, ci) to duplicate files and folders recursively, apply include/exclude filters, and handle metadata while preserving source data.

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
True

Notice both paths return True because, unlike Move-Item, the original file remains at the source location after copying.

Where:

  • -Path specifies the source file.
  • -Destination specifies the target path, either a full file path to rename during copy or an existing folder to keep the original name.
  • -PassThru instructs Copy-Item to emit the resulting FileInfo object to the pipeline (by default, Copy-Item is silent).
  • -Force allows 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:

copy-single-file.ps1powershell
$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 -PassThru
Consolepowershell
pwsh ./copy-single-file.ps1
Outputplaintext
    Directory: C:\Users\Tom\AppData\Local\Temp\Backup

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---           10/24/2024  11:45 PM           1024 appsettings.json

Recursive 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" -Recurse

Where -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-Object
Count    : 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\*".
copy-recursive-folder.ps1powershell
$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, Length
Consolepowershell
pwsh ./copy-recursive-folder.ps1
Outputplaintext
Name         Length
----         ------
SubDir            0
data.csv      45120
nested.log     2048

Where:

  • "$SourceFolder\*" selects all items inside SourceData.
  • -Recurse recursively traverses all subfolders and copies their contents.
  • -Force allows 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.

copy-filtered-files.ps1powershell
$SourceDir = "$env:TEMP\ProjectLogs"
$BackupDir = "$env:TEMP\FilteredBackup"


Copy-Item -Path "$SourceDir\*" `
          -Destination $BackupDir `
          -Include "*.log", "*.txt" `
          -Exclude "*temp*", "*debug*" `
          -Recurse `
          -Force
Consolepowershell
pwsh ./copy-filtered-files.ps1
Get-ChildItem -Path $BackupDir -Recurse | Select-Object -Property Name
Outputplaintext
Name
----
application.log
production.txt

Where:

  • "$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".
  • -Recurse applies 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-20240314

Where:

  • 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 -AutoSize

Where:

  • Join-Path constructs a unique, timestamped backup directory name.
  • Copy-Item -Path "$SourceRoot\*" selects all child items for evaluation.
  • -Include captures .json, .xml, and .conf configuration files.
  • -Exclude "*cache*", "*tmp*" filters out cache files and temporary data.
  • -PassThru streams the copied FileInfo objects 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.ps1
Initiating 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:16

Quick 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
Navigation