Chapter 07Lesson 03~135 minutes

Create, Copy, Move, Remove, and Inspect Files and Directories Safely

Operate on a bounded filesystem workspace with item cmdlets, provider-aware filtering, recursive-scope controls, idempotent state checks, and WhatIf previews before destructive changes.

Learning objectives

  • Use Get-Item and Get-ChildItem to inspect filesystem state before changing it.
  • Create directories/files idempotently with New-Item and explicit desired-state checks.
  • Differentiate Copy-Item, Move-Item, Rename-Item, and Remove-Item by the state transition each expresses.
  • Control recursive scope and understand provider/cmdlet semantics for Filter, Include, and Exclude.
  • Use -WhatIf on destructive ShouldProcess operations and understand what the preview does not guarantee.
  • Keep platform-specific link and alternate-data-stream behavior separate from portable baseline automation.

1. Safe filesystem automation starts with a disposable workspace and an intended end state

File commands are easy to demonstrate destructively: create random folders, copy unknown trees, then delete them. Production automation should instead define an intended state and operate in a bounded workspace. This lesson uses a directory under the user temporary path so every mutation is explicit and cleanup is safe.

$root = Join-Path ([System.IO.Path]::GetTempPath()) 'ps-files-lab'
$incoming = Join-Path $root 'incoming'
$archive = Join-Path $root 'archive'

foreach ($dir in $root, $incoming, $archive) {
    if (-not (Test-Path -LiteralPath $dir -PathType Container)) {
        New-Item -ItemType Directory -Path $dir | Out-Null
    }
}

The condition makes creation idempotent for this simple case: running the setup again converges on the same directories instead of treating existing state as an error.

2. Inspect before mutating: Get-Item for one path, Get-ChildItem for children

Get-Item -LiteralPath $root |
    Select-Object FullName, PSIsContainer, CreationTime

Get-ChildItem -LiteralPath $root |
    Select-Object Name, FullName, PSIsContainer

Inspection returns objects, not preformatted strings. That means you can filter on PSIsContainer, sort by timestamps, or export metadata without parsing the visible table.

3. New-Item creates items, and -Force does not mean “ignore every problem”

New-Item asks the active provider to create an item. For the FileSystem provider, -ItemType Directory and -ItemType File are common. -Force changes provider-specific behavior—such as allowing some overwrite or hidden-item scenarios—but it does not bypass OS permissions or make an unsafe operation automatically safe.

$source = Join-Path $incoming 'service.conf'
if (-not (Test-Path -LiteralPath $source)) {
    New-Item -ItemType File -Path $source | Out-Null
    Set-Content -LiteralPath $source -Value 'port=8080'
}

The code checks the desired state first, then creates only when needed. Later chapters will formalize idempotent design more deeply.

4. Copy, move, and rename express different state transitions

CmdletIntentQuestion before running
Copy-ItemCreate another item while keeping the sourceShould destination already exist, and what does Force mean here?
Move-ItemRelocate an itemIs source/destination on a compatible provider/filesystem?
Rename-ItemChange the leaf name in placeIs the new name a name rather than an arbitrary destination path?
$copy = Join-Path $archive 'service.conf'
Copy-Item -LiteralPath $source -Destination $copy -Force

$renamed = Join-Path $archive 'service.staging.conf'
Rename-Item -LiteralPath $copy -NewName 'service.staging.conf'

Get-Item -LiteralPath $renamed |
    Select-Object Name, FullName, Length

Use the narrowest cmdlet that describes the transition. Clear intent improves reviews and reduces the chance of accidentally moving an item when you meant only to rename it.

5. -Recurse expands scope, so pair it with a bounded root

Recursive operations traverse descendants. That is useful for directory trees but dangerous when the root is wrong. Before any recursive mutation, log or inspect the resolved root, make sure it belongs to the workspace you expect, and prefer -WhatIf when the cmdlet supports it.

Get-ChildItem -LiteralPath $root -Recurse |
    Select-Object FullName, Length

# Preview recursive deletion; nothing is removed:
Remove-Item -LiteralPath $root -Recurse -Force -WhatIf
Operational invariant: Never let a recursive destructive path be an unvalidated empty string, $null, an unexpected root, or a broad wildcard. Compute and validate the boundary first.

6. -Filter, -Include, and -Exclude are not interchangeable

-Filter is provider-specific and, for FileSystem, is typically the first choice when the provider can restrict enumeration efficiently. -Include and -Exclude qualify PowerShell path selection and can have cmdlet/path-shape semantics that surprise beginners. Keep the root explicit and test the result set before using it in a mutation pipeline.

'a.log','b.txt','c.log' | ForEach-Object {
    Set-Content -LiteralPath (Join-Path $incoming $_) -Value "file=$_"
}

Get-ChildItem -LiteralPath $incoming -Filter '*.log' |
    Select-Object Name, Length

# Qualification example; inspect before mutating:
Get-ChildItem -Path (Join-Path $incoming '*') -Include '*.txt'

For a complex rule, it is often clearer to enumerate a bounded root and apply Where-Object to returned object properties. The tradeoff is that provider-side filtering can be more efficient for large trees.

7. -WhatIf is a preview, not a permission check or transaction

Cmdlets that support ShouldProcess commonly expose -WhatIf. It shows the action that would be attempted without performing it. This is one of the most important habits before file deletion or large-scale mutation.

$candidate = Join-Path $archive 'service.staging.conf'

Remove-Item -LiteralPath $candidate -WhatIf

# Inspect the item still exists after the preview:
Test-Path -LiteralPath $candidate

-WhatIf does not guarantee the real command will later succeed, and it does not make the operation transactional. Permissions, races, locks, and changed state can still affect the real run.

9. Lab: converge a small workspace, preview deletion, verify, and clean up

$root = Join-Path ([System.IO.Path]::GetTempPath()) 'ps-safe-file-lab'
$src = Join-Path $root 'src'
$dst = Join-Path $root 'dst'

foreach ($dir in $root, $src, $dst) {
    if (-not (Test-Path -LiteralPath $dir -PathType Container)) {
        New-Item -ItemType Directory -Path $dir | Out-Null
    }
}

$files = @{
    'api.conf' = 'port=8080'
    'worker.conf' = 'workers=4'
    'README.txt' = 'Disposable training workspace'
}

foreach ($entry in $files.GetEnumerator()) {
    $path = Join-Path $src $entry.Key
    Set-Content -LiteralPath $path -Value $entry.Value
}

Get-ChildItem -LiteralPath $src -Filter '*.conf' |
    ForEach-Object {
        Copy-Item -LiteralPath $_.FullName -Destination $dst -Force
    }

Get-ChildItem -LiteralPath $root -Recurse |
    Select-Object FullName, Length

# Preview cleanup first:
Remove-Item -LiteralPath $root -Recurse -Force -WhatIf

# Real cleanup only after verifying $root is the disposable lab path:
if ((Split-Path $root -Leaf) -eq 'ps-safe-file-lab') {
    Remove-Item -LiteralPath $root -Recurse -Force
}

Test-Path -LiteralPath $root

Verification checklist

10. Common filesystem automation mistakes

Blindly deleting before validating the root. Compute, inspect, and bound destructive paths.

Treating -Force as a universal override. It does not bypass OS authorization and has provider-specific semantics.

Assuming recursive filters behave identically across cmdlets/providers. Read help and verify the selected result set before mutation.

Skipping -WhatIf because the lab worked once. Preview becomes more valuable as path scope and environment variability increase.

11. Knowledge check

Question 1. What does idempotent directory creation try to achieve?

Question 2. Why is -Recurse operationally risky?

Question 3. What does -WhatIf guarantee?

Question 4. Why prefer -Filter for simple FileSystem patterns when available?

Question 5. Are alternate data streams a portable FileSystem feature?

12. Summary

Safe file automation starts with a bounded workspace, inspection, and an intended end state. Use item cmdlets according to the transition you mean, validate roots before recursion, treat -Force as provider-specific rather than magical, prefer provider filtering when appropriate, and preview destructive ShouldProcess operations with -WhatIf. Idempotent checks and explicit cleanup turn filesystem scripts from one-off command sequences into predictable automation.

13. Further reading

Next lesson

Read and write file content with explicit encoding and streaming decisions

Lesson 04 crosses the object-to-text boundary, covering Get-Content, Set/Add/Clear-Content, Out-File, redirection, UTF-8 defaults, large files, and staged updates.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.