Skip to content

File & Folder Management

Note

Create, copy, move, rename, and delete files and folders with New-Item, Copy-Item, Move-Item, Remove-Item, and Test-Path.

Overview

Every script that touches the filesystem eventually needs to create a folder, copy a file, or clean up old data. PowerShell handles all of this with a small set of *-Item cmdlets that work identically across files, folders, and even other providers (registry, certificates). Once you know New-Item, Copy-Item, Move-Item, Remove-Item, and Test-Path, you can manage the filesystem without ever touching cmd.exe.

Basic Syntax

New-Item -Path "C:\Temp\report.txt" -ItemType File
Copy-Item -Path "C:\Source\file.txt" -Destination "C:\Backup\"
Move-Item -Path "C:\Temp\old.log" -Destination "C:\Archive\"
Remove-Item -Path "C:\Temp\file.txt"
Test-Path -Path "C:\Temp\file.txt"

Key Points

  • Test-Path before you assume a file or folder exists — never guess
  • -WhatIf previews destructive operations without running them
  • -Recurse is required to remove non-empty folders
  • -Force overwrites read-only or hidden items, and creates missing parent folders
  • All of these cmdlets work the same way against files and folders — the difference is -ItemType

Creating Files and Folders

New-Item

# Create a folder
New-Item -Path "C:\Temp\Reports" -ItemType Directory

# Create an empty file
New-Item -Path "C:\Temp\Reports\output.txt" -ItemType File

# Create a file with initial content
New-Item -Path "C:\Temp\Reports\log.txt" -ItemType File -Value "Log started $(Get-Date)`n"

# Create nested folders in one call (parent folders don't need to exist)
New-Item -Path "C:\Temp\Reports\2026\July" -ItemType Directory -Force

Output:

    Directory: C:\Temp\Reports\2026

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----          7/1/2026   9:12 AM                July

-Force Doesn't Just Overwrite

On New-Item, -Force also creates any missing parent directories. New-Item -Path "C:\A\B\C" -ItemType Directory -Force creates A, B, and C in one call — you don't need to build the path level by level.

Copying Files and Folders

Copy-Item

# Copy a single file
Copy-Item -Path "C:\Source\report.txt" -Destination "C:\Backup\report.txt"

# Copy a file to a folder (keeps original filename)
Copy-Item -Path "C:\Source\report.txt" -Destination "C:\Backup\"

# Copy an entire folder and its contents
Copy-Item -Path "C:\Source\Data" -Destination "C:\Backup\Data" -Recurse

# Overwrite existing files without prompting
Copy-Item -Path "C:\Source\*.csv" -Destination "C:\Backup\" -Force

Copy Multiple Files with Filtering

# Copy only .log files
Copy-Item -Path "C:\Source\*.log" -Destination "C:\Backup\Logs\"

# Copy files matching a pattern, recursively, preserving structure
Get-ChildItem -Path "C:\Source" -Filter "*.config" -Recurse | ForEach-Object {
    $destination = $_.FullName.Replace("C:\Source", "C:\Backup")
    $destFolder = Split-Path -Path $destination -Parent
    if (-not (Test-Path $destFolder)) {
        New-Item -Path $destFolder -ItemType Directory -Force | Out-Null
    }
    Copy-Item -Path $_.FullName -Destination $destination
}

Copy-Item -Recurse Doesn't Merge by Default

Copying a folder that already exists at the destination nests it inside instead of merging contents. Copy-Item -Path "C:\Source" -Destination "C:\Backup" -Recurse when C:\Backup already exists produces C:\Backup\Source\..., not a merge of contents. Use Robocopy (via robocopy.exe) if you need true mirror/merge semantics.

Moving and Renaming

Move-Item

# Move a file to a new folder
Move-Item -Path "C:\Temp\report.txt" -Destination "C:\Archive\"

# Move and rename in one step
Move-Item -Path "C:\Temp\report.txt" -Destination "C:\Archive\report-2026-07.txt"

# Move an entire folder
Move-Item -Path "C:\Temp\OldProject" -Destination "C:\Archive\OldProject"

# Move all files older than 30 days
Get-ChildItem -Path "C:\Temp" -File | Where-Object {
    $_.LastWriteTime -lt (Get-Date).AddDays(-30)
} | Move-Item -Destination "C:\Archive\"

Rename-Item

# Rename a file (stays in the same folder)
Rename-Item -Path "C:\Temp\old-name.txt" -NewName "new-name.txt"

# Rename a folder
Rename-Item -Path "C:\Temp\Draft" -NewName "Final"

# Bulk rename: add a prefix to every file in a folder
Get-ChildItem -Path "C:\Temp\Photos" -File | Rename-Item -NewName {
    "vacation-" + $_.Name
}

Deleting Files and Folders

Remove-Item

# Delete a single file
Remove-Item -Path "C:\Temp\report.txt"

# Delete an empty folder
Remove-Item -Path "C:\Temp\EmptyFolder"

# Delete a folder and everything inside it
Remove-Item -Path "C:\Temp\OldProject" -Recurse -Force

# Delete all .tmp files in a folder tree
Get-ChildItem -Path "C:\Temp" -Filter "*.tmp" -Recurse | Remove-Item -Force

Removing a Folder Without -Recurse

# BAD - Fails (or prompts) if the folder isn't empty
Remove-Item -Path "C:\Temp\Reports"

# GOOD - Removes the folder and everything in it
Remove-Item -Path "C:\Temp\Reports" -Recurse -Force
There's no Recycle Bin for Remove-Item — deletions are permanent. Always test with -WhatIf first on anything scripted.

Preview Before You Delete

# Shows what WOULD be deleted without deleting anything
Remove-Item -Path "C:\Temp\*.log" -WhatIf

# Prompt for confirmation on each item
Remove-Item -Path "C:\Temp\*.log" -Confirm

Output (-WhatIf):

What if: Performing the operation "Remove File" on target "C:\Temp\error.log".
What if: Performing the operation "Remove File" on target "C:\Temp\debug.log".

Checking Existence with Test-Path

# Basic existence check
if (Test-Path -Path "C:\Temp\config.json") {
    Write-Output "Config found"
}

# Check that a path exists AND is a specific type
Test-Path -Path "C:\Temp\Reports" -PathType Container   # folder
Test-Path -Path "C:\Temp\report.txt" -PathType Leaf     # file

# Guard pattern: create only if missing
$logFolder = "C:\Logs"
if (-not (Test-Path -Path $logFolder)) {
    New-Item -Path $logFolder -ItemType Directory | Out-Null
}

Test-Path Never Throws

Unlike trying to access a missing path directly, Test-Path always returns $true or $false — it never throws an error, even for paths that are completely invalid or unreachable. That makes it the safe way to guard filesystem operations before they run.

Important Parameters

Parameter Type Description Example
-Path String Source path for the operation Copy-Item -Path "C:\a.txt"
-Destination String Target path for Copy/Move Move-Item -Destination "C:\Archive\"
-ItemType String File, Directory, SymbolicLink New-Item -ItemType Directory
-Recurse Switch Include subfolders/contents Remove-Item -Recurse
-Force Switch Overwrite, bypass read-only, create parents Copy-Item -Force
-WhatIf Switch Preview without executing Remove-Item -WhatIf
-Confirm Switch Prompt before each action Remove-Item -Confirm
-PathType String For Test-Path: Leaf, Container, Any Test-Path -PathType Leaf

Common Patterns

# Pattern 1: Safe cleanup — only delete files older than N days
Get-ChildItem -Path "C:\Temp" -File -Recurse |
    Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-7) } |
    Remove-Item -Force -WhatIf   # remove -WhatIf once you trust the output

# Pattern 2: Ensure a folder exists before writing to it
$outputPath = "C:\Reports\2026\July"
if (-not (Test-Path $outputPath)) {
    New-Item -Path $outputPath -ItemType Directory -Force | Out-Null
}

# Pattern 3: Timestamped backup copy before overwriting
$file = "C:\Config\settings.json"
if (Test-Path $file) {
    $timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
    Copy-Item -Path $file -Destination "$file.$timestamp.bak"
}

Real-World Examples

Example: Nightly Log Rotation

Scenario: Move logs older than 1 day into a dated archive folder, then delete anything older than 90 days.

$logSource  = "C:\App\Logs"
$archiveRoot = "C:\App\Archive"
$today = Get-Date -Format "yyyy-MM-dd"

# Archive yesterday's logs
$archiveFolder = Join-Path $archiveRoot $today
if (-not (Test-Path $archiveFolder)) {
    New-Item -Path $archiveFolder -ItemType Directory -Force | Out-Null
}

Get-ChildItem -Path $logSource -Filter "*.log" | Where-Object {
    $_.LastWriteTime -lt (Get-Date).Date
} | Move-Item -Destination $archiveFolder

# Purge archives older than 90 days
Get-ChildItem -Path $archiveRoot -Directory | Where-Object {
    $_.CreationTime -lt (Get-Date).AddDays(-90)
} | Remove-Item -Recurse -Force

Explanation: Test-Path guards folder creation so the script is safe to re-run, Where-Object filters by date on both ends, and -Recurse -Force handles the actual purge of stale archive folders.

Tips & Tricks

Always -WhatIf Before Bulk Deletes

# Run this first to see exactly what will be removed
Remove-Item -Path "C:\Temp\*.tmp" -Recurse -WhatIf

# Then run it for real once the output looks right
Remove-Item -Path "C:\Temp\*.tmp" -Recurse -Force
-WhatIf works on every cmdlet that supports ShouldProcess — that includes New-Item, Copy-Item, Move-Item, and Remove-Item.

Wildcards Behave Differently on -Path vs -Destination

# BAD - wildcard destination folder that doesn't exist yet fails silently in some cases
Copy-Item -Path "C:\Source\*.txt" -Destination "C:\Backup\*"

# GOOD - point -Destination at an actual existing folder
Copy-Item -Path "C:\Source\*.txt" -Destination "C:\Backup\"
Wildcards only make sense on -Path. -Destination should be a real folder or file path.

Additional Resources