Skip to content

Instantly share code, notes, and snippets.

@jongio
Created March 3, 2021 15:34
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 12 You must be signed in to fork a gist
  • Save jongio/a40ea198ca5ebd85d711a7779289cc89 to your computer and use it in GitHub Desktop.
Save jongio/a40ea198ca5ebd85d711a7779289cc89 to your computer and use it in GitHub Desktop.
A PowerShell script to organize photos by date taken
Param(
[string]$source,
[string]$dest,
[string]$format = "yyyy/yyyy_MM/yyyy_MM_dd"
)
$shell = New-Object -ComObject Shell.Application
function Get-File-Date {
[CmdletBinding()]
Param (
$object
)
$dir = $shell.NameSpace( $object.Directory.FullName )
$file = $dir.ParseName( $object.Name )
# First see if we have Date Taken, which is at index 12
$date = Get-Date-Property-Value $dir $file 12
if ($null -eq $date) {
# If we don't have Date Taken, then find the oldest date from all date properties
0..287 | ForEach-Object {
$name = $dir.GetDetailsof($dir.items, $_)
if ( $name -match '(date)|(created)') {
# Only get value if date field because the GetDetailsOf call is expensive
$tmp = Get-Date-Property-Value $dir $file $_
if ( ($null -ne $tmp) -and (($null -eq $date) -or ($tmp -lt $date))) {
$date = $tmp
}
}
}
}
return $date
}
function Get-Date-Property-Value {
[CmdletBinding()]
Param (
$dir,
$file,
$index
)
$value = ($dir.GetDetailsof($file, $index) -replace "`u{200e}") -replace "`u{200f}"
if ($value -and $value -ne '') {
return [DateTime]::ParseExact($value, "g", $null)
}
return $null
}
Get-ChildItem -Attributes !Directory $source -Recurse |
Foreach-Object {
Write-Host "Processing $_"
$date = Get-File-Date $_
if ($date) {
$destinationFolder = Get-Date -Date $date -Format $format
$destinationPath = Join-Path -Path $dest -ChildPath $destinationFolder
# See if the destination file exists and rename until we get a unique name
$newFullName = Join-Path -Path $destinationPath -ChildPath $_.Name
if ($_.FullName -eq $newFullName) {
Write-Host "Skipping: Source file and destination files are at the same location. $_"
return
}
$newNameIndex = 1
$newName = $_.Name
while (Test-Path -Path $newFullName) {
$newName = ($_.BaseName + "_$newNameIndex" + $_.Extension)
$newFullName = Join-Path -Path $destinationPath -ChildPath $newName
$newNameIndex += 1
}
# If we have a new name, then we need to rename in current location before moving it.
if ($newNameIndex -gt 1) {
Rename-Item -Path $_.FullName -NewName $newName
}
Write-Host "Moving $_ to $newFullName"
# Create the destination directory if it doesn't exist
if (!(Test-Path $destinationPath)) {
New-Item -ItemType Directory -Force -Path $destinationPath
}
robocopy $_.DirectoryName $destinationPath $newName /mov
}
}
@jongio
Copy link
Author

jongio commented Jan 20, 2023

You can pass in a custom format to the script

Param(
[string]$source,
[string]$dest,
[string]$format = "yyyy/yyyy_MM/yyyy_MM_dd"
)

@cooltig
Copy link

cooltig commented Jan 20, 2023

Thanks but I already tried that and in the destination folder it creates a folder called "yyyy.mm" instead of "yyyy/mm"

.\PhotoOrganizer.ps1 -source "E:" -dest "C:\test"
Screenshot_20230120_171231

It seems that the slash "/" doesn't create a subfolder, it will used as . in foldername.

@jongio
Copy link
Author

jongio commented Jan 20, 2023

This:
.\PhotoMover.ps1 -source "C:\temp\phototest\source" -dest "C:\temp\phototest\dest" -format "yyyy/MM"
Created:
image

Can you try with:

`.\PhotoMover.ps1 -source "C:\temp\phototest\source" -dest "C:\temp\phototest\dest" -format "yyyy\/MM"`

To escape the slash?

@cooltig
Copy link

cooltig commented Jan 20, 2023

Awesome, this works!

[string]$format = "yyyy\/MM"

Thank you so much!

@LeoZandvliet
Copy link

For some reason the EXIF Date taken in some of my files are formatted like this:
" 23/ 07/ 2023 15:37"

including some not specificly identified non-printable characters. Sigh....

So I ended in replacing all non ASCII characters with a space, and then preg replaced the result to gat a compliance date format:

function Get-Date-Property-Value {
    [CmdletBinding()]

    Param (
        $dir,
        $file,
        $index
    )

    $value = ($dir.GetDetailsof($file, $index) -replace "`u{200e}") -replace "`u{200f}"
	
    $value = (($value  -replace '[^ -~]+', ' ') -replace '[\s]*(\d{2})\/[\s]*(\d{2})\/[\s]*(\d{4})[\s]+([\d]{2}:[\d]{2})[\s]*', '$1/$2/$3 $4')
    if ($value -and $value -ne '') {
		Write-Host "Found ""$value"""
        return [DateTime]::ParseExact($value, "g", $null)
    }
    return $null
}

@greiginsydney
Copy link

greiginsydney commented Sep 8, 2023

Thanks @LeoZandvliet , that did the trick for me!

EDIT: I also needed to replace the "g" "general date format specifier" with my specific format, which here in AU turned out to be "dd/MM/yyyy hh:mmtt".

This is my final "return" line:

return [DateTime]::ParseExact($value, "dd/MM/yyyy hh:mmtt", $null)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment