How to create a file snapshot in PowerShell: step by step
This tutorial shows how to create a file snapshot in PowerShell to record the state of a folder, detect changes and facilitate rollbacks. It is a simple and effective technique for auditing, detecting unwanted changes and incremental backups in environments where there is no more advanced versioning system. In real environments, one snapshot per day may be sufficient; in sensitive environments, you can run this process every hour. Concrete examples below show how to generate, compare and automate snapshots.
Prerequisites
- Windows with PowerShell 5.1 or PowerShell 7+
- Read/write permissions on the target folder (at least read for all files to monitor, write if you will save snapshots)
- Basic PowerShell knowledge (cmdlets like Get-ChildItem, Get-FileHash, Export-Csv)
Note on performance: computing hashes of large files depends on I/O and CPU. For example, on an SSD a modern machine can compute SHA256 at ~100 MB/s; 10 GB of files can take ~100 seconds in pure read. Test on a small folder (100–1000 files) before scaling to 50k files.
Step 1: Choose the folder and snapshot format
Decide which folder you will monitor and the snapshot format. Here we use a simple CSV with fields: RelativePath, FullPath, Length, LastWriteTime and Hash (SHA256). The CSV is portable and easy to compare with Import-Csv or Excel. Storing the timestamp in ISO 8601 ('o') format makes sorting and automatic parsing easier.
Sizing example: a snapshot of 10k files (5 fields) tends to be between 1–5 MB depending on path lengths. If you need compression, save the CSVs as .zip or use gzip forced by other tools.
Step 2: Basic script to generate the snapshot
Create a script that walks the folder recursively, computes the hash of each file and writes a CSV. The hash detects content changes even if the timestamp does not change. Use SHA256 instead of SHA1, which is less recommended today.
# GerarSnapshot.ps1
param(
[string]$Folder = ".",
[string]$SnapshotPath = "snapshot.csv"
)
$base = (Resolve-Path $Folder).Path
Get-ChildItem -Path $base -File -Recurse | ForEach-Object {
$rel = $_.FullName.Substring($base.Length).TrimStart('\\')
$hash = Get-FileHash -Path $_.FullName -Algorithm SHA256
[PSCustomObject]@{
RelativePath = $rel
FullPath = $_.FullName
Length = $_.Length
LastWriteTime = $_.LastWriteTime.ToString('o')
Hash = $hash.Hash
}
} | Export-Csv -Path $SnapshotPath -NoTypeInformation -Encoding UTF8
Save as GerarSnapshot.ps1. Run: .\GerarSnapshot.ps1 -Folder C:\\Dados -SnapshotPath C:\\snapshots\\snap1.csv. In a practical example, a set of 2,000 files (total 2 GB) can take 1–3 minutes to generate the snapshot depending on the disk.
Step 3: Compare two snapshots
To detect added, removed or changed files, load two CSVs and compare by RelativePath and Hash. The method below uses Compare-Object for changes in the list of paths and an additional comparison for content changes (different hash).
# CompararSnapshots.ps1
param(
[string]$OldSnapshot,
[string]$NewSnapshot
)
$old = Import-Csv -Path $OldSnapshot | Sort-Object RelativePath
$new = Import-Csv -Path $NewSnapshot | Sort-Object RelativePath
# Ficheiros novos
$added = Compare-Object -ReferenceObject $old -DifferenceObject $new -Property RelativePath -PassThru | Where-Object {$_.PSObject.Properties['SideIndicator'].Value -eq '=>'}
# Ficheiros removidos
$removed = Compare-Object -ReferenceObject $old -DifferenceObject $new -Property RelativePath -PassThru | Where-Object {$_.PSObject.Properties['SideIndicator'].Value -eq '<='}
# Ficheiros com o mesmo caminho mas hash diferente -> alterados
$joined = @()
foreach ($n in $new) {
$o = $old | Where-Object { $_.RelativePath -eq $n.RelativePath }
if ($o) {
if ($o.Hash -ne $n.Hash) {
$joined += [PSCustomObject]@{RelativePath=$n.RelativePath; OldHash=$o.Hash; NewHash=$n.Hash}
}
}
}
# Resultado simples
[PSCustomObject]@{
Added = $added.Count
Removed = $removed.Count
Modified = $joined.Count
}
# Lista detalhada (opcional)
if ($added) { Write-Output "--- Added ---"; $added | Select-Object RelativePath }
if ($removed) { Write-Output "--- Removed ---"; $removed | Select-Object RelativePath }
if ($joined) { Write-Output "--- Modified ---"; $joined }
Run: .\CompararSnapshots.ps1 -OldSnapshot C:\\snapshots\\snap1.csv -NewSnapshot C:\\snapshots\\snap2.csv. A typical result might be: Added = 5, Removed = 2, Modified = 10. Check the detailed lists to confirm changes.
Step 4: Ignore files that change frequently
Some folders have temporary files (logs, caches) that only generate noise. Filter by extension, pattern or size before computing the hash to reduce time and noise. For example, skip *.log, *.tmp and files smaller than 1 KB.
# Exemplo de filtro no GerarSnapshot.ps1
$exclude = '*.log','*.tmp'
Get-ChildItem -Path $base -File -Recurse | Where-Object {
$match = $false
foreach ($pat in $exclude) { if ($_.Name -like $pat) { $match = $true; break } }
-not $match -and $_.Length -gt 1024
} | ForEach-Object { ... }
This approach significantly reduces time: by excluding 30% of temporary files, snapshot generation can drop from 10 minutes to 7 minutes in an example with many small changes.
Step 5: Automate and preserve history
You can run the script regularly with Task Scheduler (e.g., daily or hourly) and save snapshots with a timestamp in the name. Keep only the last N to avoid filling the disk; 7 is a good value for weekly retention.
# Exemplo para guardar com timestamp
$ts = (Get-Date).ToString('yyyyMMddHHmmss')
.\GerarSnapshot.ps1 -Folder C:\\Dados -SnapshotPath C:\\snapshots\\snapshot_$ts.csv
# Remover snapshots antigos, fica só os 7 mais recentes
Get-ChildItem C:\\snapshots -Filter 'snapshot_*.csv' | Sort-Object LastWriteTime -Descending | Select-Object -Skip 7 | Remove-Item
Practical scheduling: create a Task that runs the script at 02:00 or during low-usage hours. If the folder has many files, set an email alert on failure (e.g., integration with Send-MailMessage or monitoring systems).
Verify the result
Confirm that the CSVs were generated and that the comparison shows coherent numbers. Open the CSVs in Excel or use Import-Csv to inspect. Do a manual check on some files listed in Added/Removed/Modified: for example, use Get-FileHash on the specific file to confirm the current hash. If you are testing, create artificial changes (edit a 1 KB file) to observe the behavior.
Conclusion
You learned how to create and compare file snapshots in PowerShell, useful for auditing and simple incremental backups. Possible next steps: integrate email notifications, sync snapshots to OneDrive/SharePoint or store only metadata in a central system. Practical tip: start by testing on a small folder (100–1,000 files) to validate filters and performance. Which folder do you want to monitor first?