PowerShellのCopy-Itemは、ファイルやフォルダーを複製するコマンドです。この記事では、単一ファイル、名前変更、複数ファイル、フォルダー全体、フォルダーの中身という5つの例を紹介します。元のファイルを削除する操作ではありませんが、コピー先の同名ファイルを上書きする可能性があるため、作業用の新しい保存先で確認しましょう。
基本構文と、実行前の確認
Copy-Item -LiteralPath 'コピー元の完全パス' -Destination 'コピー先の完全パス' -WhatIf
上の例は構文説明用です。実際のパスに置き換えて使います。-LiteralPathはコピー元を文字どおり扱うため、名前に角括弧などが含まれる場合にも適しています。ワイルドカードを使いたいときは-Pathを使います。-WhatIfは予定される操作の確認であり、容量・アクセス権・実行中のファイル変更まで検証する実行テストではありません。
以下はWindowsのファイルシステム向けです。PowerShellの版は$PSVersionTable.PSVersionで確認できます。本記事では例の実行検証は行っていないため、重要データではなく検証用ファイルから試してください。保存先はコピー元の外に置き、ほかの処理が書き込まない場所を使います。
準備:例ごとに使える新しい作業フォルダーを用意する
コピー元は、あらかじめ用意したC:\Lab\CopySourceとします。中にmonthly-report.csv、notes.txt、Projectフォルダーがある想定です。実際の環境に合わせて変更してください。保存先の親C:\Lab\CopyDestinationも先に用意し、空き容量とアクセス権を確認します。
$sourceRoot = 'C:\Lab\CopySource'
$destinationRoot = 'C:\Lab\CopyDestination'
if (-not (Test-Path -LiteralPath $sourceRoot -PathType Container)) {
throw 'コピー元フォルダーがありません'
}
if (-not (Test-Path -LiteralPath $destinationRoot -PathType Container)) {
throw 'コピー先の親フォルダーがありません'
}
$run = Join-Path $destinationRoot ('copy-example-' + [guid]::NewGuid().ToString('N'))
if (Test-Path -LiteralPath $run) { throw '保存先がすでに存在します' }
New-Item -ItemType Directory -Path $run -ErrorAction Stop | Out-Null
$run
この準備だけは空のフォルダーを作成しますが、ファイルはコピーしません。以下の例は同じPowerShellで変数を引き継ぐ想定です。例を最初からやり直すときは準備もやり直し、以前の結果を消さず新しい作業フォルダーを使います。
例1:単一ファイルを同じ名前でコピーする
$source = Join-Path $sourceRoot 'monthly-report.csv'
$target = Join-Path $run 'monthly-report.csv'
if (-not (Test-Path -LiteralPath $source -PathType Leaf)) { throw 'コピー元がありません' }
if (Test-Path -LiteralPath $target) { throw 'コピー先がすでに存在します' }
Copy-Item -LiteralPath $source -Destination $target -WhatIf -ErrorAction Stop
表示されたコピー元とコピー先を確認し、実行するときだけ最後の行の-WhatIfを外します。コピー完了後もmonthly-report.csvは元の場所に残ります。コピー先がすでにある場合は停止し、必要なファイルを上書きしない方針の例です。
例2:別名を付けてコピーする
$source = Join-Path $sourceRoot 'monthly-report.csv'
$target = Join-Path $run 'monthly-report-review.csv'
if (Test-Path -LiteralPath $target) { throw 'コピー先がすでに存在します' }
Copy-Item -LiteralPath $source -Destination $target -WhatIf -ErrorAction Stop
コピー先に新しいファイル名まで指定すると、元の名前を変更せず別名の複製を作れます。拡張子を変えてもファイル形式の変換にはならないため、CSVを.xlsxへ書き換えるような使い方はしません。
例3:選んだ複数ファイルをコピーする
$names = 'monthly-report.csv', 'notes.txt'
$multiple = Join-Path $run 'selected-files'
if (Test-Path -LiteralPath $multiple) { throw '保存先がすでに存在します' }
foreach ($name in $names) {
if (-not (Test-Path -LiteralPath (Join-Path $sourceRoot $name) -PathType Leaf)) {
throw "コピー元がありません: $name"
}
}
New-Item -ItemType Directory -Path $multiple -ErrorAction Stop | Out-Null
foreach ($name in $names) {
Copy-Item -LiteralPath (Join-Path $sourceRoot $name) -Destination $multiple -WhatIf -ErrorAction Stop
}
この例はコピー対象を名前で明示しています。確認後にコピーを実行する場合は、最後のforeachのCopy-Itemから-WhatIfを外して、そのforeach部分だけを実行します。フォルダー作成部分を含めて再実行すると、既存チェックで停止します。
複数ファイルのコピーは一つのトランザクションではありません。途中で失敗した場合、そこまでに作られたコピーは残ることがあります。例外が出たから「何もコピーされていない」と考えず、コピー先を確認してください。
例4:フォルダー全体を新しい名前でコピーする
$project = Join-Path $sourceRoot 'Project'
$target = Join-Path $run 'Project-copy'
if (-not (Test-Path -LiteralPath $project -PathType Container)) { throw 'Projectがありません' }
if (Test-Path -LiteralPath $target) { throw 'コピー先がすでに存在します' }
Copy-Item -LiteralPath $project -Destination $target -Recurse -WhatIf -ErrorAction Stop
コピー先Project-copyが存在しないことを前提に、Projectの内容をそのフォルダー名で複製します。実行後はProject-copyの下に期待したサブフォルダーがあるか確認します。隠し項目、リンク、アクセスできない項目を含むツリーでは、事前に対象と必要な扱いを確認してください。
例5:フォルダー名を増やさず、中身を既存フォルダーへコピーする
$project = Join-Path $sourceRoot 'Project'
$contents = Join-Path $run 'project-contents'
if (-not (Test-Path -LiteralPath $project -PathType Container)) { throw 'Projectがありません' }
if (Test-Path -LiteralPath $contents) { throw '保存先がすでに存在します' }
New-Item -ItemType Directory -Path $contents -ErrorAction Stop | Out-Null
Copy-Item -Path (Join-Path $project '*') -Destination $contents -Recurse -WhatIf -ErrorAction Stop
コピー元末尾の*で中身を指定します。この例のコピー元にはワイルドカードとして解釈される名前がない前提です。コピー先は先にディレクトリとして作成し、ファイル名として解釈される曖昧さを避けています。確認後は最後のCopy-Itemだけを-WhatIfなしで実行します。
上書き・Force・WhatIfで誤解しやすい点
-Forceを付けなければ上書きされない、という意味ではありません。通常の同名ファイルは上書き対象になり得ます。既存ファイルを残す必要があるなら、新しい保存先を用意し、衝突を確認します。-Forceもアクセス権を無視できる万能な指定ではありません。
Test-Pathで確認してからコピーする間に、別の処理がコピー先を作る可能性は残ります。事前チェックは同時アクセスに対する原子的な上書き防止ではありません。共有の重要な保存先では排他制御や専用の運用を検討してください。
コピー中に更新されるファイルも注意が必要です。データベースや稼働中アプリのファイルは、単純なコピーでは整合性のあるバックアップにならないことがあります。アプリが提供するバックアップ手段を優先します。
コピー結果をファイルサイズとSHA256で確認する
単一ファイルの内容を比較する例です。コピー元が書き換わっていない状態で確認します。SHA256が一致することは内容の照合に役立ちますが、所有者・アクセス権・すべてのメタデータや、フォルダー全体の完全性を保証するものではありません。
$source = Join-Path $sourceRoot 'monthly-report.csv'
$target = Join-Path $run 'monthly-report.csv'
$sourceItem = Get-Item -LiteralPath $source -ErrorAction Stop
$targetItem = Get-Item -LiteralPath $target -ErrorAction Stop
$sourceHash = (Get-FileHash -LiteralPath $source -Algorithm SHA256 -ErrorAction Stop).Hash
$targetHash = (Get-FileHash -LiteralPath $target -Algorithm SHA256 -ErrorAction Stop).Hash
[pscustomobject]@{
Source = $sourceItem.FullName
Destination = $targetItem.FullName
SameLength = ($sourceItem.Length -eq $targetItem.Length)
SameSha256 = ($sourceHash -eq $targetHash)
}
Copy-Itemは通常、成功しても結果オブジェクトを表示しません。何も表示されないことだけで失敗とは判断せず、実際のコピー先を確認します。逆に-WhatIfの表示が出てもコピーは実施されていません。
前の高度な検証方法を使う場合と、削除の扱い
重要な単一ファイルでは、コピー前の完全パス・サイズ・SHA256を記録し、実行直前にも確認して、新しい保存先へコピー後に同じ値と照合する方法が使えます。ただし、確認の間に内容が変わる競合まで防げるわけではありません。検証の失敗時はコピー元とコピー先を残して調査します。
コピーの説明に後片付けの削除コマンドを続けて実行する必要はありません。不要な検証用コピーを削除するときは、今回の作業フォルダーを改めて確認し、必要な元データや既存バックアップと取り違えないよう別作業で扱います。
公式資料とまとめ
5つの例では、コピー元を明示し、コピー先がファイルかフォルダーかをはっきりさせ、-WhatIfで確認した後に実行します。既存ファイルへの上書きと、途中失敗時の残りを考慮し、重要な結果は内容まで照合してください。



コメント