PowerShell tools for organizing a camera roll: bucket date-named media into Year\yyyy-MM-dd folders, with room for future dedup and video-sorting scripts.
Find a file
2026-07-08 17:22:51 -05:00
.github Add camera-roll-tools scaffold and Move-PhotosByDate script 2026-07-08 16:28:19 -05:00
scripts Accept flexible separators between date and time in filenames 2026-07-08 17:16:51 -05:00
.gitattributes Add .gitattributes to normalize line endings 2026-07-08 16:28:19 -05:00
AGENT-README.md Note flexible date/time separator in AGENT-README.md 2026-07-08 17:17:49 -05:00
CODE_OF_CONDUCT.md Add camera-roll-tools scaffold and Move-PhotosByDate script 2026-07-08 16:28:19 -05:00
CONTRIBUTING.md Add camera-roll-tools scaffold and Move-PhotosByDate script 2026-07-08 16:28:19 -05:00
LICENSE Add camera-roll-tools scaffold and Move-PhotosByDate script 2026-07-08 16:28:19 -05:00
README.md Merge dev (AGENT-README.md v0.1) into flexible-separator branch 2026-07-08 17:17:03 -05:00
SECURITY.md Add camera-roll-tools scaffold and Move-PhotosByDate script 2026-07-08 16:28:19 -05:00
THIRD-PARTY-NOTICES.md Add camera-roll-tools scaffold and Move-PhotosByDate script 2026-07-08 16:28:19 -05:00

camera-roll-tools

agent-readme

A growing collection of PowerShell tools for keeping a camera roll organized. Cameras and phones tend to dump every capture into a single folder; these scripts help sort that pile into a clean, date-navigable structure and (over time) handle related chores like deduplication and separating videos from stills.

Scripts

Script Purpose
scripts/Move-PhotosByDate.ps1 Moves date-named files (yyyyMMdd[sep]HHmmss.*) into Year\yyyy-MM-dd subfolders.

Additional scripts (for example duplicate detection and video sorting) may be added to scripts/ over time.

Requirements

  • Windows PowerShell 5.1 or PowerShell 7+
  • No third-party modules; the scripts use only built-in cmdlets and .NET types

Move-PhotosByDate

Given files whose names begin with a yyyyMMdd date followed by an HHmmss time (for example 20260711-123456.jpg), the script moves each file into a per-day subfolder nested under a per-year folder, keeping the original filename intact:

C:\CameraRoll\20260711-123456.jpg   ->   C:\CameraRoll\2026\2026-07-11\20260711-123456.jpg

The separator between the date and time is flexible: a dash, underscore, dot, or space, or no separator at all. All of these are recognized:

20260711-123456.jpg   20260711_123456.jpg   20260711.123456.jpg   20260711 123456.jpg   20260711123456.jpg

The date for each file is resolved in this order of precedence:

  1. The leading yyyyMMdd-HHmmss timestamp in the filename.
  2. If the filename has no valid date and -ExifFallback is supplied, the embedded capture date read from the file's metadata (EXIF DateTimeOriginal for photos, "Media created" for videos).

Only files whose extension is in the processing set are considered. The default set is a list of common image extensions; -IncludeVideo adds common video extensions, and -Extension replaces the image list.

Quick start

# Preview the moves without changing anything
.\scripts\Move-PhotosByDate.ps1 -SourceFolder 'C:\CameraRoll' -WhatIf

# Reorganize images in place (Year\yyyy-MM-dd folders created under the source folder)
.\scripts\Move-PhotosByDate.ps1 -SourceFolder 'C:\CameraRoll'

# Include videos and fall back to embedded capture dates when the filename has no date
.\scripts\Move-PhotosByDate.ps1 -SourceFolder 'C:\CameraRoll' -IncludeVideo -ExifFallback

# Sort an incoming dump into an existing camera roll, only specific extensions
.\scripts\Move-PhotosByDate.ps1 -SourceFolder 'C:\Incoming' -DestinationFolder 'C:\CameraRoll' -Extension jpg,heic,dng

Parameters

Parameter Required Description
-SourceFolder Yes Folder to scan for media files. Must exist.
-DestinationFolder No Root under which the Year\yyyy-MM-dd subfolders are created. Defaults to the source folder (in-place reorganization). Created automatically if it does not exist.
-Extension No Extensions to process, with or without a leading dot. Replaces the default image list. Default: common image extensions (.jpg .jpeg .png .heic .heif .tif .tiff .gif .bmp .webp .dng .cr2 .cr3 .nef .arw .orf .rw2 .raf .sr2 .raw).
-IncludeVideo No Also process common video extensions (.mp4 .mov .m4v .avi .mkv .mpg .mpeg .wmv .3gp .3g2 .webm .mts .m2ts) in addition to the images in effect.
-ExifFallback No When a filename has no valid date, read the capture date from file metadata (EXIF DateTimeOriginal for photos, "Media created" for videos). The filename date always wins when present and valid.
-Recurse No Also process files in subdirectories of the source folder. Files already in a correctly-named Year\yyyy-MM-dd folder are left untouched.

The script supports -WhatIf and -Confirm, so every move can be previewed before it runs.

How the EXIF/metadata fallback works

-ExifFallback reads the embedded capture date without any third-party module:

  • Photos: true EXIF DateTimeOriginal (tag 0x9003) via System.Drawing, falling back to the Windows Shell "Date taken" property when System.Drawing is unavailable (for example under PowerShell 7).
  • Videos: the Windows Shell "Media created" property (videos carry no EXIF).

The Windows Shell property system is the same metadata shown on the Details tab in File Explorer, accessed through the Shell.Application COM object, so it works under both Windows PowerShell 5.1 and PowerShell 7.

Behavior and safety

  • Files whose extension is not in the processing set are excluded (reported at verbose level).
  • Files whose names lack a valid date and, when -ExifFallback is set, have no usable metadata date, are skipped and reported as warnings.
  • The date portion is validated as a real calendar date; impossible dates (for example 2026-13-01) are skipped.
  • A file is skipped if a file with the same name already exists at the target path (no overwrite).
  • A file already located in its correct target folder is left untouched.
  • On completion the script returns a summary object with SourceFolder, DestinationFolder, Moved, ResolvedByExif, Skipped, and Excluded counts.

Assumptions

  • The filename date is taken from the first 8 digits of the name (yyyyMMdd) and always takes precedence over metadata.
  • Metadata dates are only consulted when -ExifFallback is supplied and the filename has no valid date.

Getting the help

Each script ships with comment-based help:

Get-Help .\scripts\Move-PhotosByDate.ps1 -Full

License

Released under the MIT License.