Skip to content

Instantly share code, notes, and snippets.

@mmodrow
Last active August 22, 2025 13:37
Show Gist options
  • Select an option

  • Save mmodrow/515b678b962abf07632d627d0e187775 to your computer and use it in GitHub Desktop.

Select an option

Save mmodrow/515b678b962abf07632d627d0e187775 to your computer and use it in GitHub Desktop.
copy files and rename them by creation time stamp conflict-free
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]
$source,
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[string]
$destination,
[string]
$fileNameSuffix,
[switch]
$skipDuplicates = $false,
[switch]
$overrideTimeWithNow = $false,
[string]
[ValidateSet("none", "years", "months", "days")]
$clusterDepth = "none"
)
if (!$source) {
$source = Join-Path $PSScriptRoot "102PANA"
}
if ($fileNameSuffix) {
$fileNameSuffix = "_" + $fileNameSuffix
}
<#
see: https://www.reddit.com/r/PowerShell/comments/jx8532/comment/gcv9tpz/?tl=de&utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button
#>
function PSUsing {
param
(
[IDisposable] $disposable,
[ScriptBlock] $scriptBlock
)
try {
& $scriptBlock
}
finally {
if ($disposable -ne $null) {
$disposable.Dispose()
}
}
}
<#
see: https://www.reddit.com/r/PowerShell/comments/jx8532/comment/gcv9tpz/?tl=de&utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button
#>
function Get-ExifProperty {
param
(
[string] $ImagePath,
[int] $ExifTagCode
)
$fullPath = (Resolve-Path $ImagePath).Path
PSUsing ($fs = [System.IO.File]::OpenRead($fullPath)) `
{
PSUsing ($image = [System.Drawing.Image]::FromStream($fs, $false, $false)) `
{
if (-not $image.PropertyIdList.Contains($ExifTagCode)) {
return $null
}
$propertyItem = $image.GetPropertyItem($ExifTagCode)
$valueBytes = $propertyItem.Value
$value = [System.Text.Encoding]::ASCII.GetString($valueBytes) -replace "`0$"
return $value
}
}
}
function Get-ExifDateTaken {
[CmdletBinding()]
param (
[Parameter(mandatory)]
[string]
$filePath
)
try {
return Get-ExifProperty $filePath 306
}
catch {
return $null
}
}
$files = Get-ChildItem -Path $source
$fileCount = $files.count
$currentIndex = 0
$groups = ($files | Sort-Object -Property Name | Group-Object -Property extension, @{ expression = { $_.lastWriteTime } })
for ($i = 0; $i -lt $groups.Count; $i++) {
$group = $groups[$i].Group
$count = $groups[$i].Count
$padLeft = ([Int32]$count).ToString().Count
for ($j = 0; $j -lt $count; $j++) {
$file = $group[$j]
$suffix = if ($count -gt 1) { "_" + ([Int32]$j + 1).ToString().PadLeft($padLeft, '0') } else { "" }
$suffix = $suffix + $fileNameSuffix
if ($overrideTimeWithNow) {
$time = Get-date -format "yyyy-MM-dd_HH-mm-ss"
}
else {
$time = Get-ExifDateTaken -filePath $file.FullName
if ($time) {
$time = $time.replace(":", "-").replace(" ", "_")
}
else {
$time = @($file.CreationTime.ToString("yyyy-MM-dd_HH-mm-ss"), $file.LastWriteTime.ToString("yyyy-MM-dd_HH-mm-ss")) | Sort-Object | Select-Object -First 1
}
}
$targetFileName = $time + $suffix + $file.Extension.ToLower()
$directory = $destination
if ($clusterDepth -ne "none") {
$year = $time.Substring(0, [Math]::Min($time.Length, 4))
$month = $time.Substring(5, [Math]::Min($time.Length, 2))
$day = $time.Substring(8, [Math]::Min($time.Length, 2))
switch ($clusterDepth) {
"years" {
$directory = Join-Path $directory $year
break
}
"months" {
$directory = Join-Path $directory $year
$directory = Join-Path $directory $month
break
}
"days" {
$directory = Join-Path $directory $year
$directory = Join-Path $directory $month
$directory = Join-Path $directory $day
break
}
Default {}
}
}
if (!(Test-Path $directory)) {
New-Item -Path $directory -ItemType Directory -Force | Out-Null
}
$targetPath = Join-Path $directory $targetFileName
$percentComplete = [int](($currentIndex++ / $fileCount) * 100)
$statusMessage = "From: " + $file.FullName + " to: " + $targetPath
Write-Progress -Activity "Copying Files" -PercentComplete $percentComplete -Status "$percentComplete% Complete. Current Item: $statusMessage"
Write-Host $statusMessage
if (Test-Path $targetPath) {
if ($skipDuplicates) {
Write-Host "$targetPath exists and existing files are set to be skipped. Skipping file."
continue
}
elseif ((Get-FileHash -Path $file.FullName -Algorithm MD5).Hash -eq (Get-FileHash -Path $targetPath -Algorithm MD5).Hash) {
Write-Host "$targetPath exists with identical content. Skipping file."
continue
}
else {
$fileHash = (Get-FileHash -Path $file.FullName -Algorithm MD5).Hash
$targetPath = $targetPath -replace '(^.*)(\..*?$)', "`$1_$fileHash`$2"
if (Test-Path $targetPath) {
Write-Host "$targetPath exists with identical content. Skipping file."
continue
}
}
}
Copy-Item -Path $file.FullName -Destination $targetPath
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment