############### GLOBAL ############## $LogPath = "C:/Temp/OrgPhonePhotos.log" ############### MAIN ############### # main function # $Main = { [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $SourceFolder, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $DestFolder, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $FolderForDuplicates, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $LogFile ) $header = "$("-"*80)`n`n$(Get-Date -UFormat ""%A %Y-%m-%d %T"")`n" Write-Output $header | Tee-Object -FilePath $LogFile -Append $LogPath = (Get-Item $LogFile).FullName validatedest $SourceFolder $DestFolder $FolderForDuplicates cleanfolder $SourceFolder remove-emptyfolders $SourceFolder $medialist=[System.Collections.Generic.List[object]]::new() $Files_jpg = Get-ChildItem -Path $SourceFolder -File -Filter "*.jpg" -Recurse $Files_raw = Get-ChildItem -Path $SourceFolder -File -Filter "*.cr?" -Recurse $Files_mp4 = Get-ChildItem -Path $SourceFolder -File -Filter "*.mp4" -Recurse ForEach ($File in $Files_jpg) { $medialist.Add($File) } ForEach ($File in $Files_raw) { $medialist.Add($File) } ForEach ($File in $Files_mp4) { $medialist.Add($File) } foreach ($mediafile in $medialist) { $xfoutput = @(C:\Tools\exiftool\exiftool.exe $mediafile.FullName) $sourcefilefullname = $mediafile.FullName # Write-Output $sourcefilefullname | Tee-Object -FilePath $LogPath -Append $exiflist = @{} foreach ($tagvalpair in $xfoutput) { $arr=@($tagvalpair -split ":", 2) $key=$arr[0] -replace ' ' -replace '/','_' $value=$arr[1].Trim() $exiflist[$key] = $value } # Write-Output "`n------ xfoutput ---------" $mediafile.Name $xfoutput # Write-Output "`n------ exiflist ---------" $mediafile.Name $exiflist $CreateDate = cv2datetime $exiflist['CreateDate'] $Date_TimeOriginal = cv2datetime $exiflist['Date_TimeOriginal'] $FileCreationDate_Time = cv2datetime $exiflist['FileCreationDate_Time'] $FileModificationDate_Time = cv2datetime $exiflist['FileModificationDate_Time'] $FileAccessDate_Time = cv2datetime $exiflist['FileAccessDate_Time'] $filenamedate = validatedate $mediafile.Name $datestr = $null $datetaken = getearliest $CreateDate $Date_TimeOriginal if (-not($null -eq $datetaken)) { $datestr = $datetaken.ToString('yyyyMMdd') } elseif (-not($null -eq $filenamedate)) { Write-Output """$sourcefilefullname"" : File has no usable ExifData. Using date in filename." | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Yellow $datestr = $filenamedate.ToString('yyyyMMdd') } else { $earliest = getearliest $FileCreationDate_Time $FileModificationDate_Time $FileAccessDate_Time $file_time = $earliest.ToString('yyyy-MM-dd HH:mm:ss') Write-Output "Skipped ""$sourcefilefullname"" : File has no usable ExifData! Earliest file time is $file_time" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkRed } $sourcefiledir = "$($mediafile.Directory)" #Write-Output $time_arr if (-not($null -eq $datestr)) { #Write-Output "$($mediafile.Name) $datestr" movetosubfolder "$($mediafile.Name)" "$datestr" "$sourcefiledir" "$DestFolder" "$FolderForDuplicates" remove-emptyfolder "$sourcefiledir" } } } Function validatedate { [CmdletBinding()] Param( [Parameter(Mandatory=$false)] [string] $datetimestr ) if (($null -eq $datetimestr) -or ($datetimestr -eq "")) { return $null } if ($datetimestr.Length -le 13) { return $null } $datestart = 0 $stryear = $datetimestr.substring($datestart,4) if (($stryear -eq "IMG-") -or ($stryear -eq "VID-")){ $datestart = 4 $stryear = $datetimestr.substring($datestart,4) if ($datetimestr.Length -le 17) { return $null } } $strmonth = $datetimestr.substring($datestart + 4,2) $strday = $datetimestr.substring($datestart + 6,2) $year = 0 $month = 0 $day = 0 if (-not [int]::TryParse($stryear, [ref] $year)) { return $null } if (-not [int]::TryParse($strmonth, [ref] $month)) { return $null } if (-not [int]::TryParse($strday, [ref] $day)) { return $null } if ($year -lt 1 -or $month -lt 1 -or $day -lt 1) { return $null } try { $datetime =[DateTime]"$stryear-$strmonth-$strday" } catch { return $null } $now = Get-Date if ($datetime -gt $now) { return $null } return $datetime } Function getearliest { [CmdletBinding()] Param( [Parameter(Mandatory=$false)] [string] $datetimestr1, [Parameter(Mandatory=$false)] [string] $datetimestr2, [Parameter(Mandatory=$false)] [string] $datetimestr3 ) $result = [long]::MaxValue if (-Not(($null -eq $datetimestr1) -or ($datetimestr1 -eq ""))) { $datetime1 = [DateTime]$datetimestr1 $time = $datetime1.Ticks if ($time -lt $result) { $result = $time } } if (-Not(($null -eq $datetimestr2) -or ($datetimestr2 -eq ""))) { $datetime2 = [DateTime]$datetimestr2 $time = $datetime2.Ticks if ($time -lt $result) { $result = $time } } if (-Not(($null -eq $datetimestr3) -or ($datetimestr3 -eq ""))) { $datetime3 = [DateTime]$datetimestr3 $time = $datetime3.Ticks if ($time -lt $result) { $result = $time } } if ($result -eq [long]::MaxValue) { return $null } return [DateTime]$result } Function cv2datetime { [CmdletBinding()] Param( [Parameter(Mandatory=$false)] [string] $datetimestr ) if (($null -eq $datetimestr) -or ($datetimestr -eq "")) { return $null } $time_arr = @($datetimestr -split " ", 2) $datestr = $time_arr[0] -replace ":" $datestr = $datestr.Trim() if (($null -eq $datestr) -or ($datestr -eq "")) { $datestr = "" } else { $datestr = $time_arr[0] -replace ":","-" } $timestr = $time_arr[1] -replace ":" $timestr = $timestr.Trim() if (($null -eq $timestr) -or ($timestr -eq "")) { $timestr = "" } else { $timestr = $time_arr[1] } $result = "$datestr $timestr".Trim() if ($result -eq "") { return $null } try { return [DateTime]$result } catch { return $null } } Function cleanfolder { [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $folder ) $filelist = Get-ChildItem -Path $SourceFolder -File -Filter "ZbThumbnail.info" -Recurse foreach ($file in $filelist) { deletefile "$($file.FullName)" } $filelist = Get-ChildItem -Path $SourceFolder -File -Filter "Thumbs.db" -Recurse foreach ($file in $filelist) { deletefile "$($file.FullName)" } } Function deletefile { [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $filepath ) if(Test-Path -Path $filepath -PathType Leaf) { Remove-Item $filepath Write-Output "$filepath deleted!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Magenta } } Function remove-emptyfolders { [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $folder ) $folderlist = Get-ChildItem -Path $folder -Directory -Force -Recurse | Sort-Object FullName -Descending foreach ($item in $folderlist) { $folderdetail = $item.GetFileSystemInfos() | Measure-Object If ($folderdetail.Count -eq 0) { deletefolder "$($item.FullName)" } } } Function remove-emptyfolder { [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $folder ) $count = (Get-ChildItem -Path $folder -Force | Measure-Object).Count If ($count -eq 0) { deletefolder "$folder" } } Function deletefolder { [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $folderpath ) if(Test-Path -Path $folderpath -PathType Container) { Remove-Item $folderpath Write-Output "$folderpath deleted!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkMagenta } } Function validatedest { [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $sourcefolder, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $destfolder, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $duplicatesfolder ) if(-Not(Test-Path $sourcefolder)) { Write-Output "Fatal: Source folder ""$sourcefolder"" does not exist!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red exit 1 } if(-Not(Test-Path $destfolder)) { Write-Output "Fatal: Destination folder ""$destfolder"" does not exist!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red exit 1 } if(-Not(Test-Path $duplicatesfolder)) { Write-Output "Fatal: Duplicates folder ""$duplicatesfolder"" does not exist!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red exit 1 } do { $temp = $sourcefolder $sourcefolder = $temp.TrimEnd('\').TrimEnd('/') } while ($temp -ne $sourcefolder) do { $temp = $destfolder $destfolder = $temp.TrimEnd('\').TrimEnd('/') } while ($temp -ne $destfolder) do { $temp = $duplicatesfolder $duplicatesfolder = $temp.TrimEnd('\').TrimEnd('/') } while ($temp -ne $duplicatesfolder) $sourcedir = Get-Item $sourcefolder $destdir = Get-Item $destfolder $duplicatesdir = Get-Item $duplicatesfolder if ($sourcedir.FullName -eq $destdir.FullName) { Write-Output "Fatal: Destination folder must not be the same as the source folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red exit 1 } if ($sourcedir.FullName -eq $duplicatesdir.FullName) { Write-Output "Fatal: Duplicates folder must not be the same as the source folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red exit 1 } if ($duplicatesdir.FullName -eq $destdir.FullName) { Write-Output "Fatal: Destination folder must not be the same as the duplicates folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red exit 1 } $folderlist = @(Get-ChildItem -Path $sourcedir -Directory -Recurse) ForEach ($folder in $folderlist) { if ($folder.FullName -eq $destdir.FullName) { Write-Output "Fatal: Destination folder must not be a subfolder of the source folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red exit 1 } if ($folder.FullName -eq $duplicatesdir.FullName) { Write-Output "Fatal: Duplicates folder must not be a subfolder of the source folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red exit 1 } } $folderlist = @(Get-ChildItem -Path $destdir -Directory -Recurse) ForEach ($folder in $folderlist) { if ($folder.FullName -eq $sourcedir.FullName) { Write-Output "Fatal: Source folder must not be a subfolder of the destination folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red exit 1 } if ($folder.FullName -eq $duplicatesdir.FullName) { Write-Output "Fatal: Duplicates folder must not be a subfolder of the destination folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red exit 1 } } $folderlist = @(Get-ChildItem -Path $duplicatesdir -Directory -Recurse) ForEach ($folder in $folderlist) { if ($folder.FullName -eq $sourcedir.FullName) { Write-Output "Fatal: Source folder must not be a subfolder of the duplicates folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red exit 1 } if ($folder.FullName -eq $destdir.FullName) { Write-Output "Fatal: Destination folder must not be a subfolder of the duplicates folder!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Red exit 1 } } } Function movetosubfolder { [CmdletBinding()] Param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $mediafile, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $datestr, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $sourcefolder, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $destfolder, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $duplicates ) $sourceitem="$sourcefolder/$mediafile" # 4 = length of extension + minimum length of filename = 8+4+1 if ($mediafile.Length -le 4) { Write-Output "Skipped ""$sourceitem"" : File name too short!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkYellow } else { $stryear=$datestr.substring(0,4) $strmonth=$datestr.substring(4,2) $strday=$datestr.substring(6,2) $year=[int]$stryear $month=[int]$strmonth $day=[int]$strday if ($year -lt 1 -or $month -lt 1 -or $day -lt 1) { Write-Output "Skipped ""$sourceitem"" : File ExifData has invalid date!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkYellow } else { $mediadatestr = "$stryear-$strmonth-$strday" $mediadate = [DateTime]"$mediadatestr" $mediasubfolder = "$stryear`_$strmonth`_$strday" $destdir="$destfolder\$mediasubfolder" $dupldir="$duplicates\$mediasubfolder" $todaystr=Get-Date -UFormat "%Y-%m-%d" $todaydate = [DateTime]"$todaystr" if ($mediadate -gt $todaydate) { Write-Output "Skipped ""$sourceitem"" : File ExifData has future date!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkYellow } else { $destitem="$destdir/$mediafile" $duplitem="$dupldir/$mediafile" if (-Not(Test-Path -Path "$destdir")) { Write-Output "$destdir" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Green New-Item -ItemType "Directory" -Path "$destdir" } if(Test-Path $destitem) { if(Test-Path $duplitem) { Write-Output "Skipped ""$sourceitem"" : File exists in duplicates subfolder ""$mediasubfolder""!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Yellow } else { if (-Not(Test-Path -Path "$dupldir")) { Write-Output "$dupldir" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Green New-Item -ItemType "Directory" -Path "$dupldir" } Move-Item -Path "$sourceitem" -Destination "$dupldir/" Write-Output """$sourceitem"" moved to duplicates subfolder ""$mediasubfolder""!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor DarkYellow } } else { Move-Item -Path "$sourceitem" -Destination "$destdir/" Write-Output """$sourceitem"" moved to destination subfolder ""$mediasubfolder""!" | Tee-Object -FilePath $LogPath -Append | Write-Host -ForegroundColor Green } } } } } ################################### # we call main proc from here & $Main @Args