Script
Timestamped folder backup
Copy a folder into a destination parent as Name-yyyyMMdd-HHmmss.
Copies an entire folder tree into a new timestamped directory, for example .ackups\docs-20260107-093000. Preview with -WhatIf.
The destination has to be outside the folder you are backing up. Otherwise the backup would try to copy itself, so the script stops before it touches anything.
.\backup-folder.ps1 -Source .\docs -DestinationRoot .\backups -WhatIf
.\backup-folder.ps1 -Source .\docs -DestinationRoot .\backups
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
-Source |
string | Yes | Folder to copy. |
-DestinationRoot |
string | Yes | Parent folder that will hold the timestamped copy. Must not be inside Source. |
Source
<#
.SYNOPSIS
Copy a folder to a timestamped backup directory.
.DESCRIPTION
Copies Source to DestinationRoot\<folder name>-yyyyMMdd-HHmmss.
Creates DestinationRoot if needed. Refuses to back a folder up into itself.
.PARAMETER Source
Folder to copy.
.PARAMETER DestinationRoot
Parent folder for the timestamped backup. Must not be inside Source.
.EXAMPLE
.\backup-folder.ps1 -Source .\docs -DestinationRoot .\backups -WhatIf
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)]
[string]$Source,
[Parameter(Mandatory)]
[string]$DestinationRoot
)
$ErrorActionPreference = 'Stop'
if (-not (Test-Path -LiteralPath $Source -PathType Container)) {
throw "Source folder not found: $Source"
}
$sourceItem = Get-Item -LiteralPath $Source
$sourceFull = $sourceItem.FullName.TrimEnd('\', '/')
$destFull = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($DestinationRoot).TrimEnd('\', '/')
$separator = [System.IO.Path]::DirectorySeparatorChar
$comparison = [System.StringComparison]::OrdinalIgnoreCase
if ($destFull.Equals($sourceFull, $comparison) -or $destFull.StartsWith($sourceFull + $separator, $comparison)) {
throw "DestinationRoot is inside Source. Pick a folder outside '$sourceFull' or the backup would copy itself."
}
# A drive root has no folder name, so fall back to a fixed label.
$leaf = $sourceItem.Name.TrimEnd(':', '\', '/')
if ([string]::IsNullOrWhiteSpace($leaf)) {
$leaf = 'backup'
}
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$dest = Join-Path -Path $destFull -ChildPath "$leaf-$stamp"
if (-not (Test-Path -LiteralPath $destFull -PathType Container)) {
if ($PSCmdlet.ShouldProcess($destFull, 'Create destination parent folder')) {
New-Item -ItemType Directory -Path $destFull | Out-Null
}
}
if ($PSCmdlet.ShouldProcess($sourceFull, "Copy to $dest")) {
Copy-Item -LiteralPath $sourceFull -Destination $dest -Recurse
Write-Output $dest
}
Downloaded it? Windows blocks scripts from the internet until you unblock them: Unblock-File -Path .\backup-folder.ps1. See Run a script.
Edit this page on GitHub Edit backup-folder.ps1 Report a problem