The Get-ExifDateTaken PowerShell Script Cmdlet

(Part 2 of 3) (Part 1) (Part 3)

{Updated: 08/2013}

The last blog post described how to update a whole bunch of photos using two script cmdlets called Get-ExifDateTaken and Update-ExifDateTaken.   This post describes the first of these script cmdlets in more detail.

There are several Windows APIs (Wia, GDI etc) that can read Exif values from image files.  I tend to avoid using COM if there’s a .Net alternative, so I used the [System.Drawing.Imaging.Metafile] class and its associated GetPropertyItem() method to read the Exif data I needed.

The script needs to handle pipeline input, so the first thing to do is to get the script Param() statement coded correctly:

[CmdletBinding()]Param (
[Parameter(Mandatory=$True,
ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$True)
]
[Alias(‘FullName’, ‘$FileName’)]
$Path
)

This allows image file names to be passed in as normal parameters or to be read from the pipeline using various aliases if needed.

The script cmdlet’s Process{} block needs to accept multiple file names, arrays of names, wildcards and so on.  This is all handled with a call to the Resolve-Path cmdlet and we iterate over the results in a foreach:

Process
{
# Cater for arrays of filenames and wild-cards by using Resolve-Path
Write-Verbose “Processing input item ‘$Path'”$PathItems=Resolve-Path $Path -ErrorAction SilentlyContinue -ErrorVariable ResolveError
If ($ResolveError) {
Write-Warning “Bad path ‘$Path’ ($($ResolveError[0].CategoryInfo.Category))”
}    Foreach ($PathItem in $PathItems) {
# Read the current file and extract the Exif DateTaken property# (……SNIP…)} # End Foreach Path} # End Process Block

Within the loop we open each image file into a FileStream (this is quicker than using the $Img.FromFile(…) method) and get the Exif DateTaken value by using GetPropertyItem(‘36867’) – Exif property 36867 is DateTaken; just love those magic numbers…

The value is converted to a [DateTime] and passed down the pipeline attached to the PathInfo object that we got from Resolve-Path.  Passing rich objects in this way (rather than just outputting the DateTaken value on its own, for example) ensures that stages further down the pipeline have access to all the information they might need.

Note that in the code snippet here the error checking code has been removed for clarity – see the full listing in the last post for the complete source:

$ImageFile=$PathItem.Path$FileStream=New-Object System.IO.FileStream($ImageFile,
[System.IO.FileMode]::Open,
[System.IO.FileAccess]::Read,
[System.IO.FileShare]::Read,
1024,     # Buffer size
[System.IO.FileOptions]::SequentialScan
)
$Img=[System.Drawing.Imaging.Metafile]::FromStream($FileStream)
$ExifDT=$Img.GetPropertyItem(‘36867’)# Convert the raw Exif data$ExifDtString=[System.Text.Encoding]::ASCII.GetString($ExifDT.Value)# Convert the result to a [DateTime]
# Note: This looks like a string, but it has a trailing zero (0x00)
# character that confuses ParseExact unless we include the zero
# in the ParseExact pattern….$OldTime=[datetime]::ParseExact($ExifDtString,“yyyy:MM:dd HH:mm:ss`0”,$Null)

$FileStream.Close(); $Img.Dispose()

Write-Verbose “Extracted EXIF infomation from $ImageFile”
Write-Verbose “Original Time is $($OldTime.ToString(‘F’))”

# Decorate the path object with the EXIF dates and pass it on…

$PathItem | Add-Member -MemberType NoteProperty -Name ExifDateTaken -Value $OldTime
Write-Output $PathItem

Note how easily PowerShell enable calls to .Net methods – a great feature of  PowerShell.

The final part will look at updating Exif dates and included the full code for the Get-ExifDateTaken and Update-ExifDateTaken cmdlets.

5 thoughts on “The Get-ExifDateTaken PowerShell Script Cmdlet”

Leave a comment