Search code examples
windowspowershellbatch-rename

How to batch rename files including a number and the modified date?


Edit: I should have gone for Date Taken instead, as Date Modified is sometimes off by one hour for the pictures we're working with.

I am trying to write something that will rename files to the following format:

24024 25-12-2014 20.18.JPG
24025 26-12-2014 18.01.JPG
24026 26-12-2014 18.01.JPG
24027 30-12-2014 17.05.JPG
24028 31-12-2014 15.09.JPG
24029 31-12-2014 15.19.JPG

I need this for organising my mother's pictures in the way my father designed. I looked specifically for ways to do it with a cmd batch file first, but it seemed too complicated. I am now trying to use the PowerShell.

I have tried this, which works:

Get-ChildItem *.JPG | Rename-Item -newname {$_.LastWriteTime.toString("dd-MM-yyyy HH.mm") + ".JPG"}

But I haven't managed to include a variable counting with that. This does not compile:

$a = 10; Get-ChildItem *.JPG | {Rename-Item -newname {$_.LastWriteTime.toString("dd-MM-yyyy HH.mm") + ".JPG"}; $a++}

Nor does this, which I found in another question.

Foreach ($Item in Get-ChildItem *.JPG) {Rename-Item -newname {$_.LastWriteTime.toString("dd-MM-yyyy HH.mm") + ".JPG"}}


Solution

  • You could do something like this:

    $Path = 'D:\'  # the folder where the jpg files are
    $Count = 10    # the starting number. gets increased for each file
    Get-ChildItem -Path $Path -Filter '*.JPG' -File | ForEach-Object {
        $_ | Rename-Item -NewName ('{0:00000} {1}.JPG' -f $Count++, ($_.LastWriteTime.toString("dd-MM-yyyy HH.mm")))
    }
    


    EDIT 1


    To name them in chronological order, just add a Sort-Object to the script, like this:

    $Path = 'D:\'  # the folder where the jpg files are
    $Count = 10    # the starting number. gets increased for each file
    Get-ChildItem -Path $Path -Filter '*.JPG' -File | Sort-Object LastWriteTime | ForEach-Object {
        $_ | Rename-Item -NewName ('{0:00000} {1}.JPG' -f $Count++, ($_.LastWriteTime.toString("dd-MM-yyyy HH.mm")))
    }
    


    EDIT 2


    As per your last comment, to get the date from the Exif data in the image, you need a function to get the DateTimeOriginal from the file if possible.

    You can do that with the code below:

    function Get-ExifDate {
        # returns the 'DateTimeOriginal' property from the Exif metadata in an image file if possible
        [CmdletBinding(DefaultParameterSetName = 'ByName')]
        Param (
            [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0, ParameterSetName = 'ByName')]
            [Alias('FullName', 'FileName')]
            [ValidateScript({ Test-Path -Path $_ -PathType Leaf})]
            [string]$Path,
    
            [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, ParameterSetName = 'ByObject')]
            [System.IO.FileInfo]$FileObject
        )
    
        Begin {
            Add-Type -AssemblyName 'System.Drawing'
        }
        Process {
            # the function received a path, not a file object
            if ($PSCmdlet.ParameterSetName -eq 'ByName') {
                $FileObject = Get-Item -Path $Path -Force -ErrorAction SilentlyContinue
            }
            # Parameters for FileStream: Open/Read/SequentialScan
            $streamArgs = @(
                $FileObject.FullName
                [System.IO.FileMode]::Open
                [System.IO.FileAccess]::Read
                [System.IO.FileShare]::Read
                1024,     # Buffer size
                [System.IO.FileOptions]::SequentialScan
            )
            try {
                $stream = New-Object System.IO.FileStream -ArgumentList $streamArgs
                $metaData = [System.Drawing.Imaging.Metafile]::FromStream($stream)
    
                # get the 'DateTimeOriginal' property (ID = 36867) from the metadata
                # Tag Dec  TagId Hex  TagName           Writable  Group    Notes
                # -------  ---------  -------           --------  -----    -----
                # 36867    0x9003     DateTimeOriginal  string    ExifIFD  (date/time when original image was taken)
                # see: https://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
    
                # get the date taken as an array of bytes
                $exifDateBytes = $metaData.GetPropertyItem(36867).Value
                # transform to string, but beware that this string is Null terminated, so cut off the trailing 0 character
                $exifDateString = [System.Text.Encoding]::ASCII.GetString($exifDateBytes).TrimEnd("`0")
                # return the parsed date
                return [datetime]::ParseExact($exifDateString, "yyyy:MM:dd HH:mm:ss", $null) 
            }
            catch{
                Write-Warning -Message "Could not read Exif data from '$($FileObject.FullName)'"
            }
            finally {
                If ($metaData) {$metaData.Dispose()}
                If ($stream)   {$stream.Close()}
            }
        }
    }
    

    Using that function, your code would look then like this:

    $Path = 'D:\'  # the folder where the jpg files are
    $Count = 10    # the starting number. gets increased for each file
    
    # start a loop to gather the files and reset their LastWriteTime property to the one read from the Exif data.
    # pipe the result to the Sort-Object cmdlet and enter another ForEach-Object loop to perform the rename.
    Get-ChildItem -Path $Path -Filter '*.JPG' -File | ForEach-Object {
        $date = $_ | Get-ExifDate
        if ($date) { 
            $_.LastWriteTime = $date
        }
        $_
    } | Sort-Object LastWriteTime | ForEach-Object {
        $newName = '{0:00000} {1}.JPG' -f $Count++, ($_.LastWriteTime.toString("dd-MM-yyyy HH.mm"))
        # output some info to the console
        Write-Host "Renaming file '$($_.Name)' to '$newName'"
        $_ | Rename-Item -NewName $newName
    }
    

    This uses string formatting -f. You give it a template string with numbered placeholders between curly braces.

    The first one {0:00000} is a way of formatting a number with preceeding zero characters up to a length of 5 characters in this case.

    The second one {1} gets replaced by the formatted date string.

    The $Count variable gets increased on each iteration using the ++ syntax.