PowerShell: Rename Photo and Video Files by Adding Creation Dates

Have you ever downloaded a punch of images, MOV, and MP4 from iCloud to find that Windows displays their Creation Dates as the same value of your download time. It’s difficult to group photos in such views. Although Explorer does have the feature to display ‘Date taken’ attribute for image files by enabling certain ‘columns,’ such equivalent labeling for videos are hard to locate. You’re in luck, I’ve written a PoSH function to address this very issue by adding date stamps onto file names for easy scanning.

Window Explorer View of downloaded iCloud Photos
Windows option to show ‘Date taken’ column for JPEG files
Lubuntu Linux PCMANFM-QT View of downloaded iCloud Photos
# renamePhotosByDateTaken.ps1
# Version: 0.0.1
# Author: KimConnect.com

$mediaDirectory='\\tsclient\Desktop\iCloud Photos'

function renameMediaFilesByAddingDates{
    param(
        [string]$mediaDirectory,
        [string]$regexPhotoFile='(JPG|PNG|GIF|BMP|TIFF|JPEG)+$',
        [string]$regexVideoFile='(MOV|MP4|AVI|MPG|MPEG|WMV|QT|FLV|SWF)+$'   
    )
    function getMediaDateTaken($filePath,$propertyIndex){        
        if(!$propertyIndex){
            $propertyIndex=switch -regex ($filePath){
                '(JPG|PNG|GIF|BMP|TIFF|JPEG)+$' {12}
                '(MOV|MP4|AVI|MPG|MPEG|WMV|QT|FLV|SWF)+$' {208} # Assuming Windows 10
            }
        }
        try{           
            $shell = New-Object -COMObject Shell.Application
            $shellFolder = $shell.Namespace($(split-path $filePath))
            $shellFile = $shellFolder.ParseName($(split-path $filePath -leaf))
            # How to view all attributes: 0..287|%{'{0}: {1}' -f $_,$shellFolder.GetDetailsOf($shellFile,$_)}
            $dateTakenUnicode=$shellFolder.GetDetailsOf($shellFile, $propertyIndex)
            [datetime]$dateTaken=$dateTakenUnicode -replace '[^\d^\:^\w^\/^\s]'
            return $dateTaken
        }catch{
            write-host $_
            return $false
        }
    }

    # This function reveals the index number of properties by names
    function getFileAttributes ([string[]]$filePath){
        $shell = New-Object -COMObject Shell.Application
        $shellFolder = $shell.Namespace($(split-path $filePath))
        $shellFile = $shellFolder.ParseName($(split-path $filePath -leaf))
        $attributes = @()
        0..287 | ForEach-Object {
            $propertyName = $shellFolder.GetDetailsOf($shellFolder, $_)
            $propertyValue = $shellFolder.GetDetailsOf($shellFile, $_)       
            #write-host "$propertyName`: $propertyValue"
            if($propertyName -ne '' -and $null -ne $propertyName){
                # Avoid this error:
                #Item has already been added. Key in dictionary: ''  Key being added: ''
                #At line:5 char:13
                #+             $attributes+=@{$propertyName=$propertyValue}
                #+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                #    + CategoryInfo          : OperationStopped: (:) [], ArgumentException
                #    + FullyQualifiedErrorId : System.ArgumentException
                $attributes+=[pscustomobject]@{
                    index=$_
                    name=$propertyName
                    value=$propertyValue
                }
            }
        }
        $shell=$null
        return $attributes
    }
    
    if (test-path "$mediaDirectory\Thumbs.db:encryptable" -ea SilentlyContinue){
        # remove-item -literalPath "$mediaDirectory\Thumbs.db:encryptable"
        write-warning "Retry this program after this file is deleted: $targetFile"
        return $false
    }
    #$regexIllegalCharsInName='(\<|\>|:|"|/|\?|\*|\\)'
    $regexMediaFile=($regexPhotoFile -replace '[)+$]') +'|'+ ($regexVideoFile -replace '\(')
    $allMediaFiles=Get-ChildItem $mediaDirectory -Recurse|?{!$_.PSisContainer -and $_.FullName -match $regexMediaFile}

    <# Note on an file name issue in media directories as Windows autogens a file named 'Thumbs.db:encryptable' that is an invalid file name
    Error:
    Get-ChildItem : The given path's format is not supported.
    At line:1 char:11
    + $allFiles=Get-ChildItem $mediaDirectory -ea SilentlyContinue
    +           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : NotSpecified: (:) [Get-ChildItem], NotSupportedException
        + FullyQualifiedErrorId : System.NotSupportedException,Microsoft.PowerShell.Commands.GetChildItemCommand
    Solution:
    Remove any file name that has illegal characters, specifically 'Thumbs.db:encryptable'
    #>
    if ($allMediaFiles){
        $fileAttributes=getFileAttributes $allFiles[0].FullName # generate this variable once to speed up runtime
        [hashtable]$mediaFiles=@{}
        write-host "Retrieving the creation date of each media file..."
        foreach ($item in $allMediaFiles){
            $filePath=$item.FullName
            $fileName=$item.Name
            $propertyIndex=switch -regex ($filePath){
                $regexPhotoFile {($fileAttributes|?{$_.Name -eq 'Date taken'}).index }
                $regexVideoFile {($fileAttributes|?{$_.Name -eq 'Media created'}).index} # Assuming Windows 10
            }
            $dateCreated=.{
                if($filePath -match $regexMediaFile){
                    return getMediaDateTaken $filePath $propertyIndex
                }else{
                    #return $item.CreationTime
                    return $false
                }
            }
            if($dateCreated){
                $mediaFiles.add($filePath,$dateCreated)
                write-host "$fileName`: $dateCreated"
            }else{
                write-host "$fileName`: SKIPPED!"
            }
        }

        #$maxFileNameLength=255 # Assuming legacy Windows 7 compatibility
        $maxPathNameLength=260
        $count=0
        foreach ($file in $mediaFiles.GetEnumerator()){
            try{
                $filePath=$file.Key
                $creationDateStamp=($file.Value).tostring("MM-dd-yyyy-HHmm")
                $maxCharFeasible=$maxPathNameLength-$filePath.length
                $newFilePath=if($maxCharFeasible -ge 16){
                        $filePath.Insert($filePath.LastIndexOf('.'),'-'+$creationDateStamp)
                    }elseif($maxCharFeasible -ge 11){
                        $filePath.Insert($filePath.LastIndexOf('.'),'-'+($file.Value).tostring('MM-dd-yyyy'))
                    }elseif($maxCharFeasible -ge 8){
                        $filePath.Insert($filePath.LastIndexOf('.'),'-'+($file.Value).tostring('MM-yyyy'))
                    }elseif($maxCharFeasible -ge 5){
                        $filePath.Insert($filePath.LastIndexOf('.'),'-'+($file.Value).tostring('yyyy'))
                    }else{
                        $filePath
                    }
                # Confirm the first 3 files
                if($count++ -lt 3){
                    write-host "`r`nRenaming $filePath to`r`n$newFilePath ?`r`n--- Ctrl+C to cancel ---"
                    pause
                }else{
                    write-host "$newFilePath"
                }
                rename-item $filePath $newFilePath -force
            }catch{
                Write-Host $_
            }

        }
        write-host "Done."
        return $true
    }else{
        write-host "Unable to retrieve list of media files from directory $mediaDirectory"
        return $false
    }
}

renameMediaFilesByAddingDates $mediaDirectory
<#
Sample Output:
------------------------------
Renaming \\tsclient\Desktop\iCloud Photos\IMG_0379.JPEG to
\\tsclient\Desktop\iCloud Photos\IMG_0379-11-23-2019-2010.JPEG?
--- Ctrl+C to cancel ---
Press Enter to continue...:
Renaming \\tsclient\Desktop\iCloud Photos\IMG_0570.JPEG to
\\tsclient\Desktop\iCloud Photos\IMG_0570-01-01-2020-1535.JPEG?
--- Ctrl+C to cancel ---
Press Enter to continue...:
Renaming \\tsclient\Desktop\iCloud Photos\IMG_0392.JPEG to
\\tsclient\Desktop\iCloud Photos\IMG_0392-11-23-2019-2012.JPEG?
--- Ctrl+C to cancel ---
Press Enter to continue...:
--- Truncated for brevity --- 
Done.
#>

Leave a Reply

Your email address will not be published. Required fields are marked *